diff --git a/graphql-server/package.json b/graphql-server/package.json index a366cc6b..f6b05be2 100644 --- a/graphql-server/package.json +++ b/graphql-server/package.json @@ -40,6 +40,7 @@ "graphql": "^16.7.1", "graphql-upload": "13", "mysql2": "^3.6.5", + "pg": "^8.11.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.5.4", "timeago.js": "^4.0.2", diff --git a/graphql-server/schema.gql b/graphql-server/schema.gql index 6aa15c51..800c5250 100644 --- a/graphql-server/schema.gql +++ b/graphql-server/schema.gql @@ -99,11 +99,18 @@ type DatabaseConnection { name: String! hideDoltFeatures: Boolean useSSL: Boolean + type: DatabaseType +} + +enum DatabaseType { + Mysql + Postgres } type DoltDatabaseDetails { isDolt: Boolean! hideDoltFeatures: Boolean! + type: DatabaseType! } type DiffStat { @@ -324,7 +331,7 @@ 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): String + addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean, type: DatabaseType): String removeDatabaseConnection(name: String!): Boolean! createDatabase(databaseName: String!): Boolean! resetDatabase: Boolean! diff --git a/graphql-server/src/connections/connection.resolver.ts b/graphql-server/src/connections/connection.resolver.ts index 24b79b7f..395da971 100644 --- a/graphql-server/src/connections/connection.resolver.ts +++ b/graphql-server/src/connections/connection.resolver.ts @@ -1,9 +1,11 @@ import { Resolver } from "@nestjs/graphql"; import * as mysql from "mysql2/promise"; import { DataSource } from "typeorm"; +import { DatabaseType } from "../databases/database.enum"; import { QueryFactory } from "../queryFactory"; import { DoltQueryFactory } from "../queryFactory/dolt"; import { MySQLQueryFactory } from "../queryFactory/mysql"; +import { PostgresQueryFactory } from "../queryFactory/postgres"; export class WorkbenchConfig { hideDoltFeatures: boolean; @@ -11,6 +13,8 @@ export class WorkbenchConfig { connectionUrl: string; useSSL: boolean; + + type: DatabaseType; } @Resolver() @@ -58,8 +62,8 @@ export class ConnectionResolver { this.workbenchConfig = config; this.ds = new DataSource({ - type: "mysql", - connectorPackage: "mysql2", + type: config.type, + connectorPackage: config.type === "mysql" ? "mysql2" : undefined, url: config.connectionUrl, ssl: config.useSSL ? { @@ -78,7 +82,7 @@ export class ConnectionResolver { await this.ds.initialize(); - const qf = await this.newQueryFactory(); + const qf = await this.newQueryFactory(config.type); this.qf = qf; } @@ -86,7 +90,10 @@ export class ConnectionResolver { return this.workbenchConfig; } - async newQueryFactory(): Promise { + async newQueryFactory(type: DatabaseType): Promise { + if (type === DatabaseType.Postgres) { + return new PostgresQueryFactory(this.ds); + } try { const res = await this.ds?.query("SELECT dolt_version()"); if (res) { diff --git a/graphql-server/src/databases/database.enum.ts b/graphql-server/src/databases/database.enum.ts new file mode 100644 index 00000000..73929acd --- /dev/null +++ b/graphql-server/src/databases/database.enum.ts @@ -0,0 +1,8 @@ +import { registerEnumType } from "@nestjs/graphql"; + +export enum DatabaseType { + Mysql = "mysql", + Postgres = "postgres", +} + +registerEnumType(DatabaseType, { name: "DatabaseType" }); diff --git a/graphql-server/src/databases/database.model.ts b/graphql-server/src/databases/database.model.ts index 25f859f5..9ed3bbc8 100644 --- a/graphql-server/src/databases/database.model.ts +++ b/graphql-server/src/databases/database.model.ts @@ -1,4 +1,5 @@ import { Field, ObjectType } from "@nestjs/graphql"; +import { DatabaseType } from "./database.enum"; @ObjectType() export class DatabaseConnection { @@ -13,4 +14,7 @@ export class DatabaseConnection { @Field({ nullable: true }) useSSL?: boolean; + + @Field(_type => DatabaseType, { nullable: true }) + type?: DatabaseType; } diff --git a/graphql-server/src/databases/database.resolver.ts b/graphql-server/src/databases/database.resolver.ts index 184932dd..64596606 100644 --- a/graphql-server/src/databases/database.resolver.ts +++ b/graphql-server/src/databases/database.resolver.ts @@ -10,6 +10,7 @@ import { import { ConnectionResolver } from "../connections/connection.resolver"; import { FileStoreService } from "../fileStore/fileStore.service"; import { DBArgs } from "../utils/commonTypes"; +import { DatabaseType } from "./database.enum"; import { DatabaseConnection } from "./database.model"; @ArgsType() @@ -25,6 +26,9 @@ class AddDatabaseConnectionArgs { @Field({ nullable: true }) useSSL?: boolean; + + @Field(_type => DatabaseType, { nullable: true }) + type?: DatabaseType; } @ObjectType() @@ -34,6 +38,9 @@ class DoltDatabaseDetails { @Field() hideDoltFeatures: boolean; + + @Field(_type => DatabaseType) + type: DatabaseType; } @ArgsType() @@ -64,15 +71,13 @@ export class DatabaseResolver { async databases(): Promise { const conn = this.conn.connection(); const dbs = await conn.databases(); - return dbs - .map(db => db.Database) - .filter( - db => - db !== "information_schema" && - db !== "mysql" && - db !== "dolt_cluster" && - !db.includes("/"), - ); + return dbs.filter( + db => + db !== "information_schema" && + db !== "mysql" && + db !== "dolt_cluster" && + !db.includes("/"), + ); } @Query(_returns => DoltDatabaseDetails) @@ -82,6 +87,7 @@ export class DatabaseResolver { return { isDolt: conn.isDolt, hideDoltFeatures: workbenchConfig?.hideDoltFeatures ?? false, + type: workbenchConfig?.type ?? DatabaseType.Mysql, }; } @@ -93,6 +99,7 @@ export class DatabaseResolver { connectionUrl: args.connectionUrl, hideDoltFeatures: !!args.hideDoltFeatures, useSSL: !!args.useSSL, + type: args.type ?? DatabaseType.Mysql, }; await this.conn.addConnection(workbenchConfig); diff --git a/graphql-server/src/queryFactory/dolt/index.ts b/graphql-server/src/queryFactory/dolt/index.ts index ed30da4d..e9fc20db 100644 --- a/graphql-server/src/queryFactory/dolt/index.ts +++ b/graphql-server/src/queryFactory/dolt/index.ts @@ -6,6 +6,7 @@ import * as foreignKey from "../../indexes/foreignKey.model"; import * as index from "../../indexes/index.model"; import { convertToStringForQuery } from "../../rowDiffs/rowDiff.enums"; import { SchemaType } from "../../schemas/schema.enums"; +import { SchemaItem } from "../../schemas/schema.model"; import { DoltSystemTable, systemTableValues, @@ -91,7 +92,7 @@ export class DoltQueryFactory return res.filter(c => c.Key === "PRI").map(c => c.Field); } - async getSchemas(args: t.RefArgs, type?: SchemaType): t.UPR { + async getSchemas(args: t.RefArgs, type?: SchemaType): Promise { return this.queryForBuilder( async em => { let sel = em @@ -103,21 +104,32 @@ export class DoltQueryFactory type, }); } - return handleTableNotFound(async () => sel.getRawMany()); + const res = await handleTableNotFound(async () => sel.getRawMany()); + if (!res) return []; + return res.map(r => { + return { name: r.name, type: r.type }; + }); }, args.databaseName, args.refName, ); } - async getProcedures(args: t.RefArgs): t.UPR { + async getProcedures(args: t.RefArgs): Promise { return this.queryForBuilder( async em => { const sel = em .createQueryBuilder() .select("*") .from(DoltSystemTable.PROCEDURES, ""); - return handleTableNotFound(async () => sel.getRawMany()); + const res = await handleTableNotFound(async () => sel.getRawMany()); + if (!res) return []; + return res.map(r => { + return { + name: r.name, + type: SchemaType.Procedure, + }; + }); }, args.databaseName, args.refName, diff --git a/graphql-server/src/queryFactory/index.ts b/graphql-server/src/queryFactory/index.ts index 9cdb8c8e..e7c1dda4 100644 --- a/graphql-server/src/queryFactory/index.ts +++ b/graphql-server/src/queryFactory/index.ts @@ -2,6 +2,7 @@ import { DataSource, EntityManager, QueryRunner } from "typeorm"; import { SortBranchesBy } from "../branches/branch.enum"; import { CommitDiffType } from "../diffSummaries/diffSummary.enums"; import { SchemaType } from "../schemas/schema.enums"; +import { SchemaItem } from "../schemas/schema.model"; import { TableDetails } from "../tables/table.model"; import * as t from "./types"; @@ -47,7 +48,7 @@ export declare class QueryFactory { // QUERIES - databases(): t.PR; + databases(): Promise; currentDatabase(): Promise; @@ -68,9 +69,9 @@ export declare class QueryFactory { getSqlSelect(args: t.RefArgs & { queryString: string }): t.PR; - getSchemas(args: t.RefArgs, type?: SchemaType): t.UPR; + getSchemas(args: t.RefArgs, type?: SchemaType): Promise; - getProcedures(args: t.RefArgs): t.UPR; + getProcedures(args: t.RefArgs): Promise; // DOLT-SPECIFIC QUERIES diff --git a/graphql-server/src/queryFactory/mysql/index.ts b/graphql-server/src/queryFactory/mysql/index.ts index b14a4d35..9d563d03 100644 --- a/graphql-server/src/queryFactory/mysql/index.ts +++ b/graphql-server/src/queryFactory/mysql/index.ts @@ -3,6 +3,7 @@ import { EntityManager, QueryRunner } from "typeorm"; import { QueryFactory } from ".."; import { SchemaType } from "../../schemas/schema.enums"; +import { SchemaItem } from "../../schemas/schema.model"; import { TableDetails } from "../../tables/table.model"; import { ROW_LIMIT } from "../../utils"; import { BaseQueryFactory } from "../base"; @@ -16,6 +17,14 @@ export class MySQLQueryFactory { isDolt = false; + async checkoutDatabase( + qr: QueryRunner, + dbName: string, + refName?: string, + ): Promise { + await qr.query(qh.useDB(dbName, refName, this.isDolt)); + } + async query( q: string, p: t.Params, @@ -24,7 +33,7 @@ export class MySQLQueryFactory ): Promise { return this.handleAsyncQuery(async qr => { if (dbName) { - await qr.query(qh.useDB(dbName, refName, this.isDolt)); + await this.checkoutDatabase(qr, dbName, refName); } const res = await qr.query(q, p); @@ -44,7 +53,7 @@ export class MySQLQueryFactory } if (dbName) { - await qr.query(qh.useDB(dbName, refName, this.isDolt)); + await this.checkoutDatabase(qr, dbName, refName); } return executeQuery(query); @@ -58,7 +67,7 @@ export class MySQLQueryFactory ): Promise { return this.handleAsyncQuery(async qr => { if (dbName) { - await qr.query(qh.useDB(dbName, refName, this.isDolt)); + await this.checkoutDatabase(qr, dbName, refName); } return executeQuery(qr.manager); @@ -72,15 +81,16 @@ export class MySQLQueryFactory ): Promise { return this.handleAsyncQuery(async qr => { if (dbName) { - await qr.query(qh.useDB(dbName, refName, this.isDolt)); + await this.checkoutDatabase(qr, dbName, refName); } return executeQuery(qr); }); } - async databases(): t.PR { - return this.query(qh.databasesQuery, []); + async databases(): Promise { + const res: t.RawRows = await this.query(qh.databasesQuery, []); + return res.map(r => r.Database); } async getTableNames(args: t.RefArgs): Promise { @@ -137,7 +147,7 @@ export class MySQLQueryFactory return this.query(args.queryString, [], args.databaseName, args.refName); } - async getSchemas(args: t.DBArgs, type?: SchemaType): t.UPR { + async getSchemas(args: t.DBArgs, type?: SchemaType): Promise { return this.queryMultiple(async query => { const vRes = await query(qh.getViewsQuery, [args.databaseName]); const views = vRes.map(v => { @@ -161,12 +171,15 @@ export class MySQLQueryFactory }, args.databaseName); } - async getProcedures(args: t.DBArgs): t.UPR { - return this.query( + async getProcedures(args: t.DBArgs): Promise { + const res: t.RawRows = await this.query( qh.proceduresQuery, [args.databaseName], args.databaseName, ); + return res.map(r => { + return { name: r.Name, type: SchemaType.Procedure }; + }); } // DOLT QUERIES NOT IMPLEMENTED FOR MYSQL diff --git a/graphql-server/src/queryFactory/postgres/index.ts b/graphql-server/src/queryFactory/postgres/index.ts new file mode 100644 index 00000000..bbbea0f2 --- /dev/null +++ b/graphql-server/src/queryFactory/postgres/index.ts @@ -0,0 +1,83 @@ +import { QueryRunner } from "typeorm"; +import { QueryFactory } from ".."; +import { SchemaType } from "../../schemas/schema.enums"; +import { SchemaItem } from "../../schemas/schema.model"; +import { TableDetails } from "../../tables/table.model"; +import { MySQLQueryFactory } from "../mysql"; +import { convertToTableDetails } from "../mysql/utils"; +import * as t from "../types"; +import * as qh from "./queries"; + +export class PostgresQueryFactory + extends MySQLQueryFactory + implements QueryFactory +{ + isDolt = false; + + async databases(): Promise { + const res: t.RawRows = await this.query(qh.databasesQuery, []); + return res.map(r => r.schema_name); + } + + async checkoutDatabase(qr: QueryRunner, dbName: string): Promise { + await qr.query(qh.setSearchPath(dbName, this.isDolt)); + } + + async getTableNames(args: t.RefArgs): Promise { + const res: t.RawRows = await this.query( + qh.listTablesQuery, + [args.databaseName], + args.databaseName, + ); + return res.map(r => r.tablename); + } + + async getTableInfo(args: t.TableArgs): Promise { + return this.queryQR(async qr => { + const table = await qr.getTable(`${args.databaseName}.${args.tableName}`); + if (!table) return undefined; + return convertToTableDetails(table); + }, args.databaseName); + } + + async getTables(args: t.RefArgs, tns: string[]): Promise { + const names = tns.map(tn => `${args.databaseName}.${tn}`); + return this.queryQR(async qr => { + const tables = await qr.getTables(names); + return tables.map(convertToTableDetails); + }, args.databaseName); + } + + async getSchemas(args: t.DBArgs, type?: SchemaType): Promise { + return this.queryMultiple(async query => { + const vRes = await query(qh.getViewsQuery, [args.databaseName]); + const views = vRes.map(v => { + return { name: v.table_name, type: SchemaType.View }; + }); + if (type === SchemaType.View) { + return views; + } + const tRes = await query(qh.getTriggersQuery, [args.databaseName]); + const triggers = tRes.map(tr => { + return { name: tr.trigger_name, type: SchemaType.Trigger }; + }); + + const eRes = await query(qh.getEventsQuery); + const events = eRes.map(e => { + return { name: e.evtname, type: SchemaType.Event }; + }); + return [...views, ...triggers, ...events]; + }, args.databaseName); + } + + async getProcedures(args: t.DBArgs): Promise { + const res: t.RawRows = await this.query( + qh.getProceduresQuery, + [args.databaseName], + args.databaseName, + ); + return res.map(r => { + return { name: r.proname, type: SchemaType.Procedure }; + }); + } +} diff --git a/graphql-server/src/queryFactory/postgres/queries.ts b/graphql-server/src/queryFactory/postgres/queries.ts new file mode 100644 index 00000000..7e1d5cc6 --- /dev/null +++ b/graphql-server/src/queryFactory/postgres/queries.ts @@ -0,0 +1,21 @@ +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 schema_name FROM information_schema.schemata;`; + +export const getViewsQuery = `SELECT table_name FROM INFORMATION_SCHEMA.views WHERE table_schema = $1`; + +export const getTriggersQuery = `SELECT trigger_name +FROM information_schema.triggers +where trigger_schema = $1`; + +export const getEventsQuery = `select evtname from pg_event_trigger`; + +export const getProceduresQuery = `SELECT proname +FROM pg_catalog.pg_namespace +JOIN pg_catalog.pg_proc +ON pronamespace = pg_namespace.oid +WHERE nspname = $1 +`; diff --git a/graphql-server/src/schemas/schema.resolver.ts b/graphql-server/src/schemas/schema.resolver.ts index 50f8c97a..ba3d75e7 100644 --- a/graphql-server/src/schemas/schema.resolver.ts +++ b/graphql-server/src/schemas/schema.resolver.ts @@ -15,10 +15,7 @@ export class SchemaResolver { ): Promise { const conn = this.conn.connection(); const res = await conn.getSchemas(args, type); - if (!res) return []; - return res.map(r => { - return { name: r.name, type: r.type }; - }); + return res; } @Query(_returns => [SchemaItem]) @@ -30,12 +27,6 @@ export class SchemaResolver { async doltProcedures(@Args() args: RefArgs): Promise { const conn = this.conn.connection(); const res = await conn.getProcedures(args); - if (!res) return []; - return res.map(r => { - return { - name: conn.isDolt ? r.name : r.Name, - type: SchemaType.Procedure, - }; - }); + return res; } } diff --git a/graphql-server/yarn.lock b/graphql-server/yarn.lock index e631d465..9291d281 100644 --- a/graphql-server/yarn.lock +++ b/graphql-server/yarn.lock @@ -743,6 +743,7 @@ __metadata: graphql-upload: 13 jest: ^29.5.0 mysql2: ^3.6.5 + pg: ^8.11.3 prettier: ^3.0.3 reflect-metadata: ^0.1.13 rxjs: ^7.5.4 @@ -3010,6 +3011,13 @@ __metadata: languageName: node linkType: hard +"buffer-writer@npm:2.0.0": + version: 2.0.0 + resolution: "buffer-writer@npm:2.0.0" + checksum: 11736b48bb75106c52ca8ec9f025e7c1b3b25ce31875f469d7210eabd5c576c329e34f6b805d4a8d605ff3f0db1e16342328802c4c963e9c826b0e43a4e631c2 + languageName: node + linkType: hard + "buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" @@ -7089,6 +7097,13 @@ __metadata: languageName: node linkType: hard +"packet-reader@npm:1.0.0": + version: 1.0.0 + resolution: "packet-reader@npm:1.0.0" + checksum: 0b7516f0cbf3e322aad591bed29ba544220088c53943145c0d9121a6f59182ad811f7fd6785a8979a34356aca69d97653689029964c5998dc02645633d88ffd7 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -7199,6 +7214,89 @@ __metadata: languageName: node linkType: hard +"pg-cloudflare@npm:^1.1.1": + version: 1.1.1 + resolution: "pg-cloudflare@npm:1.1.1" + checksum: 32aac06b5dc4588bbf78801b6267781bc7e13be672009df949d08e9627ba9fdc26924916665d4de99d47f9b0495301930547488dad889d826856976c7b3f3731 + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.6.2": + version: 2.6.2 + resolution: "pg-connection-string@npm:2.6.2" + checksum: 22265882c3b6f2320785378d0760b051294a684989163d5a1cde4009e64e84448d7bf67d9a7b9e7f69440c3ee9e2212f9aa10dd17ad6773f6143c6020cebbcb5 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9 + languageName: node + linkType: hard + +"pg-pool@npm:^3.6.1": + version: 3.6.1 + resolution: "pg-pool@npm:3.6.1" + peerDependencies: + pg: ">=8.0" + checksum: 8a6513e6f74a794708c9dd16d2ccda0debadc56435ec2582de2b2e35b01315550c5dab8a0a9a2a16f4adce45523228f5739940fb7687ec7e9c300f284eb08fd1 + languageName: node + linkType: hard + +"pg-protocol@npm:^1.6.0": + version: 1.6.0 + resolution: "pg-protocol@npm:1.6.0" + checksum: e12662d2de2011e0c3a03f6a09f435beb1025acdc860f181f18a600a5495dc38a69d753bbde1ace279c8c442536af9c1a7c11e1d0fe3fad3aa1348b28d9d2683 + languageName: node + linkType: hard + +"pg-types@npm:^2.1.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: 1.0.1 + postgres-array: ~2.0.0 + postgres-bytea: ~1.0.0 + postgres-date: ~1.0.4 + postgres-interval: ^1.1.0 + checksum: bf4ec3f594743442857fb3a8dfe5d2478a04c98f96a0a47365014557cbc0b4b0cee01462c79adca863b93befbf88f876299b75b72c665b5fb84a2c94fbd10316 + languageName: node + linkType: hard + +"pg@npm:^8.11.3": + version: 8.11.3 + resolution: "pg@npm:8.11.3" + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-cloudflare: ^1.1.1 + pg-connection-string: ^2.6.2 + pg-pool: ^3.6.1 + pg-protocol: ^1.6.0 + pg-types: ^2.1.0 + pgpass: 1.x + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 8af9468b8969fa0d73a6b349216c8cbc953d938fcae5594f2d24043060e9226a072c8085fc4230172b5576fcab4c39c8563c655f271dc2a9209b6ad5370cafe5 + languageName: node + linkType: hard + +"pgpass@npm:1.x": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: ^4.1.0 + checksum: 947ac096c031eebdf08d989de2e9f6f156b8133d6858c7c2c06c041e1e71dda6f5f3bad3c0ec1e96a09497bbc6ef89e762eefe703b5ef9cb2804392ec52ec400 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -7236,6 +7334,36 @@ __metadata: languageName: node linkType: hard +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 0e1e659888147c5de579d229a2d95c0d83ebdbffc2b9396d890a123557708c3b758a0a97ed305ce7f58edfa961fa9f0bbcd1ea9f08b6e5df73322e683883c464 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.0 + resolution: "postgres-bytea@npm:1.0.0" + checksum: d844ae4ca7a941b70e45cac1261a73ee8ed39d72d3d74ab1d645248185a1b7f0ac91a3c63d6159441020f4e1f7fe64689ac56536a307b31cef361e5187335090 + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 5745001d47e51cd767e46bcb1710649cd705d91a24d42fa661c454b6dcbb7353c066a5047983c90a626cd3bbfea9e626cc6fa84a35ec57e5bbb28b49f78e13ed + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: ^4.0.0 + checksum: 746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -7923,6 +8051,13 @@ __metadata: languageName: node linkType: hard +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 05d54102546549fe4d2455900699056580cca006c0275c334611420f854da30ac999230857a85fdd9914dc2109ae50f80fda43d2a445f2aa86eccdc1dfce779d + languageName: node + linkType: hard + "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" diff --git a/web/components/DatabaseHeaderAndNav/Full.tsx b/web/components/DatabaseHeaderAndNav/Full.tsx index 4d0ad6ff..ebcf05e4 100644 --- a/web/components/DatabaseHeaderAndNav/Full.tsx +++ b/web/components/DatabaseHeaderAndNav/Full.tsx @@ -1,5 +1,5 @@ import DatabaseNav from "@components/DatabaseNav"; -import PermissionLabel from "@components/PermissionLabel"; +import DatabaseTypeLabel from "@components/DatabaseTypeLabel"; import DatabaseBreadcrumbs from "@components/breadcrumbs/DatabaseBreadcrumbs"; import { OptionalRefParams } from "@lib/params"; import cx from "classnames"; @@ -23,17 +23,11 @@ export default function Full(props: Props) { >
- {/* - - */} - +
; + + return ( +
+ + {getDatabaseType( + data?.doltDatabaseDetails.type, + data?.doltDatabaseDetails.isDolt, + )} + +
+ ); +} + +function getDatabaseType(t?: DatabaseType, isDolt?: boolean): string { + switch (t) { + case DatabaseType.Mysql: + return isDolt ? "Dolt" : "MySQL"; + case DatabaseType.Postgres: + return "PostgreSQL"; + default: + return isDolt ? "Dolt" : "MySQL"; + } +} diff --git a/web/components/PermissionLabel/index.tsx b/web/components/PermissionLabel/index.tsx deleted file mode 100644 index 1d475490..00000000 --- a/web/components/PermissionLabel/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import useRole from "@hooks/useRole"; -import cx from "classnames"; -import css from "./index.module.css"; - -type InnerProps = { - // params: DeploymentParams; - className?: string; -}; - -export default function PermissionLabel({ className }: InnerProps) { - const { writesEnabled } = useRole(); - // if (!depRole) return null; - - if (!writesEnabled) { - return ( -
- Read Only -
- ); - } - - return ( -
- Admin -
- ); -} - -// export function getDeploymentRole(r: DeploymentRole): string { -// switch (r) { -// case DeploymentRole.Reader: -// return "Read"; -// case DeploymentRole.Writer: -// return "Write"; -// default: -// return r; -// } -// } diff --git a/web/components/pageComponents/ConnectionsPage/NewConnection/index.module.css b/web/components/pageComponents/ConnectionsPage/NewConnection/index.module.css index d103aed4..4a5de418 100644 --- a/web/components/pageComponents/ConnectionsPage/NewConnection/index.module.css +++ b/web/components/pageComponents/ConnectionsPage/NewConnection/index.module.css @@ -14,6 +14,10 @@ @apply px-14 py-4; } +.typeSelect { + @apply ml-8 mb-4; +} + .middle { @apply border-y pt-8; } diff --git a/web/components/pageComponents/ConnectionsPage/NewConnection/index.tsx b/web/components/pageComponents/ConnectionsPage/NewConnection/index.tsx index e2a2e9bd..de8285c6 100644 --- a/web/components/pageComponents/ConnectionsPage/NewConnection/index.tsx +++ b/web/components/pageComponents/ConnectionsPage/NewConnection/index.tsx @@ -2,8 +2,10 @@ import Button from "@components/Button"; import ButtonsWithError from "@components/ButtonsWithError"; import CustomCheckbox from "@components/CustomCheckbox"; import FormInput from "@components/FormInput"; +import FormSelect from "@components/FormSelect"; import Loader from "@components/Loader"; import ExternalLink from "@components/links/ExternalLink"; +import { DatabaseType } from "@gen/graphql-types"; import { dockerHubRepo } from "@lib/constants"; import { FaCaretDown } from "@react-icons/all-files/fa/FaCaretDown"; import { FaCaretUp } from "@react-icons/all-files/fa/FaCaretUp"; @@ -44,23 +46,40 @@ export default function NewConnection(props: Props) { View instructions for connecting to local and Docker installed databases here.

-
- setState({ name: n })} - label="Name" - placeholder="my-database (required)" - horizontal - light - /> -
+ setState({ name: n })} + label="Name" + placeholder="my-database (required)" + horizontal + light + /> + + setState({ + type: t, + port: t === DatabaseType.Mysql ? "3306" : "5432", + }) + } + options={[ + { label: "MySQL/Dolt", value: DatabaseType.Mysql }, + { label: "PostreSQL", value: DatabaseType.Postgres }, + ]} + horizontal + light + />
setState({ connectionUrl: c })} label="URL" - placeholder="mysql://[user]:[password]@[host]/[database]" + placeholder={`${ + state.type === DatabaseType.Mysql ? "mysql" : "postgresql" + }://[user]:[password]@[host]/[database]`} horizontal light /> @@ -77,7 +96,7 @@ export default function NewConnection(props: Props) { label="Port" value={state.port} onChangeString={p => setState({ port: p })} - placeholder="3306" + placeholder={state.type === DatabaseType.Mysql ? "3306" : "5432"} horizontal light /> diff --git a/web/components/pageComponents/ConnectionsPage/NewConnection/useConfig.ts b/web/components/pageComponents/ConnectionsPage/NewConnection/useConfig.ts index 8986f514..6e4d2ec6 100644 --- a/web/components/pageComponents/ConnectionsPage/NewConnection/useConfig.ts +++ b/web/components/pageComponents/ConnectionsPage/NewConnection/useConfig.ts @@ -1,4 +1,7 @@ -import { useAddDatabaseConnectionMutation } from "@gen/graphql-types"; +import { + DatabaseType, + useAddDatabaseConnectionMutation, +} from "@gen/graphql-types"; import useSetState from "@hooks/useSetState"; import { maybeDatabase } from "@lib/urls"; import { useRouter } from "next/router"; @@ -16,6 +19,7 @@ const defaultState = { useSSL: true, showAdvancedSettings: false, loading: false, + type: DatabaseType.Mysql, }; type ConfigState = typeof defaultState; @@ -43,7 +47,9 @@ export default function useConfig(): ReturnType { setState({ loading: true }); const url = state.connectionUrl || - `mysql://${state.username}:${state.password}@${state.host}:${state.port}/${state.database}`; + `${state.type === DatabaseType.Mysql ? "mysql" : "postgresql"}://${ + state.username + }:${state.password}@${state.host}:${state.port}/${state.database}`; try { const db = await addDb({ @@ -52,6 +58,7 @@ export default function useConfig(): ReturnType { connectionUrl: url, hideDoltFeatures: state.hideDoltFeatures, useSSL: state.useSSL, + type: state.type, }, }); await res.client.clearStore(); diff --git a/web/components/pageComponents/ConnectionsPage/queries.ts b/web/components/pageComponents/ConnectionsPage/queries.ts index 35b55385..30abbec0 100644 --- a/web/components/pageComponents/ConnectionsPage/queries.ts +++ b/web/components/pageComponents/ConnectionsPage/queries.ts @@ -6,12 +6,14 @@ export const ADD_DATABASE_CONNECTION = gql` $name: String! $hideDoltFeatures: Boolean $useSSL: Boolean + $type: DatabaseType ) { addDatabaseConnection( connectionUrl: $connectionUrl name: $name hideDoltFeatures: $hideDoltFeatures useSSL: $useSSL + type: $type ) } `; @@ -22,6 +24,7 @@ export const STORED_CONNECTIONS = gql` name useSSL hideDoltFeatures + type } query StoredConnections { storedConnections { diff --git a/web/components/util/NotDoltWrapper/mocks.ts b/web/components/util/NotDoltWrapper/mocks.ts index 6dd1bfe3..4b19c545 100644 --- a/web/components/util/NotDoltWrapper/mocks.ts +++ b/web/components/util/NotDoltWrapper/mocks.ts @@ -1,14 +1,21 @@ import { MockedResponse } from "@apollo/client/testing"; -import { DoltDatabaseDetailsDocument } from "@gen/graphql-types"; +import { DatabaseType, DoltDatabaseDetailsDocument } from "@gen/graphql-types"; export const databaseDetailsMock = ( isDolt: boolean, hideDoltFeatures: boolean, + isPostgres = false, ): MockedResponse => { return { request: { query: DoltDatabaseDetailsDocument }, result: { - data: { doltDatabaseDetails: { isDolt, hideDoltFeatures } }, + data: { + doltDatabaseDetails: { + isDolt, + hideDoltFeatures, + type: isPostgres ? DatabaseType.Postgres : DatabaseType.Mysql, + }, + }, }, }; }; diff --git a/web/components/util/NotDoltWrapper/queries.ts b/web/components/util/NotDoltWrapper/queries.ts index 8e0152d2..ea0727d3 100644 --- a/web/components/util/NotDoltWrapper/queries.ts +++ b/web/components/util/NotDoltWrapper/queries.ts @@ -5,6 +5,7 @@ export const DOLT_DATABASE_DETAILS = gql` doltDatabaseDetails { isDolt hideDoltFeatures + type } } `; diff --git a/web/gen/graphql-types.tsx b/web/gen/graphql-types.tsx index bd4dbb7b..d460b10a 100644 --- a/web/gen/graphql-types.tsx +++ b/web/gen/graphql-types.tsx @@ -93,9 +93,15 @@ export type DatabaseConnection = { connectionUrl: Scalars['String']['output']; hideDoltFeatures?: Maybe; name: Scalars['String']['output']; + type?: Maybe; useSSL?: Maybe; }; +export enum DatabaseType { + Mysql = 'Mysql', + Postgres = 'Postgres' +} + export enum DiffRowType { Added = 'Added', All = 'All', @@ -147,6 +153,7 @@ export type DoltDatabaseDetails = { __typename?: 'DoltDatabaseDetails'; hideDoltFeatures: Scalars['Boolean']['output']; isDolt: Scalars['Boolean']['output']; + type: DatabaseType; }; export type DoltWriter = { @@ -218,6 +225,7 @@ export type MutationAddDatabaseConnectionArgs = { connectionUrl: Scalars['String']['input']; hideDoltFeatures?: InputMaybe; name: Scalars['String']['input']; + type?: InputMaybe; useSSL?: InputMaybe; }; @@ -857,17 +865,18 @@ export type AddDatabaseConnectionMutationVariables = Exact<{ name: Scalars['String']['input']; hideDoltFeatures?: InputMaybe; useSSL?: InputMaybe; + type?: InputMaybe; }>; export type AddDatabaseConnectionMutation = { __typename?: 'Mutation', addDatabaseConnection?: string | null }; -export type DatabaseConnectionFragment = { __typename?: 'DatabaseConnection', connectionUrl: string, name: string, useSSL?: boolean | null, hideDoltFeatures?: boolean | null }; +export type DatabaseConnectionFragment = { __typename?: 'DatabaseConnection', connectionUrl: string, name: string, useSSL?: boolean | null, hideDoltFeatures?: boolean | null, type?: DatabaseType | null }; export type StoredConnectionsQueryVariables = Exact<{ [key: string]: never; }>; -export type StoredConnectionsQuery = { __typename?: 'Query', storedConnections: Array<{ __typename?: 'DatabaseConnection', connectionUrl: string, name: string, useSSL?: boolean | null, hideDoltFeatures?: boolean | null }> }; +export type StoredConnectionsQuery = { __typename?: 'Query', storedConnections: Array<{ __typename?: 'DatabaseConnection', connectionUrl: string, name: string, useSSL?: boolean | null, hideDoltFeatures?: boolean | null, type?: DatabaseType | null }> }; export type RemoveConnectionMutationVariables = Exact<{ name: Scalars['String']['input']; @@ -1034,7 +1043,7 @@ export type LoadDataMutation = { __typename?: 'Mutation', loadDataFile: boolean export type DoltDatabaseDetailsQueryVariables = Exact<{ [key: string]: never; }>; -export type DoltDatabaseDetailsQuery = { __typename?: 'Query', doltDatabaseDetails: { __typename?: 'DoltDatabaseDetails', isDolt: boolean, hideDoltFeatures: boolean } }; +export type DoltDatabaseDetailsQuery = { __typename?: 'Query', doltDatabaseDetails: { __typename?: 'DoltDatabaseDetails', isDolt: boolean, hideDoltFeatures: boolean, type: DatabaseType } }; export type ColumnForDataTableFragment = { __typename?: 'Column', name: string, isPrimaryKey: boolean, type: string, sourceTable?: string | null, constraints?: Array<{ __typename?: 'ColConstraint', notNull: boolean }> | null }; @@ -1338,6 +1347,7 @@ export const DatabaseConnectionFragmentDoc = gql` name useSSL hideDoltFeatures + type } `; export const BranchFragmentDoc = gql` @@ -2338,12 +2348,13 @@ export type RowsForViewsLazyQueryHookResult = ReturnType; export type RowsForViewsQueryResult = Apollo.QueryResult; export const AddDatabaseConnectionDocument = gql` - mutation AddDatabaseConnection($connectionUrl: String!, $name: String!, $hideDoltFeatures: Boolean, $useSSL: Boolean) { + mutation AddDatabaseConnection($connectionUrl: String!, $name: String!, $hideDoltFeatures: Boolean, $useSSL: Boolean, $type: DatabaseType) { addDatabaseConnection( connectionUrl: $connectionUrl name: $name hideDoltFeatures: $hideDoltFeatures useSSL: $useSSL + type: $type ) } `; @@ -2366,6 +2377,7 @@ export type AddDatabaseConnectionMutationFn = Apollo.MutationFunction