diff --git a/graphql-server/schema.gql b/graphql-server/schema.gql index c0ae0ea6..7133a8ac 100644 --- a/graphql-server/schema.gql +++ b/graphql-server/schema.gql @@ -249,6 +249,7 @@ type SqlSelect { rows: [Row!]! queryExecutionStatus: QueryExecutionStatus! queryExecutionMessage: String! + warnings: [String!] } enum QueryExecutionStatus { diff --git a/graphql-server/src/queryFactory/index.ts b/graphql-server/src/queryFactory/index.ts index 97cc09a7..f35226bd 100644 --- a/graphql-server/src/queryFactory/index.ts +++ b/graphql-server/src/queryFactory/index.ts @@ -70,7 +70,9 @@ export declare class QueryFactory { getTableRows(args: t.TableMaybeSchemaArgs, page: t.TableRowPagination): t.PR; - getSqlSelect(args: t.RefMaybeSchemaArgs & { queryString: string }): t.PR; + getSqlSelect( + args: t.RefMaybeSchemaArgs & { queryString: string }, + ): Promise<{ rows: t.RawRows; warnings?: string[] }>; getSchemas( args: t.RefMaybeSchemaArgs, diff --git a/graphql-server/src/queryFactory/mysql/index.ts b/graphql-server/src/queryFactory/mysql/index.ts index c08f0db8..235b99ad 100644 --- a/graphql-server/src/queryFactory/mysql/index.ts +++ b/graphql-server/src/queryFactory/mysql/index.ts @@ -142,8 +142,22 @@ export class MySQLQueryFactory ); } - async getSqlSelect(args: t.RefArgs & { queryString: string }): t.PR { - return this.query(args.queryString, [], args.databaseName, args.refName); + async getSqlSelect( + args: t.RefArgs & { queryString: string }, + ): Promise<{ rows: t.RawRows; warnings?: string[] }> { + return this.queryMultiple( + async query => { + const rows = await query(args.queryString, [ + args.databaseName, + args.refName, + ]); + const warningsRes = await query(qh.showWarningsQuery); + const warnings = warningsRes.map(w => w.Message); + return { rows, warnings }; + }, + args.databaseName, + args.refName, + ); } async getSchemas(args: t.DBArgs, type?: SchemaType): Promise { diff --git a/graphql-server/src/queryFactory/mysql/queries.ts b/graphql-server/src/queryFactory/mysql/queries.ts index 82ea66ee..46c500ab 100644 --- a/graphql-server/src/queryFactory/mysql/queries.ts +++ b/graphql-server/src/queryFactory/mysql/queries.ts @@ -20,3 +20,5 @@ export const getTriggersQuery = `SHOW TRIGGERS`; export const getEventsQuery = `SHOW EVENTS`; export const proceduresQuery = `SHOW PROCEDURE STATUS WHERE type = "PROCEDURE" AND db = ?`; + +export const showWarningsQuery = `SHOW WARNINGS`; diff --git a/graphql-server/src/queryFactory/postgres/index.ts b/graphql-server/src/queryFactory/postgres/index.ts index 78b27bf8..695f380c 100644 --- a/graphql-server/src/queryFactory/postgres/index.ts +++ b/graphql-server/src/queryFactory/postgres/index.ts @@ -134,9 +134,10 @@ export class PostgresQueryFactory ); } + // TODO: get warnings for postgres async getSqlSelect( args: t.RefMaybeSchemaArgs & { queryString: string }, - ): t.PR { + ): Promise<{ rows: t.RawRows; warnings?: string[] }> { return this.queryQR( async qr => { if (args.schemaName) { diff --git a/graphql-server/src/sqlSelects/sqlSelect.model.ts b/graphql-server/src/sqlSelects/sqlSelect.model.ts index f2238a99..828fc4f9 100644 --- a/graphql-server/src/sqlSelects/sqlSelect.model.ts +++ b/graphql-server/src/sqlSelects/sqlSelect.model.ts @@ -29,13 +29,17 @@ export class SqlSelect { @Field() queryExecutionMessage: string; + + @Field(_type => [String], { nullable: true }) + warnings?: string[]; } export function fromSqlSelectRow( databaseName: string, refName: string, - doltRows: RawRow | RawRow[], + doltRows: RawRow | RawRow[] | undefined, queryString: string, + warnings?: string[], ): SqlSelect { const res = { _id: `/databases/${databaseName}/refs/${refName}/queries/${queryString}`, @@ -46,10 +50,14 @@ export function fromSqlSelectRow( columns: [], queryExecutionStatus: QueryExecutionStatus.Success, queryExecutionMessage: "", + warnings, }; // Some mutation queries do not return an array if (!Array.isArray(doltRows)) { + if (!doltRows) { + return res; + } return { ...res, queryExecutionMessage: `Query OK, ${ diff --git a/graphql-server/src/sqlSelects/sqlSelect.resolver.ts b/graphql-server/src/sqlSelects/sqlSelect.resolver.ts index 287c934d..da88ccf4 100644 --- a/graphql-server/src/sqlSelects/sqlSelect.resolver.ts +++ b/graphql-server/src/sqlSelects/sqlSelect.resolver.ts @@ -22,8 +22,9 @@ export class SqlSelectResolver { return fromSqlSelectRow( args.databaseName, args.refName, - res, + res.rows, args.queryString, + res.warnings, ); } @@ -31,7 +32,7 @@ export class SqlSelectResolver { async sqlSelectForCsvDownload(@Args() args: SqlSelectArgs): Promise { const conn = this.conn.connection(); const res = await conn.getSqlSelect(args); - return toCsvString(res); + return toCsvString(res.rows); } } diff --git a/web/renderer/components/DataTable/Warnings.tsx b/web/renderer/components/DataTable/Warnings.tsx new file mode 100644 index 00000000..c29d06bc --- /dev/null +++ b/web/renderer/components/DataTable/Warnings.tsx @@ -0,0 +1,22 @@ +import { IoWarningOutline } from "@react-icons/all-files/io5/IoWarningOutline"; +import css from "./index.module.css"; + +type Props = { + warnings: string[]; +}; + +export default function Warnings({ warnings }: Props) { + const maxNumWarnings = 5; + const warningsToShow = warnings.slice(0, maxNumWarnings); + + return ( +
+ {warningsToShow.map(warning => ( +
+ +

{warning}

+
+ ))} +
+ ); +} diff --git a/web/renderer/components/DataTable/index.module.css b/web/renderer/components/DataTable/index.module.css index 323bbecb..6394a8af 100644 --- a/web/renderer/components/DataTable/index.module.css +++ b/web/renderer/components/DataTable/index.module.css @@ -7,7 +7,7 @@ } .top { - @apply flex justify-start items-center; + @apply flex justify-start flex-col; } .bottom { @@ -28,3 +28,16 @@ .colsButton { @apply ml-4 h-8; } + +.warning { + @apply flex items-center mb-3 mx-6 text-base; + @screen md { + @apply mx-5; + } + svg { + @apply mr-2 text-coral-400 h-5 w-5 flex-shrink-0; + } + p { + @apply text-coral-400; + } +} diff --git a/web/renderer/components/DataTable/index.tsx b/web/renderer/components/DataTable/index.tsx index f545c910..6b888044 100644 --- a/web/renderer/components/DataTable/index.tsx +++ b/web/renderer/components/DataTable/index.tsx @@ -14,6 +14,7 @@ import AddRowsButton from "./AddRowsButton"; import ShowAllColumns from "./ShowAllColumns"; import Table from "./Table"; import css from "./index.module.css"; +import Warnings from "./Warnings"; type Props = { hasMore?: boolean; @@ -23,6 +24,7 @@ type Props = { message?: ReactNode | null; params: RefParams & { tableName?: Maybe; q: string }; error?: ApolloError; + warnings?: Maybe; }; export function Inner({ columns, rows, message = null, ...props }: Props) { @@ -31,6 +33,7 @@ export function Inner({ columns, rows, message = null, ...props }: Props) {
{message}
+ {props.warnings && }
{rows && columns ? ( diff --git a/web/renderer/components/SqlDataTable/index.tsx b/web/renderer/components/SqlDataTable/index.tsx index 52c22d94..23494fab 100644 --- a/web/renderer/components/SqlDataTable/index.tsx +++ b/web/renderer/components/SqlDataTable/index.tsx @@ -15,6 +15,7 @@ import { } from "@gen/graphql-types"; import { SqlQueryParams } from "@lib/params"; import { useState } from "react"; +import { Maybe } from "@dolthub/web-utils"; import SqlMessage from "./SqlMessage"; import { isReadOnlyDatabaseRevisionError } from "./SqlMessage/utils"; import WorkingDiff from "./WorkingDiff"; @@ -32,6 +33,7 @@ type InnerProps = Props & { rows?: RowForDataTableFragment[]; columns?: ColumnForSqlDataTableFragment[]; client: ApolloClient; + warnings?: Maybe; }; function Inner(props: InnerProps) { @@ -46,6 +48,7 @@ function Inner(props: InnerProps) { columns={props.columns} loadMore={async () => {}} message={msg} + warnings={props.warnings} /> {isMut && !isReadOnlyDatabaseRevisionError(props.gqlError) && ( @@ -77,6 +80,7 @@ function Query(props: Props) { columns={data?.sqlSelect.columns} params={props.params} client={client} + warnings={data?.sqlSelect.warnings} /> ); } diff --git a/web/renderer/components/SqlDataTable/queries.ts b/web/renderer/components/SqlDataTable/queries.ts index 8efe50d5..fa0f7de3 100644 --- a/web/renderer/components/SqlDataTable/queries.ts +++ b/web/renderer/components/SqlDataTable/queries.ts @@ -32,6 +32,7 @@ export const SQL_SELECT_QUERY = gql` rows { ...RowForSqlDataTable } + warnings } } `; diff --git a/web/renderer/gen/graphql-types.tsx b/web/renderer/gen/graphql-types.tsx index 6634c25f..944a14e5 100644 --- a/web/renderer/gen/graphql-types.tsx +++ b/web/renderer/gen/graphql-types.tsx @@ -732,6 +732,7 @@ export type SqlSelect = { queryString: Scalars['String']['output']; refName: Scalars['String']['output']; rows: Array; + warnings?: Maybe>; }; export type Status = { @@ -1011,7 +1012,7 @@ export type SqlSelectForSqlDataTableQueryVariables = Exact<{ }>; -export type SqlSelectForSqlDataTableQuery = { __typename?: 'Query', sqlSelect: { __typename?: 'SqlSelect', queryExecutionStatus: QueryExecutionStatus, queryExecutionMessage: string, columns: Array<{ __typename?: 'Column', name: string, isPrimaryKey: boolean, type: string, sourceTable?: string | null }>, rows: Array<{ __typename?: 'Row', columnValues: Array<{ __typename?: 'ColumnValue', displayValue: string }> }> } }; +export type SqlSelectForSqlDataTableQuery = { __typename?: 'Query', sqlSelect: { __typename?: 'SqlSelect', queryExecutionStatus: QueryExecutionStatus, queryExecutionMessage: string, warnings?: Array | null, columns: Array<{ __typename?: 'Column', name: string, isPrimaryKey: boolean, type: string, sourceTable?: string | null }>, rows: Array<{ __typename?: 'Row', columnValues: Array<{ __typename?: 'ColumnValue', displayValue: string }> }> } }; export type StatusFragment = { __typename?: 'Status', _id: string, refName: string, tableName: string, staged: boolean, status: string }; @@ -2685,6 +2686,7 @@ export const SqlSelectForSqlDataTableDocument = gql` rows { ...RowForSqlDataTable } + warnings } } ${ColumnForSqlDataTableFragmentDoc}