Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [':vtg1'],
types: { ':vtg1': 'INT32' },
fields: [{ name: ':vtg1', type: 'INT32' }],
rows: [{ ':vtg1': 1 }],
size: 1,
statement: 'SELECT 1 from dual;',
Expand Down Expand Up @@ -163,6 +164,41 @@ describe('execute', () => {
expect(got2).toEqual(want)
})

test('it properly returns and decodes a select query with rows as array when designated', async () => {
const mockResponse = {
session: mockSession,
result: {
fields: [{ name: ':vtg1', type: 'INT32' }],
rows: [{ lengths: ['1'], values: 'MQ==' }]
}
}

const want: ExecutedQuery = {
headers: [':vtg1'],
types: { ':vtg1': 'INT32' },
rows: [[1]],
fields: [{ name: ':vtg1', type: 'INT32' }],
size: 1,
statement: 'SELECT 1 from dual;',
time: 1,
rowsAffected: null,
insertId: null
}

mockPool.intercept({ path: EXECUTE_PATH, method: 'POST' }).reply(200, (opts) => {
expect(opts.headers['authorization']).toMatch(/Basic /)
const bodyObj = JSON.parse(opts.body.toString())
expect(bodyObj.session).toEqual(null)
return mockResponse
})

const connection = connect(config)
const got = await connection.execute('SELECT 1 from dual;', null, { as: 'array' })
got.time = 1

expect(got).toEqual(want)
})

test('it properly returns an executed query for a DDL statement', async () => {
const mockResponse = {
session: mockSession,
Expand All @@ -175,6 +211,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [],
types: {},
fields: [],
rows: [],
rowsAffected: null,
insertId: null,
Expand Down Expand Up @@ -204,6 +241,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [],
types: {},
fields: [],
rows: [],
rowsAffected: 1,
insertId: null,
Expand Down Expand Up @@ -234,6 +272,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [],
types: {},
fields: [],
rows: [],
rowsAffected: 1,
insertId: '2',
Expand Down Expand Up @@ -318,6 +357,7 @@ describe('execute', () => {
headers: [':vtg1'],
rows: [{ ':vtg1': 1 }],
types: { ':vtg1': 'INT32' },
fields: [{ name: ':vtg1', type: 'INT32' }],
size: 1,
insertId: null,
rowsAffected: null,
Expand Down Expand Up @@ -350,6 +390,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [':vtg1'],
types: { ':vtg1': 'INT32' },
fields: [{ name: ':vtg1', type: 'INT32' }],
rows: [{ ':vtg1': 1 }],
size: 1,
insertId: null,
Expand Down Expand Up @@ -383,6 +424,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: [':vtg1'],
types: { ':vtg1': 'INT64' },
fields: [{ name: ':vtg1', type: 'INT64' }],
rows: [{ ':vtg1': BigInt(1) }],
size: 1,
insertId: null,
Expand Down Expand Up @@ -419,6 +461,7 @@ describe('execute', () => {
const want: ExecutedQuery = {
headers: ['document'],
types: { document: 'JSON' },
fields: [{ name: 'document', type: 'JSON' }],
rows: [{ document: JSON.parse(document) }],
size: 1,
insertId: null,
Expand Down
62 changes: 50 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export { hex } from './text.js'
import { decode } from './text.js'
import { Version } from './version.js'

type Row = Record<string, any>
type Row = Record<string, any> | any[]

interface VitessError {
message: string
Expand All @@ -28,6 +28,7 @@ export interface ExecutedQuery {
headers: string[]
types: Types
rows: Row[]
fields: Field[]
size: number
statement: string
insertId: string | null
Expand Down Expand Up @@ -97,6 +98,18 @@ interface QueryResult {
rows?: QueryResultRow[]
}

type ExecuteAs = 'array' | 'object'

type ExecuteOptions = {
as?: ExecuteAs
}

type ExecuteArgs = object | any[] | null

const defaultExecuteOptions: ExecuteOptions = {
as: 'object'
}

export class Client {
private config: Config

Expand All @@ -108,8 +121,12 @@ export class Client {
return this.connection().transaction(fn)
}

async execute(query: string, args?: object | any[]): Promise<ExecutedQuery> {
return this.connection().execute(query, args)
async execute(
query: string,
args: ExecuteArgs = null,
options: ExecuteOptions = defaultExecuteOptions
): Promise<ExecutedQuery> {
return this.connection().execute(query, args, options)
}

connection(): Connection {
Expand All @@ -126,8 +143,12 @@ class Tx {
this.conn = conn
}

async execute(query: string, args?: object | any[]): Promise<ExecutedQuery> {
return this.conn.execute(query, args)
async execute(
query: string,
args: ExecuteArgs = null,
options: ExecuteOptions = defaultExecuteOptions
): Promise<ExecutedQuery> {
return this.conn.execute(query, args, options)
}
}

Expand Down Expand Up @@ -171,7 +192,11 @@ export class Connection {
await this.createSession()
}

async execute(query: string, args?: any): Promise<ExecutedQuery> {
async execute(
query: string,
args: ExecuteArgs = null,
options: ExecuteOptions = defaultExecuteOptions
): Promise<ExecutedQuery> {
const url = new URL('/psdb.v1alpha1.Database/Execute', `https://${this.config.host}`)

const formatter = this.config.format || format
Expand All @@ -191,15 +216,17 @@ export class Connection {

this.session = session

const rows = result ? parse(result, this.config.cast || cast) : []
const headers = result ? result.fields?.map((f) => f.name) ?? [] : []
const fields = result?.fields ?? []
const rows = result ? parse(result, this.config.cast || cast, options.as || 'object') : []
const headers = fields.map((f) => f.name)

const typeByName = (acc, { name, type }) => ({ ...acc, [name]: type })
const types = result ? result.fields?.reduce<Types>(typeByName, {}) ?? {} : {}
const types = fields.reduce<Types>(typeByName, {})

return {
headers,
types,
fields,
rows,
rowsAffected,
insertId,
Expand Down Expand Up @@ -251,18 +278,29 @@ export function connect(config: Config): Connection {
return new Connection(config)
}

function parseRow(fields: Field[], rawRow: QueryResultRow, cast: Cast): Row {
function parseArrayRow(fields: Field[], rawRow: QueryResultRow, cast: Cast): Row {
const row = decodeRow(rawRow)

return fields.map((field, ix) => {
return cast(field, row[ix])
})
}

function parseObjectRow(fields: Field[], rawRow: QueryResultRow, cast: Cast): Row {
const row = decodeRow(rawRow)

return fields.reduce((acc, field, ix) => {
acc[field.name] = cast(field, row[ix])
return acc
}, {} as Row)
}

function parse(result: QueryResult, cast: Cast): Row[] {
function parse(result: QueryResult, cast: Cast, returnAs: ExecuteAs): Row[] {
const fields = result.fields
const rows = result.rows ?? []
return rows.map((row) => parseRow(fields, row, cast))
return rows.map((row) =>
returnAs === 'array' ? parseArrayRow(fields, row, cast) : parseObjectRow(fields, row, cast)
)
}

function decodeRow(row: QueryResultRow): Array<string | null> {
Expand Down