PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pgsql/src/pgsql_proto.erl

https://github.com/babo/jungerl
Erlang | 603 lines | 470 code | 53 blank | 80 comment | 0 complexity | 40e2b04968fa40e2944e5d89f19239a4 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, AGPL-1.0
  1. %%% File : pgsql_proto.erl
  2. %%% Author : Christian Sunesson <chrisu@kth.se>
  3. %%% Description : PostgreSQL protocol driver
  4. %%% Created : 9 May 2005
  5. %%% This is the protocol handling part of the PostgreSQL driver, it turns packages into
  6. %%% erlang term messages and back.
  7. -module(pgsql_proto).
  8. %% TODO:
  9. %% When factorizing make clear distinction between message and packet.
  10. %% Packet == binary on-wire representation
  11. %% Message = parsed Packet as erlang terms.
  12. %%% Version 3.0 of the protocol.
  13. %%% Supported in postgres from version 7.4
  14. -define(PROTOCOL_MAJOR, 3).
  15. -define(PROTOCOL_MINOR, 0).
  16. %%% PostgreSQL protocol message codes
  17. -define(PG_BACKEND_KEY_DATA, $K).
  18. -define(PG_PARAMETER_STATUS, $S).
  19. -define(PG_ERROR_MESSAGE, $E).
  20. -define(PG_NOTICE_RESPONSE, $N).
  21. -define(PG_EMPTY_RESPONSE, $I).
  22. -define(PG_ROW_DESCRIPTION, $T).
  23. -define(PG_DATA_ROW, $D).
  24. -define(PG_READY_FOR_QUERY, $Z).
  25. -define(PG_AUTHENTICATE, $R).
  26. -define(PG_BIND, $B).
  27. -define(PG_PARSE, $P).
  28. -define(PG_COMMAND_COMPLETE, $C).
  29. -define(PG_PARSE_COMPLETE, $1).
  30. -define(PG_BIND_COMPLETE, $2).
  31. -define(PG_CLOSE_COMPLETE, $3).
  32. -define(PG_PORTAL_SUSPENDED, $s).
  33. -define(PG_NO_DATA, $n).
  34. -export([init/2, idle/2]).
  35. -export([run/1]).
  36. %% For protocol unwrapping, pgsql_tcp for example.
  37. -export([decode_packet/2]).
  38. -export([encode_message/2]).
  39. -export([encode/2]).
  40. -import(pgsql_util, [option/2]).
  41. -import(pgsql_util, [socket/1]).
  42. -import(pgsql_util, [send/2, send_int/2, send_msg/3]).
  43. -import(pgsql_util, [recv_msg/2, recv_msg/1, recv_byte/2, recv_byte/1]).
  44. -import(pgsql_util, [string/1, make_pair/2, split_pair/1]).
  45. -import(pgsql_util, [count_string/1, to_string/1]).
  46. -import(pgsql_util, [coldescs/2, datacoldescs/3]).
  47. deliver(Message) ->
  48. DriverPid = get(driver),
  49. DriverPid ! Message.
  50. run(Options) ->
  51. Db = spawn_link(pgsql_proto, init,
  52. [self(), Options]),
  53. {ok, Db}.
  54. init(DriverPid, Options) ->
  55. put(options, Options), % connection setup options
  56. put(driver, DriverPid), % driver's process id
  57. %%io:format("Init~n", []),
  58. Host = option(host, "localhost"),
  59. Port = option(port, 5432),
  60. case socket({tcp, Host, Port}) of
  61. {ok, Sock } ->
  62. connect(Sock);
  63. Error ->
  64. Reason = {init, Error},
  65. DriverPid ! {pgsql_error, Reason},
  66. exit(Reason)
  67. end.
  68. connect(Sock) ->
  69. %%io:format("Connect~n", []),
  70. %% Connection settings for database-login.
  71. UserName = option(user, "cos"),
  72. DatabaseName = option(database, "template1"),
  73. %% Make protocol startup packet.
  74. Version = <<?PROTOCOL_MAJOR:16/integer, ?PROTOCOL_MINOR:16/integer>>,
  75. User = make_pair(user, UserName),
  76. Database = make_pair(database, DatabaseName),
  77. StartupPacket = <<Version/binary,
  78. User/binary,
  79. Database/binary,
  80. 0>>,
  81. %% Backend will continue with authentication after the startup packet
  82. PacketSize = 4 + size(StartupPacket),
  83. ok = gen_tcp:send(Sock, <<PacketSize:32/integer, StartupPacket/binary>>),
  84. authenticate(Sock).
  85. authenticate(Sock) ->
  86. %% Await authentication request from backend.
  87. {ok, Code, Packet} = recv_msg(Sock, 5000),
  88. {ok, Value} = decode_packet(Code, Packet),
  89. case Value of
  90. %% Error response
  91. {error_message, Message} ->
  92. exit({authentication, Message});
  93. {authenticate, {AuthMethod, Salt}} ->
  94. case AuthMethod of
  95. 0 -> % Auth ok
  96. setup(Sock, []);
  97. 1 -> % Kerberos 4
  98. exit({nyi, auth_kerberos4});
  99. 2 -> % Kerberos 5
  100. exit({nyi, auth_kerberos5});
  101. 3 -> % Plaintext password
  102. Password = option(password, ""),
  103. EncodedPass = encode_message(pass_plain, Password),
  104. ok = send(Sock, EncodedPass),
  105. authenticate(Sock);
  106. 4 -> % Hashed password
  107. exit({nyi, auth_crypt});
  108. 5 -> % MD5 password
  109. Password = option(password, ""),
  110. User = option(user, ""),
  111. EncodedPass = encode_message(pass_md5, {User, Password, Salt}),
  112. ok = send(Sock, EncodedPass),
  113. authenticate(Sock);
  114. _ ->
  115. exit({authentication, {unknown, AuthMethod}})
  116. end;
  117. %% Unknown message received
  118. Any ->
  119. exit({protocol_error, Any})
  120. end.
  121. setup(Sock, Params) ->
  122. %% Receive startup messages until ReadyForQuery
  123. {ok, Code, Package} = recv_msg(Sock, 5000),
  124. {ok, Pair} = decode_packet(Code, Package),
  125. case Pair of
  126. %% BackendKeyData, necessary for issuing cancel requests
  127. {backend_key_data, {Pid, Secret}} ->
  128. Params1 = [{secret, {Pid, Secret}}|Params],
  129. setup(Sock, Params1);
  130. %% ParameterStatus, a key-value pair.
  131. {parameter_status, {Key, Value}} ->
  132. Params1 = [{{parameter, Key}, Value}|Params],
  133. setup(Sock, Params1);
  134. %% Error message, with a sequence of <<Code:8/integer, String, 0>>
  135. %% of error descriptions. Code==0 terminates the Reason.
  136. {error_message, Message} ->
  137. gen_tcp:close(Sock),
  138. exit({error_response, Message});
  139. %% Notice Response, with a sequence of <<Code:8/integer, String,0>>
  140. %% identified fields. Code==0 terminates the Notice.
  141. {notice_response, Notice} ->
  142. deliver({pgsql_notice, Notice}),
  143. setup(Sock, Params);
  144. %% Ready for Query, backend is ready for a new query cycle
  145. {ready_for_query, Status} ->
  146. deliver({pgsql_params, Params}),
  147. deliver(pgsql_connected),
  148. put(params, Params),
  149. connected(Sock);
  150. Any ->
  151. deliver({unknown_setup, Any}),
  152. connected(Sock)
  153. end.
  154. %% Connected state. Can now start to push messages
  155. %% between frontend and backend. But first some setup.
  156. connected(Sock) ->
  157. DriverPid = get(driver),
  158. %% Protocol unwrapping process. Factored out to make future
  159. %% SSL and unix domain support easier. Store process under
  160. %% 'socket' in the process dictionary.
  161. begin
  162. Unwrapper = spawn_link(pgsql_tcp, loop0, [Sock, self()]),
  163. ok = gen_tcp:controlling_process(Sock, Unwrapper),
  164. put(socket, Unwrapper)
  165. end,
  166. %% Ready to start marshalling between frontend and backend.
  167. idle(Sock, DriverPid).
  168. %% In the idle state we should only be receiving requests from the
  169. %% frontend. Async backend messages should be forwarded to responsible
  170. %% process.
  171. idle(Sock, Pid) ->
  172. receive
  173. %% Unexpected idle messages. No request should continue to the
  174. %% idle state before a ready-for-query has been received.
  175. {message, Message} ->
  176. io:format("Unexpected message when idle: ~p~n", [Message]),
  177. idle(Sock, Pid);
  178. %% Socket closed or socket error messages.
  179. {socket, Sock, Condition} ->
  180. exit({socket, Condition});
  181. %% Close connection
  182. {terminate, Ref, Pid} ->
  183. Packet = encode_message(terminate, []),
  184. ok = send(Sock, Packet),
  185. gen_tcp:close(Sock),
  186. Pid ! {pgsql, Ref, terminated},
  187. unlink(Pid),
  188. exit(terminating);
  189. %% Simple query
  190. {squery, Ref, Pid, Query} ->
  191. Packet = encode_message(squery, Query),
  192. ok = send(Sock, Packet),
  193. {ok, Result} = process_squery([]),
  194. case lists:keymember(error, 1, Result) of
  195. true ->
  196. RBPacket = encode_message(squery, "ROLLBACK"),
  197. ok = send(Sock, RBPacket),
  198. {ok, RBResult} = process_squery([]);
  199. _ ->
  200. ok
  201. end,
  202. Pid ! {pgsql, Ref, Result},
  203. idle(Sock, Pid);
  204. %% Extended query
  205. %% simplistic version using the unnammed prepared statement and portal.
  206. {equery, Ref, Pid, {Query, Params}} ->
  207. ParseP = encode_message(parse, {"", Query, []}),
  208. BindP = encode_message(bind, {"", "", Params, [text]}),
  209. DescribeP = encode_message(describe, {portal, ""}),
  210. ExecuteP = encode_message(execute, {"", 0}),
  211. SyncP = encode_message(sync, []),
  212. ok = send(Sock, [ParseP, BindP, DescribeP, ExecuteP, SyncP]),
  213. {ok, Command, Desc, Status, Logs} = process_equery([]),
  214. Pid ! {pgsql, Ref, {Command, Status, Desc, Logs}},
  215. idle(Sock, Pid);
  216. %% Prepare a statement, so it can be used for queries later on.
  217. {prepare, Ref, Pid, {Name, Query}} ->
  218. send_message(Sock, parse, {Name, Query, []}),
  219. send_message(Sock, describe, {prepared_statement, Name}),
  220. send_message(Sock, sync, []),
  221. {ok, State, ParamDesc, ResultDesc} = process_prepare({[], []}),
  222. OidMap = get(oidmap),
  223. ParamTypes =
  224. lists:map(fun (Oid) -> dict:fetch(Oid, OidMap) end, ParamDesc),
  225. ResultNameTypes = lists:map(fun ({ColName, _Format, _ColNo, Oid, _, _, _}) ->
  226. {ColName, dict:fetch(Oid, OidMap)}
  227. end,
  228. ResultDesc),
  229. Pid ! {pgsql, Ref, {prepared, State, ParamTypes, ResultNameTypes}},
  230. idle(Sock, Pid);
  231. %% Close a prepared statement.
  232. {unprepare, Ref, Pid, Name} ->
  233. send_message(Sock, close, {prepared_statement, Name}),
  234. send_message(Sock, sync, []),
  235. {ok, _Status} = process_unprepare(),
  236. Pid ! {pgsql, Ref, unprepared},
  237. idle(Sock, Pid);
  238. %% Execute a prepared statement
  239. {execute, Ref, Pid, {Name, Params}} ->
  240. %%io:format("execute: ~p ~p ~n", [Name, Params]),
  241. begin % Issue first requests for the prepared statement.
  242. BindP = encode_message(bind, {"", Name, Params, [text]}),
  243. DescribeP = encode_message(describe, {portal, ""}),
  244. ExecuteP = encode_message(execute, {"", 0}),
  245. FlushP = encode_message(flush, []),
  246. ok = send(Sock, [BindP, DescribeP, ExecuteP, FlushP])
  247. end,
  248. receive
  249. {pgsql, {bind_complete, _}} -> % Bind reply first.
  250. %% Collect response to describe message,
  251. %% which gives a hint of the rest of the messages.
  252. {ok, Command, Result} = process_execute(Sock, Ref, Pid),
  253. begin % Close portal and end extended query.
  254. CloseP = encode_message(close, {portal, ""}),
  255. SyncP = encode_message(sync, []),
  256. ok = send(Sock, [CloseP, SyncP])
  257. end,
  258. receive
  259. %% Collect response to close message.
  260. {pgsql, {close_complete, _}} ->
  261. receive
  262. %% Collect response to sync message.
  263. {pgsql, {ready_for_query, Status}} ->
  264. %%io:format("execute: ~p ~p ~p~n",
  265. %% [Status, Command, Result]),
  266. Pid ! {pgsql, Ref, {Command, Result}},
  267. idle(Sock, Pid);
  268. {pgsql, Unknown} ->
  269. exit(Unknown)
  270. end;
  271. {pgsql, Unknown} ->
  272. exit(Unknown)
  273. end;
  274. {pgsql, Unknown} ->
  275. exit(Unknown)
  276. end;
  277. %% More requests to come.
  278. %% .
  279. %% .
  280. %% .
  281. Any ->
  282. exit({unknown_request, Any})
  283. end.
  284. %% In the process_squery state we collect responses until the backend is
  285. %% done processing.
  286. process_squery(Log) ->
  287. receive
  288. {pgsql, {row_description, Cols}} ->
  289. {ok, Types} = pgsql_util:decode_descs(Cols),
  290. {ok, Command, Rows} = process_squery_cols(Types, []),
  291. process_squery([{Command, Types, Rows}|Log]);
  292. {pgsql, {command_complete, Command}} ->
  293. process_squery([Command|Log]);
  294. {pgsql, {ready_for_query, Status}} ->
  295. {ok, lists:reverse(Log)};
  296. {pgsql, {error_message, Error}} ->
  297. process_squery([{error, Error}|Log]);
  298. {pgsql, Any} ->
  299. process_squery(Log)
  300. end.
  301. process_squery_cols(Types, Log) ->
  302. receive
  303. {pgsql, {data_row, Row}} ->
  304. {ok, DecodedRow} = pgsql_util:decode_row(Types, Row),
  305. process_squery_cols(Types, [DecodedRow|Log]);
  306. {pgsql, {command_complete, Command}} ->
  307. {ok, Command, lists:reverse(Log)}
  308. end.
  309. process_equery(Log) ->
  310. receive
  311. %% Consume parse and bind complete messages when waiting for the first
  312. %% first row_description message. What happens if the equery doesnt
  313. %% return a result set?
  314. {pgsql, {parse_complete, _}} ->
  315. process_equery(Log);
  316. {pgsql, {bind_complete, _}} ->
  317. process_equery(Log);
  318. {pgsql, {row_description, Descs}} ->
  319. {ok, Descs1} = pgsql_util:decode_descs(Descs),
  320. process_equery_datarow(Descs1, Log, {undefined, Descs, undefined});
  321. {pgsql, Any} ->
  322. process_equery([Any|Log])
  323. end.
  324. process_equery_datarow(Types, Log, Info={Command, Desc, Status}) ->
  325. receive
  326. %%
  327. {pgsql, {command_complete, Command1}} ->
  328. process_equery_datarow(Types, Log, {Command1, Desc, Status});
  329. {pgsql, {ready_for_query, Status1}} ->
  330. {ok, Command, Desc, Status1, lists:reverse(Log)};
  331. {pgsql, {data_row, Row}} ->
  332. {ok, DecodedRow} = pgsql_util:decode_row(Types, Row),
  333. process_equery_datarow(Types, [DecodedRow|Log], Info);
  334. {pgsql, Any} ->
  335. process_equery_datarow(Types, [Any|Log], Info)
  336. end.
  337. process_prepare(Info={ParamDesc, ResultDesc}) ->
  338. receive
  339. {pgsql, {no_data, _}} ->
  340. process_prepare({ParamDesc, []});
  341. {pgsql, {parse_complete, _}} ->
  342. process_prepare(Info);
  343. {pgsql, {parameter_description, Oids}} ->
  344. process_prepare({Oids, ResultDesc});
  345. {pgsql, {row_description, Desc}} ->
  346. process_prepare({ParamDesc, Desc});
  347. {pgsql, {ready_for_query, Status}} ->
  348. {ok, Status, ParamDesc, ResultDesc};
  349. {pgsql, Any} ->
  350. io:format("process_prepare: ~p~n", [Any]),
  351. process_prepare(Info)
  352. end.
  353. process_unprepare() ->
  354. receive
  355. {pgsql, {ready_for_query, Status}} ->
  356. {ok, Status};
  357. {pgsql, {close_complate, []}} ->
  358. process_unprepare();
  359. {pgsql, Any} ->
  360. io:format("process_unprepare: ~p~n", [Any]),
  361. process_unprepare()
  362. end.
  363. process_execute(Sock, Ref, Pid) ->
  364. %% Either the response begins with a no_data or a row_description
  365. %% Needs to return {ok, Status, Result}
  366. %% where Result = {Command, ...}
  367. receive
  368. {pgsql, {no_data, _}} ->
  369. {ok, Command, Result} = process_execute_nodata();
  370. {pgsql, {row_description, Descs}} ->
  371. {ok, Types} = pgsql_util:decode_descs(Descs),
  372. {ok, Command, Result} =
  373. process_execute_resultset(Sock, Ref, Pid, Types, []);
  374. {pgsql, Unknown} ->
  375. exit(Unknown)
  376. end.
  377. process_execute_nodata() ->
  378. receive
  379. {pgsql, {command_complete, Command}} ->
  380. case Command of
  381. "INSERT "++Rest ->
  382. {ok, [{integer, _, _Table},
  383. {integer, _, NRows}], _} = erl_scan:string(Rest),
  384. {ok, 'INSERT', NRows};
  385. "SELECT" ->
  386. {ok, 'SELECT', should_not_happen};
  387. "DELETE "++Rest ->
  388. {ok, [{integer, _, NRows}], _} =
  389. erl_scan:string(Rest),
  390. {ok, 'DELETE', NRows};
  391. Any ->
  392. {ok, nyi, Any}
  393. end;
  394. {pgsql, Unknown} ->
  395. exit(Unknown)
  396. end.
  397. process_execute_resultset(Sock, Ref, Pid, Types, Log) ->
  398. receive
  399. {pgsql, {command_complete, Command}} ->
  400. {ok, list_to_atom(Command), lists:reverse(Log)};
  401. {pgsql, {data_row, Row}} ->
  402. {ok, DecodedRow} = pgsql_util:decode_row(Types, Row),
  403. process_execute_resultset(Sock, Ref, Pid, Types, [DecodedRow|Log]);
  404. {pgsql, {portal_suspended, _}} ->
  405. throw(portal_suspended);
  406. {pgsql, Any} ->
  407. %%process_execute_resultset(Types, [Any|Log])
  408. exit(Any)
  409. end.
  410. %% With a message type Code and the payload Packet apropriate
  411. %% decoding procedure can proceed.
  412. decode_packet(Code, Packet) ->
  413. Ret = fun(CodeName, Values) -> {ok, {CodeName, Values}} end,
  414. case Code of
  415. ?PG_ERROR_MESSAGE ->
  416. Message = pgsql_util:errordesc(Packet),
  417. Ret(error_message, Message);
  418. ?PG_EMPTY_RESPONSE ->
  419. Ret(empty_response, []);
  420. ?PG_ROW_DESCRIPTION ->
  421. <<Columns:16/integer, ColDescs/binary>> = Packet,
  422. Descs = coldescs(ColDescs, []),
  423. Ret(row_description, Descs);
  424. ?PG_READY_FOR_QUERY ->
  425. <<State:8/integer>> = Packet,
  426. case State of
  427. $I ->
  428. Ret(ready_for_query, idle);
  429. $T ->
  430. Ret(ready_for_query, transaction);
  431. $E ->
  432. Ret(ready_for_query, failed_transaction)
  433. end;
  434. ?PG_COMMAND_COMPLETE ->
  435. {Task, _} = to_string(Packet),
  436. Ret(command_complete, Task);
  437. ?PG_DATA_ROW ->
  438. <<NumberCol:16/integer, RowData/binary>> = Packet,
  439. ColData = datacoldescs(NumberCol, RowData, []),
  440. Ret(data_row, ColData);
  441. ?PG_BACKEND_KEY_DATA ->
  442. <<Pid:32/integer, Secret:32/integer>> = Packet,
  443. Ret(backend_key_data, {Pid, Secret});
  444. ?PG_PARAMETER_STATUS ->
  445. {Key, Value} = split_pair(Packet),
  446. Ret(parameter_status, {Key, Value});
  447. ?PG_NOTICE_RESPONSE ->
  448. Ret(notice_response, []);
  449. ?PG_AUTHENTICATE ->
  450. <<AuthMethod:32/integer, Salt/binary>> = Packet,
  451. Ret(authenticate, {AuthMethod, Salt});
  452. ?PG_PARSE_COMPLETE ->
  453. Ret(parse_complete, []);
  454. ?PG_BIND_COMPLETE ->
  455. Ret(bind_complete, []);
  456. ?PG_PORTAL_SUSPENDED ->
  457. Ret(portal_suspended, []);
  458. ?PG_CLOSE_COMPLETE ->
  459. Ret(close_complete, []);
  460. $t ->
  461. <<NParams:16/integer, OidsP/binary>> = Packet,
  462. Oids = pgsql_util:oids(OidsP, []),
  463. Ret(parameter_description, Oids);
  464. ?PG_NO_DATA ->
  465. Ret(no_data, []);
  466. Any ->
  467. Ret(unknown, [Code])
  468. end.
  469. send_message(Sock, Type, Values) ->
  470. %%io:format("send_message:~p~n", [{Type, Values}]),
  471. Packet = encode_message(Type, Values),
  472. ok = send(Sock, Packet).
  473. %% Add header to a message.
  474. encode(Code, Packet) ->
  475. Len = size(Packet) + 4,
  476. <<Code:8/integer, Len:4/integer-unit:8, Packet/binary>>.
  477. %% Encode a message of a given type.
  478. encode_message(pass_plain, Password) ->
  479. Pass = pgsql_util:pass_plain(Password),
  480. encode($p, Pass);
  481. encode_message(pass_md5, {User, Password, Salt}) ->
  482. Pass = pgsql_util:pass_md5(User, Password, Salt),
  483. encode($p, Pass);
  484. encode_message(terminate, _) ->
  485. encode($X, <<>>);
  486. encode_message(squery, Query) -> % squery as in simple query.
  487. encode($Q, string(Query));
  488. encode_message(close, {Object, Name}) ->
  489. Type = case Object of prepared_statement -> $S; portal -> $P end,
  490. String = string(Name),
  491. encode($C, <<Type/integer, String/binary>>);
  492. encode_message(describe, {Object, Name}) ->
  493. ObjectP = case Object of prepared_statement -> $S; portal -> $P end,
  494. NameP = string(Name),
  495. encode($D, <<ObjectP:8/integer, NameP/binary>>);
  496. encode_message(flush, _) ->
  497. encode($H, <<>>);
  498. encode_message(parse, {Name, Query, _Oids}) ->
  499. StringName = string(Name),
  500. StringQuery = string(Query),
  501. encode($P, <<StringName/binary, StringQuery/binary, 0:16/integer>>);
  502. encode_message(bind, Bind={NamePortal, NamePrepared,
  503. Parameters, ResultFormats}) ->
  504. %%io:format("encode bind: ~p~n", [Bind]),
  505. PortalP = string(NamePortal),
  506. PreparedP = string(NamePrepared),
  507. ParamFormatsList = lists:map(
  508. fun (Bin) when is_binary(Bin) -> <<1:16/integer>>;
  509. (Text) -> <<0:16/integer>> end,
  510. Parameters),
  511. ParamFormatsP = erlang:concat_binary(ParamFormatsList),
  512. NParameters = length(Parameters),
  513. ParametersList = lists:map(
  514. fun (null) ->
  515. Minus = -1,
  516. <<Minus:32/integer>>;
  517. (Bin) when is_binary(Bin) ->
  518. Size = size(Bin),
  519. <<Size:32/integer, Bin/binary>>;
  520. (Integer) when is_integer(Integer) ->
  521. List = integer_to_list(Integer),
  522. Bin = list_to_binary(List),
  523. Size = size(Bin),
  524. <<Size:32/integer, Bin/binary>>;
  525. (Text) ->
  526. Bin = list_to_binary(Text),
  527. Size = size(Bin),
  528. <<Size:32/integer, Bin/binary>>
  529. end,
  530. Parameters),
  531. ParametersP = erlang:concat_binary(ParametersList),
  532. NResultFormats = length(ResultFormats),
  533. ResultFormatsList = lists:map(
  534. fun (binary) -> <<1:16/integer>>;
  535. (text) -> <<0:16/integer>> end,
  536. ResultFormats),
  537. ResultFormatsP = erlang:concat_binary(ResultFormatsList),
  538. %%io:format("encode bind: ~p~n", [{PortalP, PreparedP,
  539. %% NParameters, ParamFormatsP,
  540. %% NParameters, ParametersP,
  541. %% NResultFormats, ResultFormatsP}]),
  542. encode($B, <<PortalP/binary, PreparedP/binary,
  543. NParameters:16/integer, ParamFormatsP/binary,
  544. NParameters:16/integer, ParametersP/binary,
  545. NResultFormats:16/integer, ResultFormatsP/binary>>);
  546. encode_message(execute, {Portal, Limit}) ->
  547. String = string(Portal),
  548. encode($E, <<String/binary, Limit:32/integer>>);
  549. encode_message(sync, _) ->
  550. encode($S, <<>>).