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
68 changes: 59 additions & 9 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,21 @@ describe('execute', () => {
const mockResponse = {
session: mockSession,
result: {
fields: [{ name: ':vtg1', type: 'INT32' }],
rows: [{ lengths: ['1'], values: 'MQ==' }]
fields: [{ name: ':vtg1', type: 'INT32' }, { name: 'null' }],
rows: [{ lengths: ['1', '-1'], values: 'MQ==' }]
}
}

const want: ExecutedQuery = {
headers: [':vtg1'],
types: { ':vtg1': 'INT32' },
fields: [{ name: ':vtg1', type: 'INT32' }],
rows: [{ ':vtg1': 1 }],
headers: [':vtg1', 'null'],
types: { ':vtg1': 'INT32', null: 'NULL' },
fields: [
{ name: ':vtg1', type: 'INT32' },
{ name: 'null', type: 'NULL' }
],
rows: [{ ':vtg1': 1, null: null }],
size: 1,
statement: 'SELECT 1 from dual;',
statement: 'SELECT 1, null from dual;',
time: 1,
rowsAffected: null,
insertId: null
Expand All @@ -146,7 +149,54 @@ describe('execute', () => {
})

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

expect(got).toEqual(want)

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(mockSession)
return mockResponse
})

const got2 = await connection.execute('SELECT 1, null from dual;')
got2.time = 1

expect(got2).toEqual(want)
})

test('it properly returns and decodes a select query (select null)', async () => {
const mockResponse = {
session: mockSession,
result: {
fields: [{ name: 'null' }],
rows: [{ lengths: ['-1'] }]
}
}

const want: ExecutedQuery = {
headers: ['null'],
types: { null: 'NULL' },
fields: [{ name: 'null', type: 'NULL' }],
rows: [{ null: null }],
size: 1,
statement: 'SELECT null',
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 null')
got.time = 1

expect(got).toEqual(want)
Expand All @@ -158,7 +208,7 @@ describe('execute', () => {
return mockResponse
})

const got2 = await connection.execute('SELECT 1 from dual;')
const got2 = await connection.execute('SELECT null')
got2.time = 1

expect(got2).toEqual(want)
Expand Down
13 changes: 11 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface Config {

interface QueryResultRow {
lengths: string[]
values: string
values?: string
}

export interface Field {
Expand Down Expand Up @@ -217,6 +217,15 @@ export class Connection {
this.session = session

const fields = result?.fields ?? []
// ensure each field has a type assigned,
// the only case it would be omitted is in the case of
// NULL due to the protojson spec. NULL in our enum
// is 0, and empty fields are omitted from the JSON response,
// so we should backfill an expected type.
for (const field of fields) {
field.type ||= 'NULL'
}

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

Expand Down Expand Up @@ -304,7 +313,7 @@ function parse(result: QueryResult, cast: Cast, returnAs: ExecuteAs): Row[] {
}

function decodeRow(row: QueryResultRow): Array<string | null> {
const values = atob(row.values)
const values = row.values ? atob(row.values) : ''
let offset = 0
return row.lengths.map((size) => {
const width = parseInt(size, 10)
Expand Down