Skip to content

Commit 7637705

Browse files
committed
Extract to own file
1 parent be2f538 commit 7637705

File tree

4 files changed

+342
-69
lines changed

4 files changed

+342
-69
lines changed

__tests__/cast.test.ts

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import { cast } from '../src/cast'
2+
3+
describe('cast', () => {
4+
test('casts NULL values', () => {
5+
expect(
6+
cast(
7+
{
8+
name: 'email',
9+
type: 'VARCHAR',
10+
table: 'users',
11+
orgTable: 'users',
12+
database: 'database-js',
13+
orgName: 'email',
14+
columnLength: 1020,
15+
charset: 255
16+
},
17+
null
18+
)
19+
).toEqual(null)
20+
})
21+
22+
test('casts INT64, UINT64 values', () => {
23+
expect(
24+
cast(
25+
{
26+
name: 'id',
27+
type: 'UINT64',
28+
table: 'users',
29+
orgTable: 'users',
30+
database: 'database-js',
31+
orgName: 'id',
32+
columnLength: 20,
33+
charset: 63,
34+
flags: 49699
35+
},
36+
'1'
37+
)
38+
).toEqual('1')
39+
})
40+
41+
test('casts DECIMAL values', () => {
42+
expect(
43+
cast(
44+
{
45+
name: 'decimal',
46+
type: 'DECIMAL',
47+
table: 'decimals',
48+
orgTable: 'decimals',
49+
database: 'blob',
50+
orgName: 'decimal',
51+
columnLength: 12,
52+
charset: 63,
53+
decimals: 2,
54+
flags: 32768
55+
},
56+
'5.4'
57+
)
58+
).toEqual('5.4')
59+
})
60+
61+
test('casts DATETIME, DATE, TIMESTAMP, TIME values', () => {
62+
expect(
63+
cast(
64+
{
65+
name: 'created_at',
66+
type: 'DATETIME',
67+
table: 'users',
68+
orgTable: 'users',
69+
database: 'database-js',
70+
orgName: 'created_at',
71+
columnLength: 19,
72+
charset: 63,
73+
flags: 128
74+
},
75+
'2024-01-01 00:00:00'
76+
)
77+
).toEqual('2024-01-01 00:00:00')
78+
})
79+
80+
test('casts JSON values', () => {
81+
expect(
82+
cast(
83+
{
84+
name: 'metadata',
85+
type: 'JSON',
86+
table: 'users',
87+
orgTable: 'users',
88+
database: 'database-js',
89+
orgName: 'metadata',
90+
columnLength: 4294967295,
91+
charset: 63,
92+
flags: 144
93+
},
94+
'{ "color": "blue" }'
95+
)
96+
).toStrictEqual({ color: 'blue' })
97+
})
98+
99+
test('casts INT8, UINT8, INT16, UINT16, INT24, UINT24, INT32, UINT32, INT64, UINT64, YEAR values', () => {
100+
expect(
101+
cast(
102+
{
103+
name: 'verified',
104+
type: 'INT8',
105+
table: 'users',
106+
orgTable: 'users',
107+
database: 'database-js',
108+
orgName: 'verified',
109+
columnLength: 1,
110+
charset: 63,
111+
flags: 32768
112+
},
113+
'1'
114+
)
115+
).toEqual(1)
116+
expect(
117+
cast(
118+
{
119+
name: 'age',
120+
type: 'INT32',
121+
table: 'users',
122+
orgTable: 'users',
123+
database: 'database-js',
124+
orgName: 'age',
125+
columnLength: 11,
126+
charset: 63,
127+
flags: 32768
128+
},
129+
'21'
130+
)
131+
).toEqual(21)
132+
})
133+
134+
test('casts FLOAT32, FLOAT64 values', () => {
135+
expect(
136+
cast(
137+
{
138+
name: 'float',
139+
type: 'FLOAT32',
140+
table: 'decimals',
141+
orgTable: 'decimals',
142+
database: 'blob',
143+
orgName: 'float',
144+
columnLength: 12,
145+
charset: 63,
146+
decimals: 31,
147+
flags: 32768
148+
},
149+
'20.4'
150+
)
151+
).toEqual(20.4)
152+
expect(
153+
cast(
154+
{
155+
name: 'double',
156+
type: 'FLOAT64',
157+
table: 'decimals',
158+
orgTable: 'decimals',
159+
database: 'blob',
160+
orgName: 'double',
161+
columnLength: 22,
162+
charset: 63,
163+
decimals: 31,
164+
flags: 32768
165+
},
166+
'101.4'
167+
)
168+
).toEqual(101.4)
169+
})
170+
171+
test('casts VARCHAR values', () => {
172+
expect(
173+
cast(
174+
{
175+
name: 'email',
176+
type: 'VARCHAR',
177+
table: 'users',
178+
orgTable: 'users',
179+
database: 'database-js',
180+
orgName: 'email',
181+
columnLength: 1020,
182+
charset: 255
183+
},
184+
185+
)
186+
).toEqual('[email protected]')
187+
})
188+
189+
test('casts BLOB, BIT, GEOMETRY, BINARY, VARBINARY values', () => {
190+
/** See e2e tests for more complete assertions */
191+
192+
expect(
193+
cast(
194+
{
195+
name: 'bytes',
196+
type: 'BLOB',
197+
table: 'binary_test',
198+
orgTable: 'binary_test',
199+
database: 'database-js',
200+
orgName: 'bytes',
201+
columnLength: 4294967295,
202+
charset: 63,
203+
flags: 4241
204+
},
205+
'\u0001\u0002\u0003'
206+
)
207+
).toEqual(new Uint8Array([1, 2, 3]))
208+
})
209+
210+
test('casts BINARY, VARBINARY string values', () => {
211+
/** See e2e tests for more complete assertions */
212+
213+
expect(
214+
cast(
215+
{
216+
name: 'Index_type',
217+
type: 'VARBINARY',
218+
table: 'SHOW_STATISTICS',
219+
orgName: 'Index_type',
220+
columnLength: 44,
221+
charset: 255,
222+
flags: 129
223+
},
224+
'BTREE'
225+
)
226+
).toEqual('BTREE')
227+
expect(
228+
cast(
229+
{
230+
name: 'Tables_in_users',
231+
type: 'VARBINARY',
232+
table: 'TABLES',
233+
orgTable: 'tables',
234+
orgName: 'Tables_in_users',
235+
columnLength: 256,
236+
charset: 255,
237+
flags: 4225
238+
},
239+
'users'
240+
)
241+
).toEqual('users')
242+
})
243+
})

