source: node_modules/pg-protocol/src/serializer.ts@ 6c7cfa6

main
Last change on this file since 6c7cfa6 was 62b2964, checked in by Klimentina Efremova <klimentina08642@…>, 2 weeks ago

Project Handcraft Marketplace

  • Property mode set to 100644
File size: 7.1 KB
Line 
1import { Writer } from './buffer-writer'
2
3const enum code {
4 startup = 0x70,
5 query = 0x51,
6 parse = 0x50,
7 bind = 0x42,
8 execute = 0x45,
9 flush = 0x48,
10 sync = 0x53,
11 end = 0x58,
12 close = 0x43,
13 describe = 0x44,
14 copyFromChunk = 0x64,
15 copyDone = 0x63,
16 copyFail = 0x66,
17}
18
19const writer = new Writer()
20
21const startup = (opts: Record<string, string>): Buffer => {
22 // protocol version
23 writer.addInt16(3).addInt16(0)
24 for (const key of Object.keys(opts)) {
25 writer.addCString(key).addCString(opts[key])
26 }
27
28 writer.addCString('client_encoding').addCString('UTF8')
29
30 const bodyBuffer = writer.addCString('').flush()
31 // this message is sent without a code
32
33 const length = bodyBuffer.length + 4
34
35 return new Writer().addInt32(length).add(bodyBuffer).flush()
36}
37
38const requestSsl = (): Buffer => {
39 const response = Buffer.allocUnsafe(8)
40 response.writeInt32BE(8, 0)
41 response.writeInt32BE(80877103, 4)
42 return response
43}
44
45const password = (password: string): Buffer => {
46 return writer.addCString(password).flush(code.startup)
47}
48
49const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer {
50 // 0x70 = 'p'
51 writer.addCString(mechanism).addInt32PrefixedString(initialResponse)
52
53 return writer.flush(code.startup)
54}
55
56const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer {
57 return writer.addString(additionalData).flush(code.startup)
58}
59
60const query = (text: string): Buffer => {
61 return writer.addCString(text).flush(code.query)
62}
63
64type ParseOpts = {
65 name?: string
66 types?: number[]
67 text: string
68}
69
70const emptyArray: any[] = []
71
72const parse = (query: ParseOpts): Buffer => {
73 // expect something like this:
74 // { name: 'queryName',
75 // text: 'select * from blah',
76 // types: ['int8', 'bool'] }
77
78 // normalize missing query names to allow for null
79 const name = query.name || ''
80 if (name.length > 63) {
81 console.error('Warning! Postgres only supports 63 characters for query names.')
82 console.error('You supplied %s (%s)', name, name.length)
83 console.error('This can cause conflicts and silent errors executing queries')
84 }
85
86 const types = query.types || emptyArray
87
88 const len = types.length
89
90 const buffer = writer
91 .addCString(name) // name of query
92 .addCString(query.text) // actual query text
93 .addInt16(len)
94
95 for (let i = 0; i < len; i++) {
96 buffer.addInt32(types[i])
97 }
98
99 return writer.flush(code.parse)
100}
101
102type ValueMapper = (param: any, index: number) => any
103
104type BindOpts = {
105 portal?: string
106 binary?: boolean
107 statement?: string
108 values?: any[]
109 // optional map from JS value to postgres value per parameter
110 valueMapper?: ValueMapper
111}
112
113const paramWriter = new Writer()
114
115// make this a const enum so typescript will inline the value
116const enum ParamType {
117 STRING = 0,
118 BINARY = 1,
119}
120
121const writeValues = function (values: any[], valueMapper?: ValueMapper): void {
122 for (let i = 0; i < values.length; i++) {
123 const mappedVal = valueMapper ? valueMapper(values[i], i) : values[i]
124 if (mappedVal == null) {
125 // add the param type (string) to the writer
126 writer.addInt16(ParamType.STRING)
127 // write -1 to the param writer to indicate null
128 paramWriter.addInt32(-1)
129 } else if (mappedVal instanceof Buffer) {
130 // add the param type (binary) to the writer
131 writer.addInt16(ParamType.BINARY)
132 // add the buffer to the param writer
133 paramWriter.addInt32(mappedVal.length)
134 paramWriter.add(mappedVal)
135 } else {
136 // add the param type (string) to the writer
137 writer.addInt16(ParamType.STRING)
138 // length prefix + UTF-8 bytes in one pass (Buffer.byteLength computed once)
139 paramWriter.addInt32PrefixedString(mappedVal)
140 }
141 }
142}
143
144const bind = (config: BindOpts = {}): Buffer => {
145 // normalize config
146 const portal = config.portal || ''
147 const statement = config.statement || ''
148 const binary = config.binary || false
149 const values = config.values || emptyArray
150 const len = values.length
151
152 writer.addCString(portal).addCString(statement)
153 writer.addInt16(len)
154
155 try {
156 writeValues(values, config.valueMapper)
157 } catch (err) {
158 writer.clear()
159 paramWriter.clear()
160 throw err
161 }
162
163 writer.addInt16(len)
164 writer.add(paramWriter.flush())
165
166 // all results use the same format code
167 writer.addInt16(1)
168 // format code
169 writer.addInt16(binary ? ParamType.BINARY : ParamType.STRING)
170 return writer.flush(code.bind)
171}
172
173type ExecOpts = {
174 portal?: string
175 rows?: number
176}
177
178const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
179
180const execute = (config?: ExecOpts): Buffer => {
181 // this is the happy path for most queries
182 if (!config || (!config.portal && !config.rows)) {
183 return emptyExecute
184 }
185
186 const portal = config.portal || ''
187 const rows = config.rows || 0
188
189 const portalLength = Buffer.byteLength(portal)
190 const len = 4 + portalLength + 1 + 4
191 // one extra bit for code
192 const buff = Buffer.allocUnsafe(1 + len)
193 buff[0] = code.execute
194 buff.writeInt32BE(len, 1)
195 buff.write(portal, 5, 'utf-8')
196 buff[portalLength + 5] = 0 // null terminate portal cString
197 buff.writeUInt32BE(rows, buff.length - 4)
198 return buff
199}
200
201const cancel = (processID: number, secretKey: number): Buffer => {
202 const buffer = Buffer.allocUnsafe(16)
203 buffer.writeInt32BE(16, 0)
204 buffer.writeInt16BE(1234, 4)
205 buffer.writeInt16BE(5678, 6)
206 buffer.writeInt32BE(processID, 8)
207 buffer.writeInt32BE(secretKey, 12)
208 return buffer
209}
210
211type PortalOpts = {
212 type: 'S' | 'P'
213 name?: string
214}
215
216const cstringMessage = (code: code, string: string): Buffer => {
217 const stringLen = Buffer.byteLength(string)
218 const len = 4 + stringLen + 1
219 // one extra bit for code
220 const buffer = Buffer.allocUnsafe(1 + len)
221 buffer[0] = code
222 buffer.writeInt32BE(len, 1)
223 buffer.write(string, 5, 'utf-8')
224 buffer[len] = 0 // null terminate cString
225 return buffer
226}
227
228const emptyDescribePortal = writer.addCString('P').flush(code.describe)
229const emptyDescribeStatement = writer.addCString('S').flush(code.describe)
230
231const describe = (msg: PortalOpts): Buffer => {
232 return msg.name
233 ? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`)
234 : msg.type === 'P'
235 ? emptyDescribePortal
236 : emptyDescribeStatement
237}
238
239const close = (msg: PortalOpts): Buffer => {
240 const text = `${msg.type}${msg.name || ''}`
241 return cstringMessage(code.close, text)
242}
243
244const copyData = (chunk: Buffer): Buffer => {
245 return writer.add(chunk).flush(code.copyFromChunk)
246}
247
248const copyFail = (message: string): Buffer => {
249 return cstringMessage(code.copyFail, message)
250}
251
252const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04])
253
254const flushBuffer = codeOnlyBuffer(code.flush)
255const syncBuffer = codeOnlyBuffer(code.sync)
256const endBuffer = codeOnlyBuffer(code.end)
257const copyDoneBuffer = codeOnlyBuffer(code.copyDone)
258
259const serialize = {
260 startup,
261 password,
262 requestSsl,
263 sendSASLInitialResponseMessage,
264 sendSCRAMClientFinalMessage,
265 query,
266 parse,
267 bind,
268 execute,
269 describe,
270 close,
271 flush: () => flushBuffer,
272 sync: () => syncBuffer,
273 end: () => endBuffer,
274 copyData,
275 copyDone: () => copyDoneBuffer,
276 copyFail,
277 cancel,
278}
279
280export { serialize }
Note: See TracBrowser for help on using the repository browser.