-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #90079 from xinhaoz/backport22.1-85080-88859
- Loading branch information
Showing
4 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2022 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
import { SqlExecutionResponse, sqlResultsAreEmpty } from "./sqlApi"; | ||
|
||
describe("sqlApi", () => { | ||
test("sqlResultsAreEmpty should return true when there are no rows in the response", () => { | ||
const testCases: { | ||
response: SqlExecutionResponse<unknown>; | ||
expected: boolean; | ||
}[] = [ | ||
{ | ||
response: { | ||
num_statements: 1, | ||
execution: { | ||
retries: 0, | ||
txn_results: [ | ||
{ | ||
statement: 1, | ||
tag: "SELECT", | ||
start: "start-date", | ||
end: "end-date", | ||
rows_affected: 0, | ||
rows: [{ hello: "world" }], | ||
}, | ||
], | ||
}, | ||
}, | ||
expected: false, | ||
}, | ||
{ | ||
response: { | ||
num_statements: 1, | ||
execution: { | ||
retries: 0, | ||
txn_results: [ | ||
{ | ||
statement: 1, | ||
tag: "SELECT", | ||
start: "start-date", | ||
end: "end-date", | ||
rows_affected: 0, | ||
rows: [], | ||
}, | ||
], | ||
}, | ||
}, | ||
expected: true, | ||
}, | ||
{ | ||
response: { | ||
num_statements: 1, | ||
execution: { | ||
retries: 0, | ||
txn_results: [ | ||
{ | ||
statement: 1, | ||
tag: "SELECT", | ||
start: "start-date", | ||
end: "end-date", | ||
rows_affected: 0, | ||
columns: [], | ||
}, | ||
], | ||
}, | ||
}, | ||
expected: true, | ||
}, | ||
{ | ||
response: {}, | ||
expected: true, | ||
}, | ||
]; | ||
|
||
testCases.forEach(tc => { | ||
expect(sqlResultsAreEmpty(tc.response)).toEqual(tc.expected); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright 2022 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
import { fetchDataJSON } from "./fetchData"; | ||
|
||
export type SqlExecutionRequest = { | ||
statements: SqlStatement[]; | ||
execute?: boolean; | ||
timeout?: string; // Default 5s | ||
application_name?: string; // Defaults to '$ api-v2-sql' | ||
database?: string; // Defaults to defaultDb | ||
max_result_size?: number; // Default 10kib | ||
}; | ||
|
||
export type SqlStatement = { | ||
sql: string; | ||
arguments?: unknown[]; | ||
}; | ||
|
||
export type SqlExecutionResponse<T> = { | ||
num_statements?: number; | ||
execution?: SqlExecutionExecResult<T>; | ||
error?: SqlExecutionErrorMessage; | ||
request?: SqlExecutionRequest; | ||
}; | ||
|
||
export interface SqlExecutionExecResult<T> { | ||
retries: number; | ||
txn_results: SqlTxnResult<T>[]; | ||
} | ||
|
||
export type SqlTxnResult<RowType> = { | ||
statement: number; // Statement index from input array | ||
tag: string; // Short stmt tag | ||
start: string; // Start timestamp, encoded as RFC3339 | ||
end: string; // End timestamp, encoded as RFC3339 | ||
rows_affected: number; | ||
columns?: SqlResultColumn[]; | ||
rows?: RowType[]; | ||
error?: Error; | ||
}; | ||
|
||
export type SqlResultColumn = { | ||
name: string; | ||
type: string; | ||
oid: number; | ||
}; | ||
|
||
export type SqlExecutionErrorMessage = { | ||
message: string; | ||
code: string; | ||
severity: string; | ||
source: { file: string; line: number; function: "string" }; | ||
}; | ||
|
||
export const SQL_API_PATH = "/api/v2/sql/"; | ||
|
||
/** | ||
* executeSql executes the provided SQL statements in a single transaction | ||
* over HTTP. | ||
* | ||
* @param req execution request details | ||
*/ | ||
export function executeSql<RowType>( | ||
req: SqlExecutionRequest, | ||
): Promise<SqlExecutionResponse<RowType>> { | ||
return fetchDataJSON<SqlExecutionResponse<RowType>, SqlExecutionRequest>( | ||
SQL_API_PATH, | ||
req, | ||
); | ||
} | ||
|
||
export const INTERNAL_SQL_API_APP = "$ internal-console"; | ||
export const LONG_TIMEOUT = "300s"; | ||
export const LARGE_RESULT_SIZE = 50000; // 50 kib | ||
|
||
/** | ||
* executeInternalSql executes the provided SQL statements with | ||
* the app name set to the internal sql api app name above. | ||
* Note that technically all SQL executed over this API are | ||
* executed as internal, but we make this distinction using the | ||
* function name for when we want to execute user queries in the | ||
* future, where such queries should not have an internal app name. | ||
* | ||
* @param req execution request details | ||
*/ | ||
export function executeInternalSql<RowType>( | ||
req: SqlExecutionRequest, | ||
): Promise<SqlExecutionResponse<RowType>> { | ||
if (!req.application_name) { | ||
req.application_name = INTERNAL_SQL_API_APP; | ||
} else { | ||
req.application_name = `$ internal-${req.application_name}`; | ||
} | ||
|
||
return executeSql(req); | ||
} | ||
|
||
/** | ||
* sqlResultsAreEmpty returns true if the provided result | ||
* does not contain any rows. | ||
* @param result the sql execution result returned by the server | ||
* @returns | ||
*/ | ||
export function sqlResultsAreEmpty( | ||
result: SqlExecutionResponse<unknown>, | ||
): boolean { | ||
return ( | ||
!result.execution?.txn_results?.length || | ||
result.execution.txn_results.every( | ||
txn => !txn.rows || txn.rows.length === 0, | ||
) | ||
); | ||
} |