__tests__/index.test.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -626,31 +626,6 @@ describe('hex', () => {
626626
})
627627
})
628628

629-
describe('cast', () => {
630-
test('casts int to number', () => {
631-
expect(cast({ name: 'test', type: 'INT8' }, '12')).toEqual(12)
632-
})
633-
634-
test('casts float to number', () => {
635-
expect(cast({ name: 'test', type: 'FLOAT32' }, '2.32')).toEqual(2.32)
636-
expect(cast({ name: 'test', type: 'FLOAT64' }, '2.32')).toEqual(2.32)
637-
})
638-
639-
test('casts binary data to array of 8-bit unsigned integers', () => {
640-
expect(cast({ name: 'test', type: 'BLOB', charset: 63 }, '')).toEqual(new Uint8Array([]))
641-
expect(cast({ name: 'test', type: 'BLOB', charset: 63 }, 'Å')).toEqual(new Uint8Array([197]))
642-
expect(cast({ name: 'test', type: 'VARBINARY', charset: 63 }, 'Å')).toEqual(new Uint8Array([197]))
643-
})
644-
645-
test('casts binary text data to text', () => {
646-
expect(cast({ name: 'test', type: 'VARBINARY', charset: 255 }, 'table')).toEqual('table')
647-
})
648-
649-
test('casts JSON string to JSON object', () => {
650-
expect(cast({ name: 'test', type: 'JSON' }, '{ "foo": "bar" }')).toStrictEqual({ foo: 'bar' })
651-
})
652-
})
653-
654629
describe('parse e2e', () => {
655630
test('golden (testdb.json)', async () => {
656631
const mockResponse = {

src/cast.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { Field } from './index.js'
2+
import { decodeUTF8, uint8Array } from './text.js'
3+
4+
/**
5+
* https://github.com/vitessio/vitess/blame/v19.0.3/go/mysql/json/helpers.go#L86-L112
6+
*/
7+
8+
export function cast(field: Field, value: any): any {
9+
if (value == null) {
10+
return value
11+
}
12+
13+
if (isBigInt(field)) {
14+
return value
15+
}
16+
17+
if (isDateOrTime(field)) {
18+
return value
19+
}
20+
21+
if (isDecimal(field)) {
22+
return value
23+
}
24+
25+
if (isJson(field)) {
26+
return JSON.parse(value)
27+
}
28+
29+
if (isIntegral(field)) {
30+
return parseInt(value, 10)
31+
}
32+
33+
if (isFloat(field)) {
34+
return parseFloat(value)
35+
}
36+
37+
if (isBinary(field)) {
38+
return uint8Array(value)
39+
}
40+
41+
return decodeUTF8(value)
42+
}
43+
44+
/**
45+
* These are integral, but we want to leave the `BigInt` casting to the caller.
46+
*
47+
* https://github.com/planetscale/database-js/pull/90
48+
*/
49+
50+
function isBigInt(field: Field) {
51+
return field.type === 'INT64' || field.type === 'UINT64'
52+
}
53+
54+
/**
55+
* https://github.com/vitessio/vitess/blob/v19.0.3/go/sqltypes/type.go#L103-L106
56+
*/
57+
58+
function isDateOrTime(field: Field) {
59+
return field.type === 'DATETIME' || field.type === 'DATE' || field.type === 'TIMESTAMP' || field.type === 'TIME'
60+
}
61+
62+
function isDecimal(field: Field) {
63+
return field.type === 'DECIMAL'
64+
}
65+
66+
function isJson(field: Field) {
67+
return field.type === 'JSON'
68+
}
69+
70+
const INTEGRAL_FIELD_TYPES = ['INT8', 'INT16', 'INT24', 'INT32', 'UINT8', 'UINT16', 'UINT24', 'UINT32', 'YEAR']
71+
72+
function isIntegral(field: Field) {
73+
return INTEGRAL_FIELD_TYPES.includes(field.type)
74+
}
75+
76+
const FLOAT_FIELD_TYPES = ['FLOAT32', 'FLOAT64']
77+
78+
function isFloat(field: Field) {
79+
return FLOAT_FIELD_TYPES.includes(field.type)
80+
}
81+
82+
/**
83+
* https://github.com/vitessio/vitess/blob/v19.0.3/go/mysql/collations/env.go#L190-L198
84+
*/
85+
86+
enum Charsets {
87+
Utf8mb3ID = 33,
88+
Utf8mb4ID = 255,
89+
BinaryID = 63,
90+
Utf8mb4BinID = 46,
91+
Latin1Swedish = 8
92+
}
93+
94+
function isBinary(field: Field) {
95+
return field.charset === Charsets.BinaryID
96+
}

0 commit comments

Comments
 (0)