Skip to content

Commit 3246e60

Browse files
authored
Merge pull request #73 from planetscale/handle-nulls-better
Better support for NULLs
2 parents 716a90b + f168bd6 commit 3246e60

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

__tests__/index.test.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,21 @@ describe('execute', () => {
121121
const mockResponse = {
122122
session: mockSession,
123123
result: {
124-
fields: [{ name: ':vtg1', type: 'INT32' }],
125-
rows: [{ lengths: ['1'], values: 'MQ==' }]
124+
fields: [{ name: ':vtg1', type: 'INT32' }, { name: 'null' }],
125+
rows: [{ lengths: ['1', '-1'], values: 'MQ==' }]
126126
}
127127
}
128128

129129
const want: ExecutedQuery = {
130-
headers: [':vtg1'],
131-
types: { ':vtg1': 'INT32' },
132-
fields: [{ name: ':vtg1', type: 'INT32' }],
133-
rows: [{ ':vtg1': 1 }],
130+
headers: [':vtg1', 'null'],
131+
types: { ':vtg1': 'INT32', null: 'NULL' },
132+
fields: [
133+
{ name: ':vtg1', type: 'INT32' },
134+
{ name: 'null', type: 'NULL' }
135+
],
136+
rows: [{ ':vtg1': 1, null: null }],
134137
size: 1,
135-
statement: 'SELECT 1 from dual;',
138+
statement: 'SELECT 1, null from dual;',
136139
time: 1,
137140
rowsAffected: null,
138141
insertId: null
@@ -146,7 +149,54 @@ describe('execute', () => {
146149
})
147150

148151
const connection = connect(config)
149-
const got = await connection.execute('SELECT 1 from dual;')
152+
const got = await connection.execute('SELECT 1, null from dual;')
153+
got.time = 1
154+
155+
expect(got).toEqual(want)
156+
157+
mockPool.intercept({ path: EXECUTE_PATH, method: 'POST' }).reply(200, (opts) => {
158+
expect(opts.headers['authorization']).toMatch(/Basic /)
159+
const bodyObj = JSON.parse(opts.body.toString())
160+
expect(bodyObj.session).toEqual(mockSession)
161+
return mockResponse
162+
})
163+
164+
const got2 = await connection.execute('SELECT 1, null from dual;')
165+
got2.time = 1
166+
167+
expect(got2).toEqual(want)
168+
})
169+
170+
test('it properly returns and decodes a select query (select null)', async () => {
171+
const mockResponse = {
172+
session: mockSession,
173+
result: {
174+
fields: [{ name: 'null' }],
175+
rows: [{ lengths: ['-1'] }]
176+
}
177+
}
178+
179+
const want: ExecutedQuery = {
180+
headers: ['null'],
181+
types: { null: 'NULL' },
182+
fields: [{ name: 'null', type: 'NULL' }],
183+
rows: [{ null: null }],
184+
size: 1,
185+
statement: 'SELECT null',
186+
time: 1,
187+
rowsAffected: null,
188+
insertId: null
189+
}
190+
191+
mockPool.intercept({ path: EXECUTE_PATH, method: 'POST' }).reply(200, (opts) => {
192+
expect(opts.headers['authorization']).toMatch(/Basic /)
193+
const bodyObj = JSON.parse(opts.body.toString())
194+
expect(bodyObj.session).toEqual(null)
195+
return mockResponse
196+
})
197+
198+
const connection = connect(config)
199+
const got = await connection.execute('SELECT null')
150200
got.time = 1
151201

152202
expect(got).toEqual(want)
@@ -158,7 +208,7 @@ describe('execute', () => {
158208
return mockResponse
159209
})
160210

161-
const got2 = await connection.execute('SELECT 1 from dual;')
211+
const got2 = await connection.execute('SELECT null')
162212
got2.time = 1
163213

164214
expect(got2).toEqual(want)

src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface Config {
6464

6565
interface QueryResultRow {
6666
lengths: string[]
67-
values: string
67+
values?: string
6868
}
6969

7070
export interface Field {
@@ -217,6 +217,15 @@ export class Connection {
217217
this.session = session
218218

219219
const fields = result?.fields ?? []
220+
// ensure each field has a type assigned,
221+
// the only case it would be omitted is in the case of
222+
// NULL due to the protojson spec. NULL in our enum
223+
// is 0, and empty fields are omitted from the JSON response,
224+
// so we should backfill an expected type.
225+
for (const field of fields) {
226+
field.type ||= 'NULL'
227+
}
228+
220229
const rows = result ? parse(result, this.config.cast || cast, options.as || 'object') : []
221230
const headers = fields.map((f) => f.name)
222231

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

306315
function decodeRow(row: QueryResultRow): Array<string | null> {
307-
const values = atob(row.values)
316+
const values = row.values ? atob(row.values) : ''
308317
let offset = 0
309318
return row.lengths.map((size) => {
310319
const width = parseInt(size, 10)

0 commit comments

Comments
 (0)