diff --git a/graphql-server/schema.gql b/graphql-server/schema.gql index 04f86581..69edff00 100644 --- a/graphql-server/schema.gql +++ b/graphql-server/schema.gql @@ -322,7 +322,7 @@ enum DiffRowType { } type Mutation { - createBranch(databaseName: String!, newBranchName: String!, fromRefName: String!): Branch! + createBranch(databaseName: String!, newBranchName: String!, fromRefName: String!): String! deleteBranch(databaseName: String!, branchName: String!): Boolean! addDatabaseConnection(connectionUrl: String!, name: String!, hideDoltFeatures: Boolean, useSSL: Boolean): String removeDatabaseConnection(name: String!): Boolean! @@ -330,7 +330,7 @@ type Mutation { 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! - createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!): Tag! + createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!): String! deleteTag(databaseName: String!, tagName: String!): Boolean! } diff --git a/graphql-server/src/app.module.ts b/graphql-server/src/app.module.ts index 5a7d8934..fed6de9c 100644 --- a/graphql-server/src/app.module.ts +++ b/graphql-server/src/app.module.ts @@ -2,7 +2,6 @@ import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo"; import { Module } from "@nestjs/common"; import { GraphQLModule } from "@nestjs/graphql"; import { TerminusModule } from "@nestjs/terminus"; -import { DataSourceModule } from "./dataSources/dataSource.module"; import { FileStoreModule } from "./fileStore/fileStore.module"; import resolvers from "./resolvers"; @@ -13,7 +12,6 @@ import resolvers from "./resolvers"; context: ctx => ctx, driver: ApolloDriver, }), - DataSourceModule, FileStoreModule, TerminusModule, ], diff --git a/graphql-server/src/branches/branch.model.ts b/graphql-server/src/branches/branch.model.ts index 9c76c284..b79b7bee 100644 --- a/graphql-server/src/branches/branch.model.ts +++ b/graphql-server/src/branches/branch.model.ts @@ -1,7 +1,7 @@ import { Field, GraphQLTimestamp, ID, ObjectType } from "@nestjs/graphql"; +import { RawRow } from "../queryFactory/types"; import * as table from "../tables/table.model"; import { convertToUTCDate } from "../utils"; -import { RawRow } from "../utils/commonTypes"; @ObjectType() export class Branch { diff --git a/graphql-server/src/branches/branch.queries.ts b/graphql-server/src/branches/branch.queries.ts deleted file mode 100644 index 14dc9701..00000000 --- a/graphql-server/src/branches/branch.queries.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { SortBranchesBy } from "./branch.enum"; - -export const branchQuery = `SELECT * FROM dolt_branches WHERE name=?`; - -export const getBranchesQuery = (sortBy?: SortBranchesBy) => - `SELECT * FROM dolt_branches ${getOrderByForBranches(sortBy)}`; - -export const callNewBranch = `CALL DOLT_BRANCH(?, ?)`; - -export const callDeleteBranch = `CALL DOLT_BRANCH("-D", ?)`; - -function getOrderByForBranches(sortBy?: SortBranchesBy): string { - switch (sortBy) { - case SortBranchesBy.LastUpdated: - return "ORDER BY latest_commit_date DESC "; - default: - return ""; - } -} diff --git a/graphql-server/src/branches/branch.resolver.ts b/graphql-server/src/branches/branch.resolver.ts index aad68022..fe8846d1 100644 --- a/graphql-server/src/branches/branch.resolver.ts +++ b/graphql-server/src/branches/branch.resolver.ts @@ -8,23 +8,12 @@ import { ResolveField, Resolver, } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { Table } from "../tables/table.model"; import { TableResolver } from "../tables/table.resolver"; import { BranchArgs, DBArgs } from "../utils/commonTypes"; import { SortBranchesBy } from "./branch.enum"; -import { - Branch, - BranchNamesList, - branchForNonDoltDB, - fromDoltBranchesRow, -} from "./branch.model"; -import { - branchQuery, - callDeleteBranch, - callNewBranch, - getBranchesQuery, -} from "./branch.queries"; +import { Branch, BranchNamesList, fromDoltBranchesRow } from "./branch.model"; @ArgsType() export class GetBranchOrDefaultArgs extends DBArgs { @@ -62,20 +51,16 @@ class ListBranchesArgs extends DBArgs { @Resolver(_of => Branch) export class BranchResolver { constructor( - private readonly dss: DataSourceService, + private readonly conn: ConnectionResolver, private readonly tableResolver: TableResolver, ) {} @Query(_returns => Branch, { nullable: true }) async branch(@Args() args: BranchArgs): Promise { - return this.dss.queryMaybeDolt(async (query, isDolt) => { - if (!isDolt) { - return branchForNonDoltDB(args.databaseName); - } - const branch = await query(branchQuery, [args.branchName]); - if (!branch.length) return undefined; - return fromDoltBranchesRow(args.databaseName, branch[0]); - }, args.databaseName); + const conn = this.conn.connection(); + const res = await conn.getBranch(args); + if (!res.length) return undefined; + return fromDoltBranchesRow(args.databaseName, res[0]); } @Query(_returns => Branch, { nullable: true }) @@ -92,17 +77,11 @@ export class BranchResolver { @Query(_returns => BranchNamesList) async branches(@Args() args: ListBranchesArgs): Promise { - return this.dss.queryMaybeDolt(async (query, isDolt) => { - if (!isDolt) { - return { - list: [branchForNonDoltDB(args.databaseName)], - }; - } - const branches = await query(getBranchesQuery(args.sortBy)); - return { - list: branches.map(b => fromDoltBranchesRow(args.databaseName, b)), - }; - }, args.databaseName); + const conn = this.conn.connection(); + const res = await conn.getBranches(args); + return { + list: res.map(b => fromDoltBranchesRow(args.databaseName, b)), + }; } @Query(_returns => Branch, { nullable: true }) @@ -111,22 +90,18 @@ export class BranchResolver { return getDefaultBranchFromBranchesList(branchNames.list); } - @Mutation(_returns => Branch) - async createBranch(@Args() args: CreateBranchArgs): Promise { - return this.dss.query(async query => { - await query(callNewBranch, [args.newBranchName, args.fromRefName]); - const branch = await query(branchQuery, [args.newBranchName]); - if (!branch.length) throw new Error("Created branch not found"); - return fromDoltBranchesRow(args.databaseName, branch[0]); - }, args.databaseName); + @Mutation(_returns => String) + async createBranch(@Args() args: CreateBranchArgs): Promise { + const conn = this.conn.connection(); + await conn.createNewBranch({ ...args, branchName: args.newBranchName }); + return args.newBranchName; } @Mutation(_returns => Boolean) async deleteBranch(@Args() args: BranchArgs): Promise { - return this.dss.query(async query => { - await query(callDeleteBranch, [args.branchName]); - return true; - }, args.databaseName); + const conn = this.conn.connection(); + await conn.callDeleteBranch(args); + return true; } @ResolveField(_returns => Table, { nullable: true }) diff --git a/graphql-server/src/columns/column.model.ts b/graphql-server/src/columns/column.model.ts index e8c1d8fa..1016e891 100644 --- a/graphql-server/src/columns/column.model.ts +++ b/graphql-server/src/columns/column.model.ts @@ -1,5 +1,5 @@ import { Field, ObjectType } from "@nestjs/graphql"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; @ObjectType() export class ColConstraint { diff --git a/graphql-server/src/commits/commit.model.ts b/graphql-server/src/commits/commit.model.ts index e60bd57f..368d292e 100644 --- a/graphql-server/src/commits/commit.model.ts +++ b/graphql-server/src/commits/commit.model.ts @@ -1,6 +1,7 @@ import { Field, GraphQLTimestamp, ID, ObjectType } from "@nestjs/graphql"; +import { RawRow } from "../queryFactory/types"; import { convertToUTCDate } from "../utils"; -import { ListOffsetRes, RawRow } from "../utils/commonTypes"; +import { ListOffsetRes } from "../utils/commonTypes"; import * as doltWriter from "./doltWriter.model"; @ObjectType() diff --git a/graphql-server/src/commits/commit.queries.ts b/graphql-server/src/commits/commit.queries.ts deleted file mode 100644 index 32227ac8..00000000 --- a/graphql-server/src/commits/commit.queries.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const doltLogsQuery = `SELECT * FROM DOLT_LOG(?, '--parents') LIMIT ? OFFSET ?`; - -export const twoDotDoltLogsQuery = `SELECT * FROM DOLT_LOG(?, '--parents')`; diff --git a/graphql-server/src/commits/commit.resolver.ts b/graphql-server/src/commits/commit.resolver.ts index 0fbb9fbd..71a05670 100644 --- a/graphql-server/src/commits/commit.resolver.ts +++ b/graphql-server/src/commits/commit.resolver.ts @@ -1,9 +1,9 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; +import { RawRow } from "../queryFactory/types"; import { ROW_LIMIT, getNextOffset } from "../utils"; -import { DBArgsWithOffset, RawRow } from "../utils/commonTypes"; +import { DBArgsWithOffset } from "../utils/commonTypes"; import { Commit, CommitList, fromDoltLogRow } from "./commit.model"; -import { doltLogsQuery, twoDotDoltLogsQuery } from "./commit.queries"; @ArgsType() export class ListCommitsArgs extends DBArgsWithOffset { @@ -23,7 +23,7 @@ export class ListCommitsArgs extends DBArgsWithOffset { @Resolver(_of => Commit) export class CommitResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => CommitList) async commits( @@ -34,16 +34,18 @@ export class CommitResolver { if (err) throw err; const refName = args.refName ?? args.afterCommitId ?? ""; const offset = args.offset ?? 0; - return this.dss.query(async query => { - if (args.twoDot && args.excludingCommitsFromRefName) { - const logs = await query(twoDotDoltLogsQuery, [ - `${args.excludingCommitsFromRefName}..${refName}`, - ]); - return getCommitListRes(logs, args); - } - const logs = await query(doltLogsQuery, [refName, ROW_LIMIT + 1, offset]); + const conn = this.conn.connection(); + + if (args.twoDot && args.excludingCommitsFromRefName) { + const logs = await conn.getTwoDotLogs({ + toRefName: args.excludingCommitsFromRefName, + fromRefName: refName, + databaseName: args.databaseName, + }); return getCommitListRes(logs, args); - }, args.databaseName); + } + const logs = await conn.getLogs({ ...args, refName }, offset); + return getCommitListRes(logs, args); } } diff --git a/graphql-server/src/commits/doltWriter.model.ts b/graphql-server/src/commits/doltWriter.model.ts index bc4d82b5..f5b6a150 100644 --- a/graphql-server/src/commits/doltWriter.model.ts +++ b/graphql-server/src/commits/doltWriter.model.ts @@ -1,5 +1,5 @@ import { Field, ID, ObjectType } from "@nestjs/graphql"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; @ObjectType() export class DoltWriter { diff --git a/graphql-server/src/connections/connection.resolver.ts b/graphql-server/src/connections/connection.resolver.ts new file mode 100644 index 00000000..6f32541e --- /dev/null +++ b/graphql-server/src/connections/connection.resolver.ts @@ -0,0 +1,109 @@ +import { Resolver } from "@nestjs/graphql"; +import * as mysql from "mysql2/promise"; +import { DataSource } from "typeorm"; +import { DoltQueryFactory } from "../queryFactory/dolt"; +import { MySQLQueryFactory } from "../queryFactory/mysql"; +import { QueryFactory } from "../queryFactory/types"; + +export class WorkbenchConfig { + hideDoltFeatures: boolean; + + connectionUrl: string; + + useSSL: boolean; +} + +@Resolver() +export class ConnectionResolver { + private ds: DataSource | undefined; + + private qf: QueryFactory | undefined; + + private workbenchConfig: WorkbenchConfig | undefined; + + connection(): QueryFactory { + if (!this.qf) { + throw new Error("Data source service not initialized"); + } + return this.qf; + } + + // Used for file upload only, must destroy after using + async mysqlConnection(): Promise { + const { workbenchConfig } = this; + if (!workbenchConfig) { + throw new Error("Workbench config not found for MySQL connection"); + } + + const options: mysql.ConnectionOptions = { + uri: workbenchConfig.connectionUrl, + ssl: { + rejectUnauthorized: false, + }, + connectionLimit: 1, + dateStrings: ["DATE"], + + // Allows file upload via LOAD DATA + flags: ["+LOCAL_FILES"], + }; + + return mysql.createConnection(options); + } + + async addConnection(config: WorkbenchConfig): Promise { + if (this.ds?.isInitialized) { + await this.ds.destroy(); + } + + this.workbenchConfig = config; + + this.ds = new DataSource({ + type: "mysql", + connectorPackage: "mysql2", + url: config.connectionUrl, + ssl: config.useSSL + ? { + rejectUnauthorized: false, + } + : undefined, + synchronize: false, + logging: "all", + + extra: { + connectionLimit: 6, + dateStrings: ["DATE"], + namedPlaceholders: true, + }, + }); + + await this.ds.initialize(); + + const qf = await this.newQueryFactory(); + this.qf = qf; + } + + getWorkbenchConfig(): WorkbenchConfig | undefined { + return this.workbenchConfig; + } + + async newQueryFactory(): Promise { + try { + const res = await this.ds?.query("SELECT dolt_version()"); + if (res) { + return new DoltQueryFactory(this.ds); + } + return new MySQLQueryFactory(this.ds); + } catch (_) { + return new MySQLQueryFactory(this.ds); + } + } + + async resetDS(): Promise { + if (!this.workbenchConfig) { + throw new Error( + "Workbench config not found. Please add connectivity information.", + ); + } + await this.addConnection(this.workbenchConfig); + } +} diff --git a/graphql-server/src/dataSources/dataSource.module.ts b/graphql-server/src/dataSources/dataSource.module.ts deleted file mode 100644 index 48f8c425..00000000 --- a/graphql-server/src/dataSources/dataSource.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from "@nestjs/common"; -import { DataSourceService } from "./dataSource.service"; - -@Module({ - providers: [ - { - provide: DataSourceService, - useValue: new DataSourceService(undefined, undefined), - }, - ], - exports: [DataSourceService], -}) -export class DataSourceModule {} diff --git a/graphql-server/src/dataSources/dataSource.service.ts b/graphql-server/src/dataSources/dataSource.service.ts deleted file mode 100644 index 6ac3b585..00000000 --- a/graphql-server/src/dataSources/dataSource.service.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import * as mysql from "mysql2/promise"; -import { DataSource, QueryRunner } from "typeorm"; -import { RawRows } from "../utils/commonTypes"; - -export const dbNotFoundErr = "Database connection not found"; -export type ParQuery = (q: string, p?: any[] | undefined) => Promise; - -class WorkbenchConfig { - hideDoltFeatures: boolean; - - connectionUrl: string; - - useSSL: boolean; - - isDolt?: boolean; -} - -@Injectable() -export class DataSourceService { - constructor( - private ds: DataSource | undefined, - private workbenchConfig: WorkbenchConfig | undefined, - ) {} - - getDS(): DataSource { - const { ds } = this; - if (!ds) throw new Error(dbNotFoundErr); - return ds; - } - - // Used for file upload only - getMySQLConfig(): mysql.ConnectionOptions { - const { workbenchConfig } = this; - if (!workbenchConfig) { - throw new Error("Workbench config not found for MySQL connection"); - } - - return { - uri: workbenchConfig.connectionUrl, - ssl: { - rejectUnauthorized: false, - }, - connectionLimit: 1, - dateStrings: ["DATE"], - - // Allows file upload via LOAD DATA - flags: ["+LOCAL_FILES"], - }; - } - - getQR(): QueryRunner { - return this.getDS().createQueryRunner(); - } - - async handleAsyncQuery( - work: (qr: QueryRunner) => Promise, - ): Promise { - const qr = this.getQR(); - try { - await qr.connect(); - const res = await work(qr); - return res; - } finally { - await qr.release(); - } - } - - // Assumes Dolt database - async query( - executeQuery: (pq: ParQuery) => any, - dbName?: string, - refName?: string, - ): Promise { - return this.handleAsyncQuery(async qr => { - async function query(q: string, p?: any[] | undefined): Promise { - const res = await qr.query(q, p); - return res; - } - - if (dbName) { - await qr.query(useDBStatement(dbName, refName)); - } - - return executeQuery(query); - }); - } - - async getIsDolt(qr: QueryRunner): Promise { - const { workbenchConfig } = this; - if (!workbenchConfig) { - throw new Error("Workbench config not found. Restart database."); - } - - if (workbenchConfig.isDolt !== undefined) { - return workbenchConfig.isDolt; - } - try { - const res = await qr.query("SELECT dolt_version()"); - workbenchConfig.isDolt = !!res; - return !!res; - } catch (_) { - workbenchConfig.isDolt = false; - return false; - } - } - - // Queries that will work on both MySQL and Dolt - async queryMaybeDolt( - executeQuery: (pq: ParQuery, isDolt: boolean) => any, - dbName?: string, - refName?: string, - ): Promise { - return this.handleAsyncQuery(async qr => { - async function query(q: string, p?: any[] | undefined): Promise { - const res = await qr.query(q, p); - return res; - } - - const isDolt = await this.getIsDolt(qr); - if (dbName) { - await qr.query(useDBStatement(dbName, refName, isDolt)); - } - - return executeQuery(query, isDolt); - }); - } - - getWorkbenchConfig(): WorkbenchConfig | undefined { - return this.workbenchConfig; - } - - async addDS(config: WorkbenchConfig) { - if (this.ds?.isInitialized) { - await this.ds.destroy(); - } - - this.workbenchConfig = config; - - this.ds = new DataSource({ - type: "mysql", - connectorPackage: "mysql2", - url: config.connectionUrl, - ssl: config.useSSL - ? { - rejectUnauthorized: false, - } - : undefined, - synchronize: false, - logging: "all", - - extra: { - connectionLimit: 6, - dateStrings: ["DATE"], - namedPlaceholders: true, - }, - }); - - await this.ds.initialize(); - } - - async resetDS() { - if (!this.workbenchConfig) { - throw new Error( - "Workbench config not found. Please add connectivity information.", - ); - } - await this.addDS(this.workbenchConfig); - } -} - -// Cannot use params here for the database revision. It will incorrectly -// escape refs with dots -export function useDBStatement( - dbName?: string, - refName?: string, - isDolt = true, -): string { - if (refName && isDolt) { - return `USE \`${dbName}/${refName}\``; - } - return `USE \`${dbName}\``; -} diff --git a/graphql-server/src/databases/database.resolver.ts b/graphql-server/src/databases/database.resolver.ts index eca8499b..184932dd 100644 --- a/graphql-server/src/databases/database.resolver.ts +++ b/graphql-server/src/databases/database.resolver.ts @@ -7,7 +7,7 @@ import { Query, Resolver, } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { FileStoreService } from "../fileStore/fileStore.service"; import { DBArgs } from "../utils/commonTypes"; import { DatabaseConnection } from "./database.model"; @@ -45,19 +45,14 @@ class RemoveDatabaseConnectionArgs { @Resolver(_of => DatabaseConnection) export class DatabaseResolver { constructor( - private readonly dss: DataSourceService, + private readonly conn: ConnectionResolver, private readonly fileStoreService: FileStoreService, ) {} @Query(_returns => String, { nullable: true }) async currentDatabase(): Promise { - const qr = this.dss.getQR(); - try { - const res = await qr.getCurrentDatabase(); - return res; - } finally { - await qr.release(); - } + const conn = this.conn.connection(); + return conn.currentDatabase(); } @Query(_returns => [DatabaseConnection]) @@ -67,33 +62,27 @@ export class DatabaseResolver { @Query(_returns => [String]) async databases(): Promise { - return this.dss.query(async query => { - const dbs = await query("SHOW DATABASES"); - return dbs - .map(db => db.Database) - .filter( - db => - db !== "information_schema" && - db !== "mysql" && - db !== "dolt_cluster" && - !db.includes("/"), - ); - }); + 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("/"), + ); } @Query(_returns => DoltDatabaseDetails) async doltDatabaseDetails(): Promise { - const workbenchConfig = this.dss.getWorkbenchConfig(); - const qr = this.dss.getQR(); - try { - const isDolt = await this.dss.getIsDolt(qr); - return { - isDolt, - hideDoltFeatures: workbenchConfig?.hideDoltFeatures ?? false, - }; - } finally { - await qr.release(); - } + const workbenchConfig = this.conn.getWorkbenchConfig(); + const conn = this.conn.connection(); + return { + isDolt: conn.isDolt, + hideDoltFeatures: workbenchConfig?.hideDoltFeatures ?? false, + }; } @Mutation(_returns => String, { nullable: true }) @@ -105,7 +94,7 @@ export class DatabaseResolver { hideDoltFeatures: !!args.hideDoltFeatures, useSSL: !!args.useSSL, }; - await this.dss.addDS(workbenchConfig); + await this.conn.addConnection(workbenchConfig); this.fileStoreService.addItemToStore({ ...workbenchConfig, @@ -127,18 +116,14 @@ export class DatabaseResolver { @Mutation(_returns => Boolean) async createDatabase(@Args() args: DBArgs): Promise { - const qr = this.dss.getQR(); - try { - await qr.createDatabase(args.databaseName); - return true; - } finally { - await qr.release(); - } + const conn = this.conn.connection(); + await conn.createDatabase(args); + return true; } @Mutation(_returns => Boolean) async resetDatabase(): Promise { - await this.dss.resetDS(); + await this.conn.resetDS(); return true; } } diff --git a/graphql-server/src/diffStats/diffStat.model.ts b/graphql-server/src/diffStats/diffStat.model.ts index cea0e69f..3a585fc8 100644 --- a/graphql-server/src/diffStats/diffStat.model.ts +++ b/graphql-server/src/diffStats/diffStat.model.ts @@ -1,5 +1,5 @@ import { Field, Float, ObjectType } from "@nestjs/graphql"; -import { RawRow, RawRows } from "../utils/commonTypes"; +import { RawRow, RawRows } from "../queryFactory/types"; @ObjectType() export class DiffStat { diff --git a/graphql-server/src/diffStats/diffStat.queries.ts b/graphql-server/src/diffStats/diffStat.queries.ts deleted file mode 100644 index e00546c6..00000000 --- a/graphql-server/src/diffStats/diffStat.queries.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const getThreeDotDiffStatQuery = (hasTableName?: boolean): string => - `SELECT * FROM DOLT_DIFF_STAT(?${hasTableName ? `, ?` : ""})`; - -export const getDiffStatQuery = (hasTableName?: boolean): string => - `SELECT * FROM DOLT_DIFF_STAT(?, ?${hasTableName ? `, ?` : ""})`; diff --git a/graphql-server/src/diffStats/diffStat.resolver.ts b/graphql-server/src/diffStats/diffStat.resolver.ts index 48bc524f..4be37e2e 100644 --- a/graphql-server/src/diffStats/diffStat.resolver.ts +++ b/graphql-server/src/diffStats/diffStat.resolver.ts @@ -1,9 +1,8 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { CommitDiffType } from "../diffSummaries/diffSummary.enums"; import { DBArgs } from "../utils/commonTypes"; import { DiffStat, fromDoltDiffStat } from "./diffStat.model"; -import { getDiffStatQuery, getThreeDotDiffStatQuery } from "./diffStat.queries"; @ArgsType() export class DiffStatArgs extends DBArgs { @@ -25,33 +24,21 @@ export class DiffStatArgs extends DBArgs { @Resolver(_of => DiffStat) export class DiffStatResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => DiffStat) async diffStat(@Args() args: DiffStatArgs): Promise { + const conn = this.conn.connection(); const type = args.type ?? CommitDiffType.TwoDot; checkArgs(args); - return this.dss.query( - async query => { - if (type === CommitDiffType.ThreeDot) { - const res = await query(getThreeDotDiffStatQuery(!!args.tableName), [ - `${args.toRefName}...${args.fromRefName}`, - args.tableName, - ]); - return fromDoltDiffStat(res); - } + if (type === CommitDiffType.ThreeDot) { + const res = await conn.getThreeDotDiffStat(args); + return fromDoltDiffStat(res); + } - const res = await query(getDiffStatQuery(!!args.tableName), [ - args.fromRefName, - args.toRefName, - args.tableName, - ]); - return fromDoltDiffStat(res); - }, - args.databaseName, - args.refName, - ); + const res = await conn.getDiffStat(args); + return fromDoltDiffStat(res); } } diff --git a/graphql-server/src/diffSummaries/diffSummary.model.ts b/graphql-server/src/diffSummaries/diffSummary.model.ts index 5c15de49..bacfe40c 100644 --- a/graphql-server/src/diffSummaries/diffSummary.model.ts +++ b/graphql-server/src/diffSummaries/diffSummary.model.ts @@ -1,5 +1,5 @@ import { Field, ID, ObjectType } from "@nestjs/graphql"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; import { TableDiffType, toTableDiffType } from "./diffSummary.enums"; @ObjectType() diff --git a/graphql-server/src/diffSummaries/diffSummary.queries.ts b/graphql-server/src/diffSummaries/diffSummary.queries.ts deleted file mode 100644 index 5fdda674..00000000 --- a/graphql-server/src/diffSummaries/diffSummary.queries.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const getDiffSummaryQuery = (hasTableName?: boolean): string => - `SELECT * FROM DOLT_DIFF_SUMMARY(?, ?${hasTableName ? `, ?` : ""})`; - -export const getThreeDotDiffSummaryQuery = (hasTableName?: boolean): string => - `SELECT * FROM DOLT_DIFF_SUMMARY(?${hasTableName ? `, ?` : ""})`; diff --git a/graphql-server/src/diffSummaries/diffSummary.resolver.ts b/graphql-server/src/diffSummaries/diffSummary.resolver.ts index c8a21011..1142588b 100644 --- a/graphql-server/src/diffSummaries/diffSummary.resolver.ts +++ b/graphql-server/src/diffSummaries/diffSummary.resolver.ts @@ -1,13 +1,10 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService, ParQuery } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { checkArgs } from "../diffStats/diffStat.resolver"; +import { QueryFactory } from "../queryFactory/types"; import { DBArgs } from "../utils/commonTypes"; import { CommitDiffType } from "./diffSummary.enums"; import { DiffSummary, fromDoltDiffSummary } from "./diffSummary.model"; -import { - getDiffSummaryQuery, - getThreeDotDiffSummaryQuery, -} from "./diffSummary.queries"; @ArgsType() class DiffSummaryArgs extends DBArgs { @@ -29,38 +26,28 @@ class DiffSummaryArgs extends DBArgs { @Resolver(_of => DiffSummary) export class DiffSummaryResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => [DiffSummary]) async diffSummaries(@Args() args: DiffSummaryArgs): Promise { - return this.dss.query( - async q => getDiffSummaries(q, args), - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + return getDiffSummaries(conn, args); } } export async function getDiffSummaries( - query: ParQuery, + conn: QueryFactory, args: DiffSummaryArgs, ): Promise { const type = args.type ?? CommitDiffType.TwoDot; checkArgs(args); if (type === CommitDiffType.ThreeDot) { - const res = await query(getThreeDotDiffSummaryQuery(!!args.tableName), [ - `${args.toRefName}...${args.fromRefName}`, - args.tableName, - ]); + const res = await conn.getThreeDotDiffSummary(args); return res.map(fromDoltDiffSummary).sort(sortByTableName); } - const res = await query(getDiffSummaryQuery(!!args.tableName), [ - args.fromRefName, - args.toRefName, - args.tableName, - ]); + const res = await conn.getDiffSummary(args); return res.map(fromDoltDiffSummary).sort(sortByTableName); } diff --git a/graphql-server/src/docs/doc.model.ts b/graphql-server/src/docs/doc.model.ts index 3793d261..bfdc9962 100644 --- a/graphql-server/src/docs/doc.model.ts +++ b/graphql-server/src/docs/doc.model.ts @@ -1,6 +1,6 @@ import { Field, ObjectType } from "@nestjs/graphql"; +import { RawRow } from "../queryFactory/types"; import { Row } from "../rows/row.model"; -import { RawRow } from "../utils/commonTypes"; import { DocType } from "./doc.enum"; @ObjectType() diff --git a/graphql-server/src/docs/doc.resolver.ts b/graphql-server/src/docs/doc.resolver.ts index 936d95bf..0d34ea69 100644 --- a/graphql-server/src/docs/doc.resolver.ts +++ b/graphql-server/src/docs/doc.resolver.ts @@ -1,9 +1,8 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { RefArgs } from "../utils/commonTypes"; import { DocType } from "./doc.enum"; import { Doc, DocList, fromDoltDocsRow } from "./doc.model"; -import { docsQuery } from "./docs.queries"; @ArgsType() class GetDefaultDocArgs extends RefArgs { @@ -13,52 +12,42 @@ class GetDefaultDocArgs extends RefArgs { @Resolver(_of => Doc) export class DocsResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => DocList) async docs( @Args() args: RefArgs, ): Promise { - return this.dss.query( - async query => { - const docRows = await query(docsQuery); - if (!docRows.length) return { list: [] }; - const sortedDocs = docRows.sort(d => - d.doc_name === DocType.Readme ? -1 : 1, - ); - return { - list: sortedDocs.map(d => fromDoltDocsRow(args.refName, d)), - }; - }, - args.databaseName, - args.refName, + const conn = this.conn.connection(); + const docRows = await conn.getDocs(args); + if (!docRows?.length) return { list: [] }; + const sortedDocs = docRows.sort(d => + d.doc_name === DocType.Readme ? -1 : 1, ); + return { + list: sortedDocs.map(d => fromDoltDocsRow(args.refName, d)), + }; } @Query(_returns => Doc, { nullable: true }) async docOrDefaultDoc( @Args() args: GetDefaultDocArgs, ): Promise { - return this.dss.query( - async query => { - const docRows = await query(docsQuery); - if (!docRows.length) return { list: [] }; + const conn = this.conn.connection(); + const docRows = await conn.getDocs(args); + if (!docRows?.length) return undefined; - if (args.docType) { - const doc = docRows.find(d => d.doc_name === args.docType); - if (doc) { - return fromDoltDocsRow(args.refName, doc); - } - } + if (args.docType) { + const doc = docRows.find(d => d.doc_name === args.docType); + if (doc) { + return fromDoltDocsRow(args.refName, doc); + } + } - const sortedDocs = docRows.sort(d => - d.doc_name === DocType.Readme ? -1 : 1, - ); - return fromDoltDocsRow(args.refName, sortedDocs[0]); - }, - args.databaseName, - args.refName, + const sortedDocs = docRows.sort(d => + d.doc_name === DocType.Readme ? -1 : 1, ); + return fromDoltDocsRow(args.refName, sortedDocs[0]); } } diff --git a/graphql-server/src/docs/docs.queries.ts b/graphql-server/src/docs/docs.queries.ts deleted file mode 100644 index 13808ab5..00000000 --- a/graphql-server/src/docs/docs.queries.ts +++ /dev/null @@ -1 +0,0 @@ -export const docsQuery = `SELECT * FROM dolt_docs`; diff --git a/graphql-server/src/indexes/foreignKey.model.ts b/graphql-server/src/indexes/foreignKey.model.ts index febd1c31..c13b2236 100644 --- a/graphql-server/src/indexes/foreignKey.model.ts +++ b/graphql-server/src/indexes/foreignKey.model.ts @@ -1,5 +1,5 @@ import { Field, ObjectType } from "@nestjs/graphql"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; @ObjectType() export class ForeignKeyColumn { diff --git a/graphql-server/src/indexes/index.model.ts b/graphql-server/src/indexes/index.model.ts index 587b7bc7..0ca15ed1 100644 --- a/graphql-server/src/indexes/index.model.ts +++ b/graphql-server/src/indexes/index.model.ts @@ -1,5 +1,5 @@ import { Field, ObjectType } from "@nestjs/graphql"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; @ObjectType() export class IndexColumn { diff --git a/graphql-server/src/pulls/pull.queries.ts b/graphql-server/src/pulls/pull.queries.ts deleted file mode 100644 index 237d3952..00000000 --- a/graphql-server/src/pulls/pull.queries.ts +++ /dev/null @@ -1 +0,0 @@ -export const callMerge = `CALL DOLT_MERGE(?, "--no-ff", "-m", ?)`; diff --git a/graphql-server/src/pulls/pull.resolver.ts b/graphql-server/src/pulls/pull.resolver.ts index ea73c078..86d63f12 100644 --- a/graphql-server/src/pulls/pull.resolver.ts +++ b/graphql-server/src/pulls/pull.resolver.ts @@ -7,10 +7,9 @@ import { Resolver, } from "@nestjs/graphql"; import { CommitResolver } from "../commits/commit.resolver"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { DBArgs } from "../utils/commonTypes"; import { PullWithDetails, fromAPIModelPullWithDetails } from "./pull.model"; -import { callMerge } from "./pull.queries"; @ArgsType() class PullArgs extends DBArgs { @@ -24,7 +23,7 @@ class PullArgs extends DBArgs { @Resolver(_of => PullWithDetails) export class PullResolver { constructor( - private readonly dss: DataSourceService, + private readonly conn: ConnectionResolver, private readonly commitResolver: CommitResolver, ) {} @@ -43,29 +42,12 @@ export class PullResolver { @Mutation(_returns => Boolean) async mergePull(@Args() args: PullArgs): Promise { - return this.dss.query( - async query => { - await query("BEGIN"); - - const res = await query(callMerge, [ - args.fromBranchName, - `Merge branch ${args.fromBranchName}`, - // commitAuthor: { - // name: currentUser.username, - // email: currentUser.emailAddressesList[0].address, - // }, - ]); - - if (res.length && res[0].conflicts !== "0") { - await query("ROLLBACK"); - throw new Error("Merge conflict detected"); - } - - await query("COMMIT"); - return true; - }, - args.databaseName, - args.toBranchName, - ); + const conn = this.conn.connection(); + await conn.callMerge({ + databaseName: args.databaseName, + fromBranchName: args.fromBranchName, + toBranchName: args.toBranchName, + }); + return true; } } diff --git a/graphql-server/src/queryFactory/base.ts b/graphql-server/src/queryFactory/base.ts new file mode 100644 index 00000000..6bccdc13 --- /dev/null +++ b/graphql-server/src/queryFactory/base.ts @@ -0,0 +1,43 @@ +import { DataSource, QueryRunner } from "typeorm"; +import * as t from "./types"; + +export const dbNotFoundErr = "Database connection not found"; + +export class BaseQueryFactory { + ds: DataSource | undefined; + + constructor(ds: DataSource | undefined) { + this.ds = ds; + } + + getDS(): DataSource { + const { ds } = this; + if (!ds) throw new Error(dbNotFoundErr); + return ds; + } + + getQR(): QueryRunner { + return this.getDS().createQueryRunner(); + } + + async handleAsyncQuery(work: (qr: QueryRunner) => Promise): Promise { + const qr = this.getQR(); + try { + await qr.connect(); + const res = await work(qr); + return res; + } finally { + await qr.release(); + } + } + + async currentDatabase(): Promise { + return this.handleAsyncQuery(async qr => qr.getCurrentDatabase()); + } + + async createDatabase(args: t.DBArgs): Promise { + return this.handleAsyncQuery(async qr => + qr.createDatabase(args.databaseName), + ); + } +} diff --git a/graphql-server/src/queryFactory/dolt/index.ts b/graphql-server/src/queryFactory/dolt/index.ts new file mode 100644 index 00000000..bc72e312 --- /dev/null +++ b/graphql-server/src/queryFactory/dolt/index.ts @@ -0,0 +1,312 @@ +import { SortBranchesBy } from "../../branches/branch.enum"; +import { CommitDiffType } from "../../diffSummaries/diffSummary.enums"; +import { convertToStringForQuery } from "../../rowDiffs/rowDiff.enums"; +import { SchemaType } from "../../schemas/schema.enums"; +import { systemTableValues } from "../../systemTables/systemTable.enums"; +import { ROW_LIMIT, handleTableNotFound } from "../../utils"; +import { MySQLQueryFactory } from "../mysql"; +import * as myqh from "../mysql/queries"; +import * as t from "../types"; +import * as qh from "./queries"; +import { handleRefNotFound, unionCols } from "./utils"; + +export class DoltQueryFactory + extends MySQLQueryFactory + implements t.QueryFactory +{ + isDolt = true; + + async getTableNames(args: t.RefArgs, filterSystemTables?: boolean): t.PR { + return this.queryMultiple( + async query => { + const tables = await query(myqh.listTablesQuery, []); + if (filterSystemTables) return tables; + + const systemTables: Array = await Promise.all( + systemTableValues.map(async st => { + const cols = await handleTableNotFound(async () => + query(myqh.columnsQuery, [st]), + ); + if (cols) { + return { [`Tables_in_${args.databaseName}`]: `${st}` }; + } + return undefined; + }), + ); + return [...tables, ...(systemTables.filter(st => !!st) as t.RawRows)]; + }, + args.databaseName, + args.refName, + ); + } + + async getSchemas(args: t.RefArgs, type?: SchemaType): t.UPR { + const q = qh.getDoltSchemasQuery(!!type); + const p = type ? [type] : []; + return handleTableNotFound(async () => + this.query(q, p, args.databaseName, args.refName), + ); + } + + async getProcedures(args: t.RefArgs): t.UPR { + return handleTableNotFound(async () => + this.query(qh.doltProceduresQuery, [], args.databaseName, args.refName), + ); + } + + async getBranch(args: t.BranchArgs): t.PR { + return this.query(qh.branchQuery, [args.branchName], args.databaseName); + } + + async getBranches(args: t.DBArgs & { sortBy?: SortBranchesBy }): t.PR { + return this.query(qh.getBranchesQuery(args.sortBy), [], args.databaseName); + } + + async createNewBranch(args: t.BranchArgs & { fromRefName: string }): t.PR { + return this.query( + qh.callNewBranch, + [args.branchName, args.fromRefName], + args.databaseName, + ); + } + + async callDeleteBranch(args: t.BranchArgs): t.PR { + return this.query( + qh.callDeleteBranch, + [args.branchName], + args.databaseName, + ); + } + + async getLogs(args: t.RefArgs, offset: number): t.PR { + return handleRefNotFound(async () => + this.query( + qh.doltLogsQuery, + [args.refName, ROW_LIMIT + 1, offset], + args.databaseName, + ), + ); + } + + async getTwoDotLogs(args: t.RefsArgs): t.PR { + return handleRefNotFound(async () => + this.query( + qh.twoDotDoltLogsQuery, + [`${args.toRefName}..${args.fromRefName}`], + args.databaseName, + ), + ); + } + + async getDiffStat(args: t.RefsArgs & { tableName?: string }): t.PR { + return this.query( + qh.getDiffStatQuery(!!args.tableName), + [args.fromRefName, args.toRefName, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getThreeDotDiffStat(args: t.RefsArgs & { tableName?: string }): t.PR { + return this.query( + qh.getThreeDotDiffStatQuery(!!args.tableName), + [`${args.toRefName}...${args.fromRefName}`, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getDiffSummary(args: t.RefsArgs & { tableName?: string }): t.PR { + return this.query( + qh.getDiffSummaryQuery(!!args.tableName), + [args.fromRefName, args.toRefName, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getThreeDotDiffSummary( + args: t.RefsArgs & { tableName?: string }, + ): t.PR { + return this.query( + qh.getThreeDotDiffSummaryQuery(!!args.tableName), + [`${args.toRefName}...${args.fromRefName}`, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getSchemaPatch(args: t.RefsArgs & { tableName: string }): t.PR { + return this.query( + qh.schemaPatchQuery, + [args.fromRefName, args.toRefName, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getThreeDotSchemaPatch(args: t.RefsArgs & { tableName: string }): t.PR { + return this.query( + qh.threeDotSchemaPatchQuery, + [`${args.toRefName}...${args.fromRefName}`, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getSchemaDiff(args: t.RefsArgs & { tableName: string }): t.PR { + return this.query( + qh.schemaDiffQuery, + [args.fromRefName, args.toRefName, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getThreeDotSchemaDiff(args: t.RefsArgs & { tableName: string }): t.PR { + return this.query( + qh.threeDotSchemaDiffQuery, + [`${args.toRefName}...${args.fromRefName}`, args.tableName], + args.databaseName, + args.refName, + ); + } + + async getDocs(args: t.RefArgs): t.UPR { + return handleTableNotFound(async () => + this.query(qh.docsQuery, [], args.databaseName, args.refName), + ); + } + + async getStatus(args: t.RefArgs): t.PR { + return this.query(qh.statusQuery, [], args.databaseName, args.refName); + } + + async getTag(args: t.TagArgs): t.PR { + return this.query(qh.tagQuery, [args.tagName], args.databaseName); + } + + async getTags(args: t.DBArgs): t.PR { + return this.query(qh.tagsQuery, [], args.databaseName); + } + + async createNewTag( + args: t.TagArgs & { + fromRefName: string; + message?: string; + }, + ): t.PR { + return this.query( + qh.getCallNewTag(!!args.message), + [args.tagName, args.fromRefName, args.message], + args.databaseName, + ); + } + + async callDeleteTag(args: t.TagArgs): t.PR { + return this.query(qh.callDeleteTag, [args.tagName], args.databaseName); + } + + async callMerge(args: t.BranchesArgs): Promise { + return this.queryMultiple( + async query => { + await query("BEGIN"); + + const res = await query(qh.callMerge, [ + args.fromBranchName, + `Merge branch ${args.fromBranchName}`, + // TODO: add commit author + // commitAuthor: { + // name: currentUser.username, + // email: currentUser.emailAddressesList[0].address, + // }, + ]); + + if (res.length && res[0].conflicts !== "0") { + await query("ROLLBACK"); + throw new Error("Merge conflict detected"); + } + + await query("COMMIT"); + return true; + }, + args.databaseName, + args.toBranchName, + ); + } + + async resolveRefs( + args: t.RefsArgs & { type?: CommitDiffType }, + ): t.CommitsRes { + if (args.type !== CommitDiffType.ThreeDot) { + return { fromCommitId: args.fromRefName, toCommitId: args.toRefName }; + } + return this.queryMultiple( + async query => { + const toCommitId = await query(qh.hashOf, [args.fromRefName]); + const mergeBaseCommit = await query(qh.mergeBase, [ + args.toRefName, + args.fromRefName, + ]); + return { + fromCommitId: Object.values(mergeBaseCommit[0])[0], + toCommitId: Object.values(toCommitId[0])[0], + }; + }, + args.databaseName, + args.refName, + ); + } + + async getOneSidedRowDiff( + args: t.TableArgs & { offset: number }, + ): Promise<{ rows: t.RawRows; columns: t.RawRows }> { + return this.queryMultiple( + async query => { + const columns = await query(qh.tableColsQueryAsOf, [ + args.tableName, + args.refName, + ]); + const { q, cols } = qh.getRowsQueryAsOf(columns); + const rows = await query(q, [ + args.tableName, + args.refName, + ...cols, + ROW_LIMIT + 1, + args.offset, + ]); + return { rows, columns }; + }, + args.databaseName, + args.refName, + ); + } + + async getRowDiffs(args: t.RowDiffArgs): t.DiffRes { + return this.queryMultiple( + async query => { + const oldCols = await query(qh.tableColsQueryAsOf, [ + args.fromTableName, + args.fromCommitId, + ]); + const newCols = await query(qh.tableColsQueryAsOf, [ + args.toTableName, + args.toCommitId, + ]); + const colsUnion = unionCols(oldCols, newCols); + const diffType = convertToStringForQuery(args.filterByRowType); + const refArgs = [args.fromCommitId, args.toCommitId, args.toTableName]; + const pageArgs = [ROW_LIMIT + 1, args.offset]; + const diff = await query( + qh.getTableCommitDiffQuery(colsUnion, !!diffType), + diffType + ? [...refArgs, diffType, ...pageArgs] + : [...refArgs, ...pageArgs], + ); + return { colsUnion, diff }; + }, + args.databaseName, + args.refName, + ); + } +} diff --git a/graphql-server/src/queryFactory/dolt/queries.ts b/graphql-server/src/queryFactory/dolt/queries.ts new file mode 100644 index 00000000..0d44b2b8 --- /dev/null +++ b/graphql-server/src/queryFactory/dolt/queries.ts @@ -0,0 +1,128 @@ +import { SortBranchesBy } from "../../branches/branch.enum"; +import { DoltSystemTable } from "../../systemTables/systemTable.enums"; +import { getOrderByFromCols, getPKColsForRowsQuery } from "../mysql/queries"; +import { RawRows } from "../types"; + +export const getDoltSchemasQuery = (hasWhereCause = false): string => + `SELECT * FROM ${DoltSystemTable.SCHEMAS}${ + hasWhereCause ? " WHERE type = ?" : "" + }`; + +export const doltProceduresQuery = `SELECT * FROM ${DoltSystemTable.PROCEDURES}`; + +// BRANCHES + +export const branchQuery = `SELECT * FROM dolt_branches WHERE name=?`; + +export const getBranchesQuery = (sortBy?: SortBranchesBy) => + `SELECT * FROM dolt_branches ${getOrderByForBranches(sortBy)}`; + +export const callNewBranch = `CALL DOLT_BRANCH(?, ?)`; + +export const callDeleteBranch = `CALL DOLT_BRANCH("-D", ?)`; + +function getOrderByForBranches(sortBy?: SortBranchesBy): string { + switch (sortBy) { + case SortBranchesBy.LastUpdated: + return "ORDER BY latest_commit_date DESC "; + default: + return ""; + } +} + +// COMMITS + +export const doltLogsQuery = `SELECT * FROM DOLT_LOG(?, '--parents') LIMIT ? OFFSET ?`; + +export const twoDotDoltLogsQuery = `SELECT * FROM DOLT_LOG(?, '--parents')`; + +// DIFFS + +export const hashOf = `SELECT HASHOF(?)`; +export const mergeBase = `SELECT DOLT_MERGE_BASE(?, ?)`; + +export const getThreeDotDiffStatQuery = (hasTableName?: boolean): string => + `SELECT * FROM DOLT_DIFF_STAT(?${hasTableName ? `, ?` : ""})`; + +export const getDiffStatQuery = (hasTableName?: boolean): string => + `SELECT * FROM DOLT_DIFF_STAT(?, ?${hasTableName ? `, ?` : ""})`; + +export const getDiffSummaryQuery = (hasTableName?: boolean): string => + `SELECT * FROM DOLT_DIFF_SUMMARY(?, ?${hasTableName ? `, ?` : ""})`; + +export const getThreeDotDiffSummaryQuery = (hasTableName?: boolean): string => + `SELECT * FROM DOLT_DIFF_SUMMARY(?${hasTableName ? `, ?` : ""})`; + +export const schemaPatchQuery = `SELECT * FROM DOLT_PATCH(?, ?, ?) WHERE diff_type="schema"`; + +export const threeDotSchemaPatchQuery = `SELECT * FROM DOLT_PATCH(?, ?) WHERE diff_type="schema"`; + +export const schemaDiffQuery = `SELECT * FROM DOLT_SCHEMA_DIFF(?, ?, ?)`; + +export const threeDotSchemaDiffQuery = `SELECT * FROM DOLT_SCHEMA_DIFF(?, ?)`; + +// DOCS + +export const docsQuery = `SELECT * FROM dolt_docs`; + +// PULLS + +export const callMerge = `CALL DOLT_MERGE(?, "--no-ff", "-m", ?)`; + +// STATUS + +export const statusQuery = `SELECT * FROM dolt_status`; + +// TAGS + +export const tagsQuery = `SELECT * FROM dolt_tags ORDER BY date DESC`; + +export const tagQuery = `SELECT * FROM dolt_tags WHERE tag_name=?`; + +export const callDeleteTag = `CALL DOLT_TAG("-d", ?)`; + +export const getCallNewTag = (hasMessage = false, hasAuthor = false) => + `CALL DOLT_TAG(?, ?${hasMessage ? `, "-m", ?` : ""}${getAuthorNameString( + hasAuthor, + )})`; + +export function getAuthorNameString(hasAuthor: boolean): string { + if (!hasAuthor) return ""; + return `, "--author", ?`; +} + +export const getRowsQueryAsOf = ( + columns: RawRows, +): { q: string; cols: string[] } => { + const cols = getPKColsForRowsQuery(columns); + return { + q: `SELECT * FROM ?? AS OF ? ${getOrderByFromCols( + cols.length, + )}LIMIT ? OFFSET ?`, + cols, + }; +}; + +export const tableColsQueryAsOf = `DESCRIBE ?? AS OF ?`; + +export function getTableCommitDiffQuery( + cols: RawRows, + hasFilter = false, +): string { + const whereDiffType = hasFilter ? ` WHERE diff_type=? ` : ""; + return `SELECT * FROM DOLT_DIFF(?, ?, ?)${whereDiffType} + ${getOrderByFromDiffCols(cols)} + LIMIT ? + OFFSET ?`; +} + +export function getOrderByFromDiffCols(cols: RawRows): string { + const pkCols = cols.filter(col => col.Key === "PRI"); + const diffCols: string[] = []; + pkCols.forEach(col => { + diffCols.push(`to_${col.Field}`); + diffCols.push(`from_${col.Field}`); + }); + const orderBy = diffCols.map(c => `\`${c}\` ASC`).join(", "); + return orderBy === "" ? "" : `ORDER BY ${orderBy} `; +} diff --git a/graphql-server/src/queryFactory/dolt/utils.ts b/graphql-server/src/queryFactory/dolt/utils.ts new file mode 100644 index 00000000..029cc201 --- /dev/null +++ b/graphql-server/src/queryFactory/dolt/utils.ts @@ -0,0 +1,26 @@ +import { RawRows } from "../types"; + +export async function handleRefNotFound(q: () => Promise): Promise { + try { + const res = await q(); + return res; + } catch (err) { + if (err.message.includes("invalid ref spec")) { + throw new Error("no such ref in database"); + } + throw err; + } +} + +export function unionCols(a: RawRows, b: RawRows): RawRows { + const mergedArray = [...a, ...b]; + const set = new Set(); + const unionArray = mergedArray.filter(item => { + if (!set.has(item.Field)) { + set.add(item.Field); + return true; + } + return false; + }, set); + return unionArray; +} diff --git a/graphql-server/src/queryFactory/mysql/index.ts b/graphql-server/src/queryFactory/mysql/index.ts new file mode 100644 index 00000000..4897e377 --- /dev/null +++ b/graphql-server/src/queryFactory/mysql/index.ts @@ -0,0 +1,270 @@ +/* eslint-disable class-methods-use-this */ + +import { SchemaType } from "../../schemas/schema.enums"; +import { ROW_LIMIT } from "../../utils"; +import { BaseQueryFactory } from "../base"; +import * as t from "../types"; +import * as qh from "./queries"; +import { notDoltError } from "./utils"; + +export class MySQLQueryFactory + extends BaseQueryFactory + implements t.QueryFactory +{ + isDolt = false; + + async query( + q: string, + p: t.Params, + dbName?: string, + refName?: string, + ): Promise { + return this.handleAsyncQuery(async qr => { + if (dbName) { + await qr.query(qh.useDB(dbName, refName, this.isDolt)); + } + + const res = await qr.query(q, p); + return res; + }); + } + + async queryMultiple( + executeQuery: (pq: t.ParQuery) => Promise, + dbName?: string, + refName?: string, + ): Promise { + return this.handleAsyncQuery(async qr => { + async function query(q: string, p?: t.Params): t.PR { + const res = await qr.query(q, p); + return res; + } + + if (dbName) { + await qr.query(qh.useDB(dbName, refName, this.isDolt)); + } + + return executeQuery(query); + }); + } + + async databases(): t.PR { + return this.query(qh.databasesQuery, []); + } + + async getTableNames(args: t.RefArgs): t.PR { + return this.query(qh.listTablesQuery, [], args.databaseName); + } + + async getTableInfo(args: t.TableArgs): t.SPR { + return this.queryMultiple( + async query => getTableInfoWithQR(query, args, this.isDolt), + args.databaseName, + args.refName, + ); + } + + async getTables(args: t.RefArgs, tns: string[]): t.PR { + return this.queryMultiple(async query => { + const tableInfos = await Promise.all( + tns.map(async name => { + const row = await getTableInfoWithQR( + query, + { + ...args, + tableName: name, + }, + this.isDolt, + ); + return row; + }), + ); + return tableInfos; + }, args.databaseName); + } + + async getTableColumns(args: t.TableArgs): t.PR { + return this.query( + qh.tableColsQuery, + [args.tableName], + args.databaseName, + args.refName, + ); + } + + async getTableRows(args: t.TableArgs, page: t.TableRowPagination): t.PR { + const { q, cols } = qh.getRowsQuery(page.columns); + return this.query( + q, + [args.tableName, ...cols, ROW_LIMIT + 1, page.offset], + args.databaseName, + args.refName, + ); + } + + async getSqlSelect(args: t.RefArgs & { queryString: string }): t.PR { + return this.query(args.queryString, [], args.databaseName, args.refName); + } + + async getSchemas(args: t.DBArgs, type?: SchemaType): t.UPR { + 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); + const triggers = tRes.map(tr => { + return { name: tr.Trigger, type: SchemaType.Trigger }; + }); + + const eRes = await query(qh.getEventsQuery); + const events = eRes.map(e => { + return { name: e.Name, type: SchemaType.Event }; + }); + + return [...views, ...triggers, ...events]; + }, args.databaseName); + } + + async getProcedures(args: t.DBArgs): t.UPR { + return this.query( + qh.proceduresQuery, + [args.databaseName], + args.databaseName, + ); + } + + // DOLT QUERIES NOT IMPLEMENTED FOR MYSQL + + // Returns static branch + async getBranch(args: t.BranchArgs): t.PR { + return [ + { + name: args.branchName, + latest_commit_date: new Date(), + latest_committer: "", + head: "", + }, + ]; + } + + async getBranches(args: t.DBArgs): t.PR { + return this.getBranch({ ...args, branchName: "main" }); + } + + async createNewBranch(_: t.BranchArgs & { fromRefName: string }): t.PR { + throw notDoltError("create new branch"); + } + + async callDeleteBranch(_: t.BranchArgs): t.PR { + throw notDoltError("delete branch"); + } + + async getLogs(_args: t.RefArgs, _offset: number): t.PR { + throw notDoltError("get logs"); + } + + async getTwoDotLogs(_args: t.RefsArgs): t.PR { + throw notDoltError("get two dot logs"); + } + + async getDiffStat(_args: t.RefsArgs): t.PR { + throw notDoltError("get diff stat"); + } + + async getThreeDotDiffStat(_args: t.RefsArgs): t.PR { + throw notDoltError("get three dot diff stat"); + } + + async getDiffSummary(_args: t.RefsArgs): t.PR { + throw notDoltError("get diff summary"); + } + + async getThreeDotDiffSummary(_args: t.RefsArgs): t.PR { + throw notDoltError("get three dot diff summary"); + } + + async getSchemaPatch(_args: t.RefsArgs & { tableName: string }): t.PR { + throw notDoltError("get schema patch"); + } + + async getThreeDotSchemaPatch( + _args: t.RefsArgs & { tableName: string }, + ): t.PR { + throw notDoltError("get three dot schema patch"); + } + + async getSchemaDiff(_args: t.RefsArgs & { tableName: string }): t.PR { + throw notDoltError("get schema diff"); + } + + async getThreeDotSchemaDiff(_args: t.RefsArgs & { tableName: string }): t.PR { + throw notDoltError("get three dot schema diff"); + } + + async getDocs(_args: t.RefArgs): t.UPR { + throw notDoltError("get docs"); + } + + async getStatus(_args: t.RefArgs): t.PR { + throw notDoltError("get status"); + } + + async getTag(_args: t.TagArgs): t.PR { + throw notDoltError("get tag"); + } + + async getTags(_args: t.DBArgs): t.PR { + throw notDoltError("get tags"); + } + + async createNewTag( + _args: t.TagArgs & { + fromRefName: string; + }, + ): t.PR { + throw notDoltError("create new tag"); + } + + async callDeleteTag(_args: t.TagArgs): t.PR { + throw notDoltError("delete tag"); + } + + async callMerge(_args: t.BranchesArgs): Promise { + throw notDoltError("merge branches"); + } + + async resolveRefs( + args: t.RefsArgs, + ): Promise<{ fromCommitId: string; toCommitId: string }> { + return { fromCommitId: args.fromRefName, toCommitId: args.toRefName }; + } + + async getOneSidedRowDiff( + _args: t.TableArgs & { offset: number }, + ): Promise<{ rows: t.RawRows; columns: t.RawRows }> { + throw notDoltError("get one-sided diff"); + } + + async getRowDiffs(_args: t.RowDiffArgs): t.DiffRes { + throw notDoltError("get row sided diffs"); + } +} + +async function getTableInfoWithQR( + query: t.ParQuery, + args: t.TableArgs, + isDolt: boolean, +): t.SPR { + const columns = await query(qh.columnsQuery, [args.tableName]); + const fkRows = await query(qh.foreignKeysQuery, [ + args.tableName, + isDolt ? `${args.databaseName}/${args.refName}` : args.databaseName, + ]); + const idxRows = await query(qh.indexQuery, [args.tableName]); + return { name: args.tableName, columns, fkRows, idxRows }; +} diff --git a/graphql-server/src/queryFactory/mysql/queries.ts b/graphql-server/src/queryFactory/mysql/queries.ts new file mode 100644 index 00000000..09568f80 --- /dev/null +++ b/graphql-server/src/queryFactory/mysql/queries.ts @@ -0,0 +1,66 @@ +import { RawRows } from "../types"; + +// Cannot use params here for the database revision. It will incorrectly +// escape refs with dots +export function useDB(dbName: string, refName?: string, isDolt = true): string { + if (refName && isDolt) { + return `USE \`${dbName}/${refName}\``; + } + return `USE \`${dbName}\``; +} + +export const databasesQuery = `SHOW DATABASES`; + +export const columnsQuery = `DESCRIBE ??`; + +export const foreignKeysQuery = `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_name=? AND table_schema=? AND referenced_table_schema IS NOT NULL`; + +export const indexQuery = `SELECT + table_name, + index_name, + GROUP_CONCAT(comment) as COMMENTS, + GROUP_CONCAT(non_unique) AS NON_UNIQUES, + GROUP_CONCAT(column_name ORDER BY seq_in_index) AS COLUMNS +FROM information_schema.statistics +WHERE table_name=? AND index_name!="PRIMARY" +GROUP BY index_name;`; + +export const listTablesQuery = `SHOW FULL TABLES WHERE table_type = 'BASE TABLE'`; + +export const tableColsQuery = `SHOW FULL TABLES WHERE table_type = 'BASE TABLE'`; + +export const getRowsQuery = ( + columns: RawRows, +): { q: string; cols: string[] } => { + const cols = getPKColsForRowsQuery(columns); + return { + q: `SELECT * FROM ?? ${getOrderByFromCols(cols.length)}LIMIT ? OFFSET ?`, + cols, + }; +}; + +export function getPKColsForRowsQuery(cs: RawRows): string[] { + const pkCols = cs.filter(col => col.Key === "PRI"); + const cols = pkCols.map(c => c.Field); + return cols; +} + +// Creates ORDER BY statement with column parameters +// i.e. ORDER BY ::col1, ::col2 +export function getOrderByFromCols(numCols: number): string { + if (!numCols) return ""; + const pkCols = Array.from({ length: numCols }) + .map(() => `? ASC`) + .join(", "); + return pkCols === "" ? "" : `ORDER BY ${pkCols} `; +} + +export const getViewsQuery = `SELECT TABLE_SCHEMA, TABLE_NAME +FROM information_schema.tables +WHERE TABLE_TYPE = 'VIEW' AND TABLE_SCHEMA = ?`; + +export const getTriggersQuery = `SHOW TRIGGERS`; + +export const getEventsQuery = `SHOW EVENTS`; + +export const proceduresQuery = `SHOW PROCEDURE STATUS WHERE type = "PROCEDURE" AND db = ?`; diff --git a/graphql-server/src/queryFactory/mysql/utils.ts b/graphql-server/src/queryFactory/mysql/utils.ts new file mode 100644 index 00000000..cb9d4f63 --- /dev/null +++ b/graphql-server/src/queryFactory/mysql/utils.ts @@ -0,0 +1,3 @@ +export function notDoltError(action: string): Error { + return new Error(`Cannot ${action} on non-Dolt database`); +} diff --git a/graphql-server/src/queryFactory/types.ts b/graphql-server/src/queryFactory/types.ts new file mode 100644 index 00000000..f3e88786 --- /dev/null +++ b/graphql-server/src/queryFactory/types.ts @@ -0,0 +1,151 @@ +import { DataSource, QueryRunner } from "typeorm"; +import { SortBranchesBy } from "../branches/branch.enum"; +import { CommitDiffType } from "../diffSummaries/diffSummary.enums"; +import { DiffRowType } from "../rowDiffs/rowDiff.enums"; +import { SchemaType } from "../schemas/schema.enums"; + +export type DBArgs = { databaseName: string }; +export type RefArgs = DBArgs & { refName: string }; +export type BranchArgs = DBArgs & { branchName: string }; +export type TagArgs = DBArgs & { tagName: string }; +export type TableArgs = RefArgs & { tableName: string }; + +export type BranchesArgs = DBArgs & { + fromBranchName: string; + toBranchName: string; + refName?: string; +}; +export type RefsArgs = DBArgs & { + fromRefName: string; + toRefName: string; + refName?: string; +}; +export type RowDiffArgs = DBArgs & { + refName?: string; + tableName: string; + fromTableName: string; + toTableName: string; + fromCommitId: string; + toCommitId: string; + offset: number; + filterByRowType?: DiffRowType; +}; + +export type RawRow = Record; +export type RawRows = RawRow[]; +export type PR = Promise; +export type SPR = Promise; +export type UPR = Promise; +export type Params = Array | undefined; +export type ParQuery = (q: string, p?: Params) => PR; + +export type TableRowPagination = { columns: RawRow[]; offset: number }; +export type DiffRes = Promise<{ colsUnion: RawRows; diff: RawRows }>; +export type CommitsRes = Promise<{ fromCommitId: string; toCommitId: string }>; + +export declare class QueryFactory { + ds: DataSource | undefined; + + isDolt: boolean; + + constructor(ds: DataSource | undefined); + + // UTILS + + getDS(): DataSource; + + getQR(): QueryRunner; + + handleAsyncQuery(work: (qr: QueryRunner) => Promise): Promise; + + query(q: string, p: Params, dbName?: string, refName?: string): Promise; + + queryMultiple( + executeQuery: (pq: ParQuery) => Promise, + dbName?: string, + refName?: string, + ): Promise; + + // QUERIES + + databases(): PR; + + currentDatabase(): Promise; + + createDatabase(args: DBArgs): Promise; + + getTableNames(args: RefArgs, filterSystemTables?: boolean): PR; + + getTables(args: RefArgs, tns: string[]): PR; + + getTableInfo(args: TableArgs): SPR; + + getTableColumns(args: TableArgs): PR; + + getTableRows(args: TableArgs, page: TableRowPagination): PR; + + getSqlSelect(args: RefArgs & { queryString: string }): PR; + + getSchemas(args: RefArgs, type?: SchemaType): UPR; + + getProcedures(args: RefArgs): UPR; + + // DOLT-SPECIFIC QUERIES + + getBranch(args: BranchArgs): PR; + + getBranches(args: DBArgs & { sortBy?: SortBranchesBy }): PR; + + createNewBranch(args: BranchArgs & { fromRefName: string }): PR; + + callDeleteBranch(args: BranchArgs): PR; + + getLogs(args: RefArgs, offset: number): PR; + + getTwoDotLogs(args: RefsArgs): PR; + + getDiffStat(args: RefsArgs & { tableName?: string }): PR; + + getThreeDotDiffStat(args: RefsArgs & { tableName?: string }): PR; + + getDiffSummary(args: RefsArgs & { tableName?: string }): PR; + + getThreeDotDiffSummary(args: RefsArgs & { tableName?: string }): PR; + + getSchemaPatch(args: RefsArgs & { tableName: string }): PR; + + getThreeDotSchemaPatch(args: RefsArgs & { tableName: string }): PR; + + getSchemaDiff(args: RefsArgs & { tableName: string }): PR; + + getThreeDotSchemaDiff(args: RefsArgs & { tableName: string }): PR; + + getDocs(args: RefArgs): UPR; + + getStatus(args: RefArgs): PR; + + getTag(args: TagArgs): PR; + + getTags(args: DBArgs): PR; + + createNewTag( + args: TagArgs & { + fromRefName: string; + message?: string; + }, + ): PR; + + callDeleteTag(args: TagArgs): PR; + + callMerge(args: BranchesArgs): Promise; + + resolveRefs( + args: RefsArgs & { type?: CommitDiffType }, + ): Promise<{ fromCommitId: string; toCommitId: string }>; + + getOneSidedRowDiff( + args: TableArgs & { offset: number }, + ): Promise<{ rows: RawRows; columns: RawRows }>; + + getRowDiffs(args: RowDiffArgs): DiffRes; +} diff --git a/graphql-server/src/resolvers.ts b/graphql-server/src/resolvers.ts index f92ffd35..a4abd4ce 100644 --- a/graphql-server/src/resolvers.ts +++ b/graphql-server/src/resolvers.ts @@ -1,5 +1,6 @@ import { BranchResolver } from "./branches/branch.resolver"; import { CommitResolver } from "./commits/commit.resolver"; +import { ConnectionResolver } from "./connections/connection.resolver"; import { DatabaseResolver } from "./databases/database.resolver"; import { DiffStatResolver } from "./diffStats/diffStat.resolver"; import { DiffSummaryResolver } from "./diffSummaries/diffSummary.resolver"; @@ -18,6 +19,7 @@ import { TagResolver } from "./tags/tag.resolver"; const resolvers = [ BranchResolver, CommitResolver, + ConnectionResolver, DatabaseResolver, DiffStatResolver, DiffSummaryResolver, diff --git a/graphql-server/src/rowDiffs/rowDiff.model.ts b/graphql-server/src/rowDiffs/rowDiff.model.ts index 6b52b372..cda5f5c5 100644 --- a/graphql-server/src/rowDiffs/rowDiff.model.ts +++ b/graphql-server/src/rowDiffs/rowDiff.model.ts @@ -1,9 +1,10 @@ import { Field, ObjectType } from "@nestjs/graphql"; -import * as columns from "../columns/column.model"; +import * as column from "../columns/column.model"; import { Column } from "../columns/column.model"; +import { RawRow, RawRows } from "../queryFactory/types"; import * as row from "../rows/row.model"; import { ROW_LIMIT, getNextOffset } from "../utils"; -import { ListOffsetRes, RawRow } from "../utils/commonTypes"; +import { ListOffsetRes } from "../utils/commonTypes"; import { DiffRowType } from "./rowDiff.enums"; import { canShowDroppedOrAddedRows } from "./utils"; @@ -27,19 +28,21 @@ export class RowDiffList extends ListOffsetRes { @ObjectType() export class RowListWithCols extends row.RowList { - @Field(_type => [columns.Column]) - columns: columns.Column[]; + @Field(_type => [column.Column]) + columns: column.Column[]; } export function fromRowDiffRowsWithCols( - cols: Column[], - diffs: RawRow[], + tableName: string, + cols: RawRows, + diffs: RawRows, offset: number, ): RowDiffList { + const columns = cols.map(c => column.fromDoltRowRes(c, tableName)); const rowDiffsList: RowDiff[] = diffs.map(rd => { const addedVals: Array = []; const deletedVals: Array = []; - cols.forEach(c => { + columns.forEach(c => { addedVals.push(rd[`to_${c.name}`]); deletedVals.push(rd[`from_${c.name}`]); }); @@ -50,7 +53,7 @@ export function fromRowDiffRowsWithCols( return { list: rowDiffsList.slice(0, ROW_LIMIT), nextOffset: getNextOffset(rowDiffsList.length, offset), - columns: cols, + columns, }; } @@ -63,7 +66,7 @@ export function fromDoltListRowWithColsRes( return { list: rows.slice(0, ROW_LIMIT).map(row.fromDoltRowRes), nextOffset: getNextOffset(rows.length, offset), - columns: cols.map(c => columns.fromDoltRowRes(c, tableName)), + columns: cols.map(c => column.fromDoltRowRes(c, tableName)), }; } diff --git a/graphql-server/src/rowDiffs/rowDiff.queries.ts b/graphql-server/src/rowDiffs/rowDiff.queries.ts deleted file mode 100644 index 8b50f9e5..00000000 --- a/graphql-server/src/rowDiffs/rowDiff.queries.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { getOrderByFromCols, getPKColsForRowsQuery } from "../rows/row.queries"; -import { RawRows } from "../utils/commonTypes"; - -export const getRowsQueryAsOf = ( - columns: RawRows, -): { q: string; cols: string[] } => { - const cols = getPKColsForRowsQuery(columns); - return { - q: `SELECT * FROM ?? AS OF ? ${getOrderByFromCols( - cols.length, - )}LIMIT ? OFFSET ?`, - cols, - }; -}; - -export const tableColsQueryAsOf = `DESCRIBE ?? AS OF ?`; - -export const hashOf = `SELECT HASHOF(?)`; -export const mergeBase = `SELECT DOLT_MERGE_BASE(?, ?)`; - -export function getTableCommitDiffQuery( - cols: RawRows, - hasFilter = false, -): string { - const whereDiffType = hasFilter ? ` WHERE diff_type=? ` : ""; - return `SELECT * FROM DOLT_DIFF(?, ?, ?)${whereDiffType} - ${getOrderByFromDiffCols(cols)} - LIMIT ? - OFFSET ?`; -} - -export function getOrderByFromDiffCols(cols: RawRows): string { - const pkCols = cols.filter(col => col.Key === "PRI"); - const diffCols: string[] = []; - pkCols.forEach(col => { - diffCols.push(`to_${col.Field}`); - diffCols.push(`from_${col.Field}`); - }); - const orderBy = diffCols.map(c => `\`${c}\` ASC`).join(", "); - return orderBy === "" ? "" : `ORDER BY ${orderBy} `; -} diff --git a/graphql-server/src/rowDiffs/rowDiff.resolver.ts b/graphql-server/src/rowDiffs/rowDiff.resolver.ts index 271772e2..ce93264b 100644 --- a/graphql-server/src/rowDiffs/rowDiff.resolver.ts +++ b/graphql-server/src/rowDiffs/rowDiff.resolver.ts @@ -1,15 +1,12 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import * as column from "../columns/column.model"; -import { DataSourceService, ParQuery } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { CommitDiffType, TableDiffType, } from "../diffSummaries/diffSummary.enums"; import { getDiffSummaries } from "../diffSummaries/diffSummary.resolver"; -import { ListRowsArgs } from "../rows/row.resolver"; -import { ROW_LIMIT } from "../utils"; import { DBArgsWithOffset } from "../utils/commonTypes"; -import { DiffRowType, convertToStringForQuery } from "./rowDiff.enums"; +import { DiffRowType } from "./rowDiff.enums"; import { RowDiff, RowDiffList, @@ -17,14 +14,6 @@ import { fromOneSidedTable, fromRowDiffRowsWithCols, } from "./rowDiff.model"; -import { - getRowsQueryAsOf, - getTableCommitDiffQuery, - hashOf, - mergeBase, - tableColsQueryAsOf, -} from "./rowDiff.queries"; -import { unionCols } from "./utils"; @ArgsType() class ListRowDiffsArgs extends DBArgsWithOffset { @@ -49,7 +38,7 @@ class ListRowDiffsArgs extends DBArgsWithOffset { @Resolver(_of => RowDiff) export class RowDiffResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => RowDiffList) async rowDiffs( @@ -58,107 +47,64 @@ export class RowDiffResolver { ): Promise { const dbArgs = { databaseName, refName }; const offset = args.offset ?? 0; - - return this.dss.query( - async query => { - const ds = await getDiffSummaries(query, { - ...dbArgs, - tableName, - fromRefName: args.fromRefName, - toRefName: args.toRefName, - type: args.type, - }); - if (!ds.length) { - throw new Error(`Could not get summary for table "${tableName}"`); - } - - const { fromCommitId, toCommitId } = await resolveRefs( - query, - args.fromRefName, - args.toRefName, - args.type, - ); - - const { tableType, fromTableName, toTableName } = ds[0]; - if (tableType === TableDiffType.Dropped) { - const rows = await getRowsForDiff(query, { - ...dbArgs, - tableName, - refName: fromCommitId, - }); - return fromOneSidedTable(rows, "dropped", args.filterByRowType); - } - if (tableType === TableDiffType.Added) { - const rows = await getRowsForDiff(query, { - ...dbArgs, - tableName, - refName: toCommitId, - }); - return fromOneSidedTable(rows, "added", args.filterByRowType); - } - - const oldCols = await query(tableColsQueryAsOf, [ - fromTableName, - fromCommitId, - ]); - const newCols = await query(tableColsQueryAsOf, [ - toTableName, - toCommitId, - ]); - - const colsUnion = unionCols( - oldCols.map(c => column.fromDoltRowRes(c, fromTableName)), - newCols.map(c => column.fromDoltRowRes(c, toTableName)), - ); - - const diffType = convertToStringForQuery(args.filterByRowType); - const refArgs = [fromCommitId, toCommitId, tableName]; - const pageArgs = [ROW_LIMIT + 1, offset]; - const diffs = await query( - getTableCommitDiffQuery(colsUnion, !!diffType), - diffType - ? [...refArgs, diffType, ...pageArgs] - : [...refArgs, ...pageArgs], - ); - - return fromRowDiffRowsWithCols(colsUnion, diffs, offset); - }, - databaseName, - refName, - ); - } -} - -async function getRowsForDiff(query: ParQuery, args: ListRowsArgs) { - const columns = await query(tableColsQueryAsOf, [ - args.tableName, - args.refName, - ]); - const offset = args.offset ?? 0; - const { q, cols } = getRowsQueryAsOf(columns); - const rows = await query(q, [ - args.tableName, - args.refName, - ...cols, - ROW_LIMIT + 1, - offset, - ]); - return fromDoltListRowWithColsRes(rows, columns, offset, args.tableName); -} - -async function resolveRefs( - query: ParQuery, - fromRefName: string, - toRefName: string, - type?: CommitDiffType, -): Promise<{ fromCommitId: string; toCommitId: string }> { - if (type === CommitDiffType.ThreeDot) { - const toCommitId = await query(hashOf, [fromRefName]); - const mergeBaseCommit = await query(mergeBase, [toRefName, fromRefName]); - return { - fromCommitId: Object.values(mergeBaseCommit[0])[0], - toCommitId: Object.values(toCommitId[0])[0], - }; + const conn = this.conn.connection(); + + const ds = await getDiffSummaries(conn, { + ...dbArgs, + tableName, + fromRefName: args.fromRefName, + toRefName: args.toRefName, + type: args.type, + }); + if (!ds.length) { + throw new Error(`Could not get summary for table "${tableName}"`); + } + + const { fromCommitId, toCommitId } = await conn.resolveRefs({ + ...dbArgs, + ...args, + }); + const { tableType, fromTableName, toTableName } = ds[0]; + + if (tableType === TableDiffType.Dropped) { + const { rows, columns } = await conn.getOneSidedRowDiff({ + ...dbArgs, + tableName, + refName: fromCommitId, + offset, + }); + return fromOneSidedTable( + fromDoltListRowWithColsRes(rows, columns, offset, tableName), + "dropped", + args.filterByRowType, + ); + } + + if (tableType === TableDiffType.Added) { + const { rows, columns } = await conn.getOneSidedRowDiff({ + ...dbArgs, + tableName, + refName: toCommitId, + offset, + }); + return fromOneSidedTable( + fromDoltListRowWithColsRes(rows, columns, offset, tableName), + "added", + args.filterByRowType, + ); + } + + const { colsUnion, diff } = await conn.getRowDiffs({ + ...dbArgs, + tableName, + fromTableName, + toTableName, + fromCommitId, + toCommitId, + offset, + filterByRowType: args.filterByRowType, + }); + + return fromRowDiffRowsWithCols(tableName, colsUnion, diff, offset); } - return { fromCommitId: fromRefName, toCommitId: toRefName }; } diff --git a/graphql-server/src/rows/row.model.ts b/graphql-server/src/rows/row.model.ts index 5d37aa2e..a1482931 100644 --- a/graphql-server/src/rows/row.model.ts +++ b/graphql-server/src/rows/row.model.ts @@ -1,10 +1,11 @@ import { Field, ObjectType } from "@nestjs/graphql"; +import { RawRow } from "../queryFactory/types"; import { ROW_LIMIT, convertDateToUTCDatetimeString, getNextOffset, } from "../utils"; -import { ListOffsetRes, RawRow } from "../utils/commonTypes"; +import { ListOffsetRes } from "../utils/commonTypes"; // Using an unprintable string for null values so we can distinguish between // string "null" and null diff --git a/graphql-server/src/rows/row.queries.ts b/graphql-server/src/rows/row.queries.ts deleted file mode 100644 index 4cda1897..00000000 --- a/graphql-server/src/rows/row.queries.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { RawRows } from "../utils/commonTypes"; - -export const getRowsQuery = ( - columns: RawRows, -): { q: string; cols: string[] } => { - const cols = getPKColsForRowsQuery(columns); - return { - q: `SELECT * FROM ?? ${getOrderByFromCols(cols.length)}LIMIT ? OFFSET ?`, - cols, - }; -}; - -export function getPKColsForRowsQuery(cs: RawRows): string[] { - const pkCols = cs.filter(col => col.Key === "PRI"); - const cols = pkCols.map(c => c.Field); - return cols; -} - -// Creates ORDER BY statement with column parameters -// i.e. ORDER BY ::col1, ::col2 -export function getOrderByFromCols(numCols: number): string { - if (!numCols) return ""; - const pkCols = Array.from({ length: numCols }) - .map(() => `? ASC`) - .join(", "); - return pkCols === "" ? "" : `ORDER BY ${pkCols} `; -} diff --git a/graphql-server/src/rows/row.resolver.ts b/graphql-server/src/rows/row.resolver.ts index c5aa6fef..f00c3639 100644 --- a/graphql-server/src/rows/row.resolver.ts +++ b/graphql-server/src/rows/row.resolver.ts @@ -1,10 +1,7 @@ import { Args, ArgsType, Field, Int, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; -import { listTablesQuery } from "../tables/table.queries"; -import { ROW_LIMIT } from "../utils"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { RefArgs } from "../utils/commonTypes"; import { Row, RowList, fromDoltListRowRes } from "./row.model"; -import { getRowsQuery } from "./row.queries"; @ArgsType() export class ListRowsArgs extends RefArgs { @@ -17,25 +14,17 @@ export class ListRowsArgs extends RefArgs { @Resolver(_of => Row) export class RowResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => RowList) async rows(@Args() args: ListRowsArgs): Promise { - return this.dss.queryMaybeDolt( - async query => { - const columns = await query(listTablesQuery, [args.tableName]); - const offset = args.offset ?? 0; - const { q, cols } = getRowsQuery(columns); - const rows = await query(q, [ - args.tableName, - ...cols, - ROW_LIMIT + 1, - offset, - ]); - return fromDoltListRowRes(rows, offset); - }, - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + const columns = await conn.getTableColumns(args); + const offset = args.offset ?? 0; + const rows = await conn.getTableRows(args, { + columns, + offset, + }); + return fromDoltListRowRes(rows, offset); } } diff --git a/graphql-server/src/schemaDiffs/schemaDiff.model.ts b/graphql-server/src/schemaDiffs/schemaDiff.model.ts index 5913fc07..ff576964 100644 --- a/graphql-server/src/schemaDiffs/schemaDiff.model.ts +++ b/graphql-server/src/schemaDiffs/schemaDiff.model.ts @@ -1,5 +1,5 @@ import { Field, Int, ObjectType } from "@nestjs/graphql"; -import { RawRows } from "../utils/commonTypes"; +import { RawRows } from "../queryFactory/types"; @ObjectType() export class TextDiff { diff --git a/graphql-server/src/schemaDiffs/schemaDiff.queries.ts b/graphql-server/src/schemaDiffs/schemaDiff.queries.ts deleted file mode 100644 index a4dfdee2..00000000 --- a/graphql-server/src/schemaDiffs/schemaDiff.queries.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const schemaPatchQuery = `SELECT * FROM DOLT_PATCH(?, ?, ?) WHERE diff_type="schema"`; - -export const threeDotSchemaPatchQuery = `SELECT * FROM DOLT_PATCH(?, ?) WHERE diff_type="schema"`; - -export const schemaDiffQuery = `SELECT * FROM DOLT_SCHEMA_DIFF(?, ?, ?)`; - -export const threeDotSchemaDiffQuery = `SELECT * FROM DOLT_SCHEMA_DIFF(?, ?)`; diff --git a/graphql-server/src/schemaDiffs/schemaDiff.resolver.ts b/graphql-server/src/schemaDiffs/schemaDiff.resolver.ts index 5addbe89..fbb2e665 100644 --- a/graphql-server/src/schemaDiffs/schemaDiff.resolver.ts +++ b/graphql-server/src/schemaDiffs/schemaDiff.resolver.ts @@ -1,13 +1,8 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { CommitDiffType } from "../diffSummaries/diffSummary.enums"; import { DBArgs } from "../utils/commonTypes"; import { SchemaDiff, fromDoltSchemaDiffRows } from "./schemaDiff.model"; -import { - schemaDiffQuery, - schemaPatchQuery, - threeDotSchemaPatchQuery, -} from "./schemaDiff.queries"; @ArgsType() class SchemaDiffArgs extends DBArgs { @@ -29,31 +24,22 @@ class SchemaDiffArgs extends DBArgs { @Resolver(_of => SchemaDiff) export class SchemaDiffResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => SchemaDiff, { nullable: true }) async schemaDiff( @Args() args: SchemaDiffArgs, ): Promise { - return this.dss.query( - async query => { - if (args.type === CommitDiffType.ThreeDot) { - const commitArgs = [ - `${args.toRefName}...${args.fromRefName}`, - args.tableName, - ]; - const patchRes = await query(threeDotSchemaPatchQuery, commitArgs); - const diffRes = await query(schemaDiffQuery, commitArgs); - return fromDoltSchemaDiffRows(patchRes, diffRes); - } - - const commitArgs = [args.fromRefName, args.toRefName, args.tableName]; - const patchRes = await query(schemaPatchQuery, commitArgs); - const diffRes = await query(schemaDiffQuery, commitArgs); - return fromDoltSchemaDiffRows(patchRes, diffRes); - }, - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + + if (args.type === CommitDiffType.ThreeDot) { + const patchRes = await conn.getThreeDotSchemaPatch(args); + const diffRes = await conn.getThreeDotSchemaDiff(args); + return fromDoltSchemaDiffRows(patchRes, diffRes); + } + + const patchRes = await conn.getSchemaPatch(args); + const diffRes = await conn.getSchemaDiff(args); + return fromDoltSchemaDiffRows(patchRes, diffRes); } } diff --git a/graphql-server/src/schemas/schema.queries.ts b/graphql-server/src/schemas/schema.queries.ts deleted file mode 100644 index 29353b8d..00000000 --- a/graphql-server/src/schemas/schema.queries.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DoltSystemTable } from "../systemTables/systemTable.enums"; - -export const getDoltSchemasQuery = (hasWhereCause = false): string => - `SELECT * FROM ${DoltSystemTable.SCHEMAS}${ - hasWhereCause ? " WHERE type = ?" : "" - }`; - -export const doltProceduresQuery = `SELECT * FROM ${DoltSystemTable.PROCEDURES}`; - -export const getViewsQuery = `SELECT TABLE_SCHEMA, TABLE_NAME -FROM information_schema.tables -WHERE TABLE_TYPE = 'VIEW' AND TABLE_SCHEMA = ?`; - -export const getTriggersQuery = `SHOW TRIGGERS`; - -export const getEventsQuery = `SHOW EVENTS`; - -export const getProceduresQuery = `SHOW PROCEDURE STATUS WHERE type = "PROCEDURE" AND db = ?`; diff --git a/graphql-server/src/schemas/schema.resolver.ts b/graphql-server/src/schemas/schema.resolver.ts index 128fd805..50f8c97a 100644 --- a/graphql-server/src/schemas/schema.resolver.ts +++ b/graphql-server/src/schemas/schema.resolver.ts @@ -1,44 +1,24 @@ import { Args, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService, ParQuery } from "../dataSources/dataSource.service"; -import { handleTableNotFound } from "../tables/table.resolver"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { RefArgs } from "../utils/commonTypes"; import { SchemaType } from "./schema.enums"; import { SchemaItem } from "./schema.model"; -import { - doltProceduresQuery, - getDoltSchemasQuery, - getEventsQuery, - getProceduresQuery, - getTriggersQuery, - getViewsQuery, -} from "./schema.queries"; @Resolver(_of => SchemaItem) export class SchemaResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => [SchemaItem]) async doltSchemas( @Args() args: RefArgs, type?: SchemaType, ): Promise { - return this.dss.queryMaybeDolt( - async (query, isDolt) => { - if (!isDolt) { - return getSchemasForNonDolt(query, args.databaseName, type); - } - - const res = await handleTableNotFound(async () => - query(getDoltSchemasQuery(!!type), [type]), - ); - if (!res) return []; - return res.map(r => { - return { name: r.name, type: r.type }; - }); - }, - args.databaseName, - args.refName, - ); + 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 }; + }); } @Query(_returns => [SchemaItem]) @@ -47,52 +27,15 @@ export class SchemaResolver { } @Query(_returns => [SchemaItem]) - async doltProcedures(@Args() args: RefArgs): Promise<[SchemaItem]> { - return this.dss.queryMaybeDolt( - async (query, isDolt) => { - if (!isDolt) { - const res = await query(getProceduresQuery, [args.databaseName]); - return res.map(r => { - return { name: r.Name, type: SchemaType.Procedure }; - }); - } - - const res = await handleTableNotFound(async () => - query(doltProceduresQuery), - ); - if (!res) return []; - return res.map(r => { - return { name: r.name, type: SchemaType.Procedure }; - }); - }, - args.databaseName, - args.refName, - ); - } -} - -async function getSchemasForNonDolt( - query: ParQuery, - dbName: string, - type?: SchemaType, -): Promise { - const vRes = await query(getViewsQuery, [dbName]); - const views = vRes.map(v => { - return { name: v.TABLE_NAME, type: SchemaType.View }; - }); - if (type === SchemaType.View) { - return views; + 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, + }; + }); } - - const tRes = await query(getTriggersQuery); - const triggers = tRes.map(t => { - return { name: t.Trigger, type: SchemaType.Trigger }; - }); - - const eRes = await query(getEventsQuery); - const events = eRes.map(e => { - return { name: e.Name, type: SchemaType.Event }; - }); - - return [...views, ...triggers, ...events]; } diff --git a/graphql-server/src/sqlSelects/sqlSelect.model.ts b/graphql-server/src/sqlSelects/sqlSelect.model.ts index 8d5feff9..eba8fa8c 100644 --- a/graphql-server/src/sqlSelects/sqlSelect.model.ts +++ b/graphql-server/src/sqlSelects/sqlSelect.model.ts @@ -1,7 +1,7 @@ import { Field, ID, ObjectType } from "@nestjs/graphql"; import * as column from "../columns/column.model"; +import { RawRow } from "../queryFactory/types"; import * as row from "../rows/row.model"; -import { RawRow } from "../utils/commonTypes"; import { QueryExecutionStatus } from "./sqlSelect.enums"; @ObjectType() diff --git a/graphql-server/src/sqlSelects/sqlSelect.resolver.ts b/graphql-server/src/sqlSelects/sqlSelect.resolver.ts index 5b5b65ad..fffdc9ba 100644 --- a/graphql-server/src/sqlSelects/sqlSelect.resolver.ts +++ b/graphql-server/src/sqlSelects/sqlSelect.resolver.ts @@ -1,7 +1,8 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; +import { RawRows } from "../queryFactory/types"; import { getCellValue } from "../rows/row.model"; -import { RawRows, RefArgs } from "../utils/commonTypes"; +import { RefArgs } from "../utils/commonTypes"; import { SqlSelect, fromSqlSelectRow } from "./sqlSelect.model"; @ArgsType() @@ -12,35 +13,25 @@ export class SqlSelectArgs extends RefArgs { @Resolver(_of => SqlSelect) export class SqlSelectResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => SqlSelect) async sqlSelect(@Args() args: SqlSelectArgs): Promise { - return this.dss.queryMaybeDolt( - async query => { - const res = await query(args.queryString); - return fromSqlSelectRow( - args.databaseName, - args.refName, - res, - args.queryString, - ); - }, + const conn = this.conn.connection(); + const res = await conn.getSqlSelect(args); + return fromSqlSelectRow( args.databaseName, args.refName, + res, + args.queryString, ); } @Query(_returns => String) async sqlSelectForCsvDownload(@Args() args: SqlSelectArgs): Promise { - return this.dss.queryMaybeDolt( - async query => { - const res = await query(args.queryString); - return toCsvString(res); - }, - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + const res = await conn.getSqlSelect(args); + return toCsvString(res); } } diff --git a/graphql-server/src/status/status.model.ts b/graphql-server/src/status/status.model.ts index 32ee145a..d99b7a1c 100644 --- a/graphql-server/src/status/status.model.ts +++ b/graphql-server/src/status/status.model.ts @@ -1,5 +1,5 @@ import { Field, ID, ObjectType } from "@nestjs/graphql"; -import { RawRows } from "../utils/commonTypes"; +import { RawRows } from "../queryFactory/types"; @ObjectType() export class Status { diff --git a/graphql-server/src/status/status.queries.ts b/graphql-server/src/status/status.queries.ts deleted file mode 100644 index 6589fe11..00000000 --- a/graphql-server/src/status/status.queries.ts +++ /dev/null @@ -1 +0,0 @@ -export const statusQuery = `SELECT * FROM dolt_status`; diff --git a/graphql-server/src/status/status.resolver.ts b/graphql-server/src/status/status.resolver.ts index 5a6ee128..332e919d 100644 --- a/graphql-server/src/status/status.resolver.ts +++ b/graphql-server/src/status/status.resolver.ts @@ -1,22 +1,16 @@ import { Args, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { RefArgs } from "../utils/commonTypes"; import { Status, fromStatusRows } from "./status.model"; -import { statusQuery } from "./status.queries"; @Resolver(_of => Status) export class StatusResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => [Status]) async status(@Args() args: RefArgs): Promise { - return this.dss.query( - async query => { - const res = await query(statusQuery); - return fromStatusRows(res, args.databaseName, args.refName); - }, - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + const res = await conn.getStatus(args); + return fromStatusRows(res, args.databaseName, args.refName); } } diff --git a/graphql-server/src/tables/table.model.ts b/graphql-server/src/tables/table.model.ts index 8a3ac796..ed700a4d 100644 --- a/graphql-server/src/tables/table.model.ts +++ b/graphql-server/src/tables/table.model.ts @@ -2,7 +2,7 @@ import { Field, ID, ObjectType } from "@nestjs/graphql"; import * as column from "../columns/column.model"; import * as foreignKey from "../indexes/foreignKey.model"; import * as index from "../indexes/index.model"; -import { RawRow } from "../utils/commonTypes"; +import { RawRow } from "../queryFactory/types"; @ObjectType() export class Table { diff --git a/graphql-server/src/tables/table.queries.ts b/graphql-server/src/tables/table.queries.ts deleted file mode 100644 index f90134d4..00000000 --- a/graphql-server/src/tables/table.queries.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { FileType, LoadDataModifier } from "./table.enum"; - -export const indexQuery = `SELECT - table_name, - index_name, - GROUP_CONCAT(comment) as COMMENTS, - GROUP_CONCAT(non_unique) AS NON_UNIQUES, - GROUP_CONCAT(column_name ORDER BY seq_in_index) AS COLUMNS -FROM information_schema.statistics -WHERE table_name=? AND index_name!="PRIMARY" -GROUP BY index_name;`; - -export const foreignKeysQuery = `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_name=? AND table_schema=? AND referenced_table_schema IS NOT NULL`; - -export const columnsQuery = `DESCRIBE ??`; - -export const listTablesQuery = `SHOW FULL TABLES WHERE table_type = 'BASE TABLE'`; - -export const getLoadDataQuery = ( - filename: string, - tableName: string, - fileType: FileType, - modifier?: LoadDataModifier, -): string => `LOAD DATA LOCAL INFILE '${filename}' -${getModifier(modifier)}INTO TABLE \`${tableName}\` -FIELDS TERMINATED BY '${getDelim(fileType)}' ENCLOSED BY '' -LINES TERMINATED BY '\n' -IGNORE 1 ROWS;`; - -function getModifier(m?: LoadDataModifier): string { - switch (m) { - case LoadDataModifier.Ignore: - return "IGNORE "; - case LoadDataModifier.Replace: - return "REPLACE "; - default: - return ""; - } -} - -function getDelim(ft: FileType): string { - if (ft === FileType.Psv) { - return "|"; - } - return ","; -} diff --git a/graphql-server/src/tables/table.resolver.ts b/graphql-server/src/tables/table.resolver.ts index 8e0fdbee..9d23a8d5 100644 --- a/graphql-server/src/tables/table.resolver.ts +++ b/graphql-server/src/tables/table.resolver.ts @@ -1,14 +1,8 @@ import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql"; -import { DataSourceService, ParQuery } from "../dataSources/dataSource.service"; -import { systemTableValues } from "../systemTables/systemTable.enums"; +import { ConnectionResolver } from "../connections/connection.resolver"; +import { handleTableNotFound } from "../utils"; import { RefArgs, TableArgs } from "../utils/commonTypes"; import { Table, TableNames, fromDoltRowRes } from "./table.model"; -import { - columnsQuery, - foreignKeysQuery, - indexQuery, - listTablesQuery, -} from "./table.queries"; import { mapTablesRes } from "./utils"; @ArgsType() @@ -19,40 +13,43 @@ class ListTableArgs extends RefArgs { @Resolver(_of => Table) export class TableResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => Table) async table(@Args() args: TableArgs): Promise { - return this.dss.queryMaybeDolt( - async (q, isDolt) => getTableInfo(q, args, isDolt), + const conn = this.conn.connection(); + const res = await conn.getTableInfo(args); + return fromDoltRowRes( args.databaseName, args.refName, + args.tableName, + res.columns, + res.fkRows, + res.idxRows, ); } @Query(_returns => TableNames) async tableNames(@Args() args: ListTableArgs): Promise { - return this.dss.queryMaybeDolt( - async (q, isDolt) => getTableNames(q, args, isDolt), - args.databaseName, - args.refName, - ); + const conn = this.conn.connection(); + const res = await conn.getTableNames(args, args.filterSystemTables); + return { list: mapTablesRes(res) }; } @Query(_returns => [Table]) async tables(@Args() args: ListTableArgs): Promise { - return this.dss.queryMaybeDolt( - async (q, isDolt) => { - const tableNames = await getTableNames(q, args, isDolt); - const tables = await Promise.all( - tableNames.list.map(async name => - getTableInfo(q, { ...args, tableName: name }, isDolt), - ), - ); - return tables; - }, - args.databaseName, - args.refName, + const conn = this.conn.connection(); + const tableNames = await conn.getTableNames(args, args.filterSystemTables); + const res = await conn.getTables(args, mapTablesRes(tableNames)); + return res.map(t => + fromDoltRowRes( + args.databaseName, + args.refName, + t.name, + t.columns, + t.fkRows, + t.idxRows, + ), ); } @@ -61,66 +58,3 @@ export class TableResolver { return handleTableNotFound(async () => this.table(args)); } } - -async function getTableNames( - query: ParQuery, - args: ListTableArgs, - isDolt: boolean, -): Promise { - const tables = await query(listTablesQuery); - const mapped = mapTablesRes(tables); - - if (args.filterSystemTables || !isDolt) return { list: mapped }; - - // Add system tables if filter is false - const systemTables = await getSystemTables(query); - return { list: [...mapped, ...systemTables] }; -} - -async function getTableInfo( - query: ParQuery, - args: TableArgs, - isDolt: boolean, -): Promise
{ - const columns = await query(columnsQuery, [args.tableName]); - const fkRows = await query(foreignKeysQuery, [ - args.tableName, - isDolt ? `${args.databaseName}/${args.refName}` : args.databaseName, - ]); - const idxRows = await query(indexQuery, [args.tableName]); - return fromDoltRowRes( - args.databaseName, - args.refName, - args.tableName, - columns, - fkRows, - idxRows, - ); -} - -export async function handleTableNotFound( - q: () => Promise, -): Promise { - try { - const res = await q(); - return res; - } catch (err) { - if (err.message.includes("table not found")) { - return undefined; - } - throw err; - } -} - -export async function getSystemTables(query: ParQuery): Promise { - const systemTables = await Promise.all( - systemTableValues.map(async st => { - const cols = await handleTableNotFound(async () => - query(columnsQuery, [st]), - ); - if (cols) return `${st}`; - return undefined; - }), - ); - return systemTables.filter(st => !!st) as string[]; -} diff --git a/graphql-server/src/tables/upload.resolver.ts b/graphql-server/src/tables/upload.resolver.ts index eca3676a..6f98ca86 100644 --- a/graphql-server/src/tables/upload.resolver.ts +++ b/graphql-server/src/tables/upload.resolver.ts @@ -1,15 +1,11 @@ import { Args, ArgsType, Field, Mutation, Resolver } from "@nestjs/graphql"; import { ReadStream } from "fs"; import { GraphQLUpload } from "graphql-upload"; -import * as mysql from "mysql2/promise"; -import { - DataSourceService, - useDBStatement, -} from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; +import { useDB } from "../queryFactory/mysql/queries"; import { TableArgs } from "../utils/commonTypes"; import { FileType, ImportOperation, LoadDataModifier } from "./table.enum"; import { Table } from "./table.model"; -import { getLoadDataQuery } from "./table.queries"; export interface FileUpload { filename: string; @@ -35,11 +31,11 @@ class TableImportArgs extends TableArgs { @Resolver(_of => Table) export class FileUploadResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly connResolver: ConnectionResolver) {} @Mutation(_returns => Boolean) async loadDataFile(@Args() args: TableImportArgs): Promise { - const conn = await mysql.createConnection(this.dss.getMySQLConfig()); + const conn = await this.connResolver.mysqlConnection(); let isDolt = false; try { @@ -49,7 +45,7 @@ export class FileUploadResolver { // ignore } - await conn.query(useDBStatement(args.databaseName, args.refName, isDolt)); + await conn.query(useDB(args.databaseName, args.refName, isDolt)); await conn.query("SET GLOBAL local_infile=ON;"); const { createReadStream, filename } = await args.file; @@ -69,3 +65,34 @@ export class FileUploadResolver { return true; } } + +function getLoadDataQuery( + filename: string, + tableName: string, + fileType: FileType, + modifier?: LoadDataModifier, +): string { + return `LOAD DATA LOCAL INFILE '${filename}' +${getModifier(modifier)}INTO TABLE \`${tableName}\` +FIELDS TERMINATED BY '${getDelim(fileType)}' ENCLOSED BY '' +LINES TERMINATED BY '\n' +IGNORE 1 ROWS;`; +} + +function getModifier(m?: LoadDataModifier): string { + switch (m) { + case LoadDataModifier.Ignore: + return "IGNORE "; + case LoadDataModifier.Replace: + return "REPLACE "; + default: + return ""; + } +} + +function getDelim(ft: FileType): string { + if (ft === FileType.Psv) { + return "|"; + } + return ","; +} diff --git a/graphql-server/src/tables/utils.ts b/graphql-server/src/tables/utils.ts index cb766d4c..734e0cef 100644 --- a/graphql-server/src/tables/utils.ts +++ b/graphql-server/src/tables/utils.ts @@ -1,4 +1,4 @@ -import { RawRows } from "../utils/commonTypes"; +import { RawRows } from "../queryFactory/types"; export function mapTablesRes(tables: RawRows): string[] { return tables diff --git a/graphql-server/src/tags/tag.model.ts b/graphql-server/src/tags/tag.model.ts index aab89f9e..65819a27 100644 --- a/graphql-server/src/tags/tag.model.ts +++ b/graphql-server/src/tags/tag.model.ts @@ -1,7 +1,7 @@ import { Field, GraphQLTimestamp, ID, ObjectType } from "@nestjs/graphql"; import * as doltWriter from "../commits/doltWriter.model"; +import { RawRow } from "../queryFactory/types"; import { convertToUTCDate } from "../utils"; -import { RawRow } from "../utils/commonTypes"; @ObjectType() export class Tag { diff --git a/graphql-server/src/tags/tag.queries.ts b/graphql-server/src/tags/tag.queries.ts deleted file mode 100644 index 80104ee3..00000000 --- a/graphql-server/src/tags/tag.queries.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const tagsQuery = `SELECT * FROM dolt_tags ORDER BY date DESC`; - -export const tagQuery = `SELECT * FROM dolt_tags WHERE tag_name=?`; - -export const callDeleteTag = `CALL DOLT_TAG("-d", ?)`; - -export const getCallNewTag = (hasMessage = false, hasAuthor = false) => - `CALL DOLT_TAG(?, ?${hasMessage ? `, "-m", ?` : ""}${getAuthorNameString( - hasAuthor, - )})`; - -export function getAuthorNameString(hasAuthor: boolean): string { - if (!hasAuthor) return ""; - return `, "--author", ?`; -} diff --git a/graphql-server/src/tags/tag.resolver.ts b/graphql-server/src/tags/tag.resolver.ts index 2fe41560..45aa279b 100644 --- a/graphql-server/src/tags/tag.resolver.ts +++ b/graphql-server/src/tags/tag.resolver.ts @@ -6,15 +6,9 @@ import { Query, Resolver, } from "@nestjs/graphql"; -import { DataSourceService } from "../dataSources/dataSource.service"; +import { ConnectionResolver } from "../connections/connection.resolver"; import { DBArgs, TagArgs } from "../utils/commonTypes"; import { Tag, TagList, fromDoltRowRes } from "./tag.model"; -import { - callDeleteTag, - getCallNewTag, - tagQuery, - tagsQuery, -} from "./tag.queries"; // @InputType() // class AuthorInfo { @@ -39,51 +33,41 @@ class CreateTagArgs extends TagArgs { @Resolver(_of => Tag) export class TagResolver { - constructor(private readonly dss: DataSourceService) {} + constructor(private readonly conn: ConnectionResolver) {} @Query(_returns => TagList) async tags(@Args() args: DBArgs): Promise { - return this.dss.query(async query => { - const res = await query(tagsQuery); - return { - list: res.map(t => fromDoltRowRes(args.databaseName, t)), - }; - }, args.databaseName); + const conn = this.conn.connection(); + const res = await conn.getTags(args); + return { + list: res.map(t => fromDoltRowRes(args.databaseName, t)), + }; } @Query(_returns => Tag, { nullable: true }) async tag(@Args() args: TagArgs): Promise { - return this.dss.query(async query => { - const res = await query(tagQuery, [args.tagName]); - if (!res.length) return undefined; - return fromDoltRowRes(args.databaseName, res[0]); - }, args.databaseName); + const conn = this.conn.connection(); + const res = await conn.getTag(args); + if (!res.length) return undefined; + return fromDoltRowRes(args.databaseName, res[0]); } - @Mutation(_returns => Tag) - async createTag(@Args() args: CreateTagArgs): Promise { - return this.dss.query(async query => { - await query(getCallNewTag(!!args.message), [ - args.tagName, - args.fromRefName, - args.message, - // getAuthorString(args.author), - ]); - const res = await query(tagQuery, [args.tagName]); - if (!res.length) throw new Error("Error creating tag"); - return fromDoltRowRes(args.databaseName, res[0]); - }, args.databaseName); + @Mutation(_returns => String) + async createTag(@Args() args: CreateTagArgs): Promise { + const conn = this.conn.connection(); + await conn.createNewTag(args); + return args.tagName; } @Mutation(_returns => Boolean) async deleteTag(@Args() args: TagArgs): Promise { - return this.dss.query(async query => { - await query(callDeleteTag, [args.tagName]); - return true; - }, args.databaseName); + const conn = this.conn.connection(); + await conn.callDeleteTag(args); + return true; } } +// TODO: commit author // export type CommitAuthor = { name: string; email: string }; // function getAuthorString(commitAuthor?: CommitAuthor): string { diff --git a/graphql-server/src/utils/commonTypes.ts b/graphql-server/src/utils/commonTypes.ts index b35325d4..2bd06ffe 100644 --- a/graphql-server/src/utils/commonTypes.ts +++ b/graphql-server/src/utils/commonTypes.ts @@ -1,8 +1,5 @@ import { ArgsType, Field, Int, ObjectType } from "@nestjs/graphql"; -export type RawRow = Record; -export type RawRows = RawRow[]; - @ObjectType() export class ListOffsetRes { @Field(_type => Int, { nullable: true }) diff --git a/graphql-server/src/utils/index.ts b/graphql-server/src/utils/index.ts index 5167bd9b..8c30f1fc 100644 --- a/graphql-server/src/utils/index.ts +++ b/graphql-server/src/utils/index.ts @@ -45,3 +45,17 @@ export function convertToUTCDate(d: Date): Date { utcDate.setUTCSeconds(d.getSeconds()); return utcDate; } + +export async function handleTableNotFound( + q: () => Promise, +): Promise { + try { + const res = await q(); + return res; + } catch (err) { + if (err.message.includes("table not found")) { + return undefined; + } + throw err; + } +} diff --git a/web/components/layouts/DatabaseLayout/Wrapper.tsx b/web/components/layouts/DatabaseLayout/Wrapper.tsx index da1ac6c1..e753a0b6 100644 --- a/web/components/layouts/DatabaseLayout/Wrapper.tsx +++ b/web/components/layouts/DatabaseLayout/Wrapper.tsx @@ -19,7 +19,11 @@ function Inner(props: Props) { if (res.loading) { return ; } - if (res.error && errorMatches(gqlDatabaseNotFoundErr, res.error)) { + if ( + res.error && + (errorMatches(gqlDatabaseNotFoundErr, res.error) || + errorMatches("Data source service not initialized", res.error)) + ) { router.push("/").catch(console.error); return ; } diff --git a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/index.tsx b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/index.tsx index 5faf2513..b42e32a2 100644 --- a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/index.tsx +++ b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/index.tsx @@ -46,7 +46,7 @@ export default function NewBranchForm(props: Props): JSX.Element { if (!data) return; const { href, as } = ref({ ...props.params, - refName: data.createBranch.branchName, + refName: data.createBranch, }); router.push(href, as).catch(console.error); }; diff --git a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/mocks.ts b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/mocks.ts index 9732f744..327a6579 100644 --- a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/mocks.ts +++ b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/mocks.ts @@ -3,10 +3,7 @@ import { branchSelectorMock, mainBranch, } from "@components/CustomFormSelect/mocks"; -import { - BranchForCreateBranchFragment, - CreateBranchDocument, -} from "@gen/graphql-types"; +import { CreateBranchDocument } from "@gen/graphql-types"; import { loremer } from "@lib/loremer"; import { DatabaseParams } from "@lib/params"; import { defaultBranchMock } from "../../../ForDefaultBranch/mocks"; @@ -19,11 +16,6 @@ export const fromRefName: string = fromBranch.branchName; // Using generateFakeBranch since we want the same ownerName and repoName as the branchNames mock export const newBranchName = loremer.resourceNameSegment(); -export const returnedBranch: BranchForCreateBranchFragment = { - __typename: "Branch", - ...dbParams, - branchName: newBranchName, -}; // QUERY MOCKS @@ -33,7 +25,7 @@ export const defaultBranchQueryMock = defaultBranchMock(fromBranch); // MUTATION MOCKS export const createNewBranchData = jest.fn(() => { - return { data: { createBranch: returnedBranch } }; + return { data: { createBranch: newBranchName } }; }); export const createBranchMutationMock: MockedResponse = { diff --git a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/queries.ts b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/queries.ts index d820dec3..0c2e246e 100644 --- a/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/queries.ts +++ b/web/components/pageComponents/DatabasePage/ForBranches/NewBranchPage/NewBranchForm/queries.ts @@ -1,10 +1,6 @@ import { gql } from "@apollo/client"; export const CREATE_BRANCH = gql` - fragment BranchForCreateBranch on Branch { - databaseName - branchName - } mutation CreateBranch( $newBranchName: String! $databaseName: String! @@ -14,8 +10,6 @@ export const CREATE_BRANCH = gql` newBranchName: $newBranchName databaseName: $databaseName fromRefName: $fromRefName - ) { - ...BranchForCreateBranch - } + ) } `; diff --git a/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/queries.ts b/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/queries.ts index a150fbb1..9902c611 100644 --- a/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/queries.ts +++ b/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/queries.ts @@ -12,8 +12,6 @@ export const CREATE_TAG_MUTATION = gql` tagName: $tagName message: $message fromRefName: $fromRefName - ) { - ...TagForList - } + ) } `; diff --git a/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/useCreateTag.ts b/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/useCreateTag.ts index a8f9fdf4..87701668 100644 --- a/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/useCreateTag.ts +++ b/web/components/pageComponents/DatabasePage/ForReleases/NewReleasePage/NewReleaseForm/useCreateTag.ts @@ -1,4 +1,4 @@ -import { CreateTagMutation, useCreateTagMutation } from "@gen/graphql-types"; +import { useCreateTagMutation } from "@gen/graphql-types"; import useMutation from "@hooks/useMutation"; import useSetState from "@hooks/useSetState"; import { ApolloErrorType } from "@lib/errors/types"; @@ -15,7 +15,7 @@ type FormData = { type ReturnType = { // Returns the created tag if successful - createTag: () => Promise; + createTag: () => Promise; creationErr: ApolloErrorType; loading: boolean; canCreateTag: boolean; @@ -40,7 +40,7 @@ export default function useCreateTag(params: DatabaseParams): ReturnType { refetchQueries: refetchTagQueries(params), }); - const createTag = async (): Promise => { + const createTag = async (): Promise => { setLoading(true); try { const { data } = await createTagMutation({ @@ -58,7 +58,7 @@ export default function useCreateTag(params: DatabaseParams): ReturnType { // undefined, }, }); - return data; + return data?.createTag; } finally { setLoading(false); } diff --git a/web/gen/graphql-types.tsx b/web/gen/graphql-types.tsx index 32dd09fd..bd4dbb7b 100644 --- a/web/gen/graphql-types.tsx +++ b/web/gen/graphql-types.tsx @@ -202,9 +202,9 @@ export enum LoadDataModifier { export type Mutation = { __typename?: 'Mutation'; addDatabaseConnection?: Maybe; - createBranch: Branch; + createBranch: Scalars['String']['output']; createDatabase: Scalars['Boolean']['output']; - createTag: Tag; + createTag: Scalars['String']['output']; deleteBranch: Scalars['Boolean']['output']; deleteTag: Scalars['Boolean']['output']; loadDataFile: Scalars['Boolean']['output']; @@ -894,8 +894,6 @@ export type DeleteBranchMutationVariables = Exact<{ export type DeleteBranchMutation = { __typename?: 'Mutation', deleteBranch: boolean }; -export type BranchForCreateBranchFragment = { __typename?: 'Branch', databaseName: string, branchName: string }; - export type CreateBranchMutationVariables = Exact<{ newBranchName: Scalars['String']['input']; databaseName: Scalars['String']['input']; @@ -903,7 +901,7 @@ export type CreateBranchMutationVariables = Exact<{ }>; -export type CreateBranchMutation = { __typename?: 'Mutation', createBranch: { __typename?: 'Branch', databaseName: string, branchName: string } }; +export type CreateBranchMutation = { __typename?: 'Mutation', createBranch: string }; export type CommitForAfterCommitHistoryFragment = { __typename?: 'Commit', _id: string, commitId: string, parents: Array, message: string, committedAt: any, committer: { __typename?: 'DoltWriter', _id: string, displayName: string, username?: string | null } }; @@ -1010,7 +1008,7 @@ export type CreateTagMutationVariables = Exact<{ }>; -export type CreateTagMutation = { __typename?: 'Mutation', createTag: { __typename?: 'Tag', _id: string, tagName: string, message: string, taggedAt: any, commitId: string, tagger: { __typename?: 'DoltWriter', _id: string, username?: string | null, displayName: string, emailAddress: string } } }; +export type CreateTagMutation = { __typename?: 'Mutation', createTag: string }; export type DeleteTagMutationVariables = Exact<{ databaseName: Scalars['String']['input']; @@ -1351,12 +1349,6 @@ export const BranchFragmentDoc = gql` lastCommitter } `; -export const BranchForCreateBranchFragmentDoc = gql` - fragment BranchForCreateBranch on Branch { - databaseName - branchName -} - `; export const CommitForAfterCommitHistoryFragmentDoc = gql` fragment CommitForAfterCommitHistory on Commit { _id @@ -2535,11 +2527,9 @@ export const CreateBranchDocument = gql` newBranchName: $newBranchName databaseName: $databaseName fromRefName: $fromRefName - ) { - ...BranchForCreateBranch - } + ) } - ${BranchForCreateBranchFragmentDoc}`; + `; export type CreateBranchMutationFn = Apollo.MutationFunction; /** @@ -2964,11 +2954,9 @@ export const CreateTagDocument = gql` tagName: $tagName message: $message fromRefName: $fromRefName - ) { - ...TagForList - } + ) } - ${TagForListFragmentDoc}`; + `; export type CreateTagMutationFn = Apollo.MutationFunction; /**