50 |
7u83 |
1 |
# Pluggable commands
|
|
|
2 |
|
|
|
3 |
Starting from epgsql 4.0.0 it's possible to create custom epgsql commands. The term "command"
|
|
|
4 |
signifies a single `request -> response` sequence.
|
|
|
5 |
Under the hood it might contain many PostgreSQL protocol command requests and responses,
|
|
|
6 |
but from the point of view of an epgsql user, it's a single request that produces a single
|
|
|
7 |
response.
|
|
|
8 |
Examples of such commands are `connect`, `squery`, `equery`, `prepared_query`,
|
|
|
9 |
`parse`/`bind`/`execute` and so on. See [src/commands](src/commands) for a full list and
|
|
|
10 |
examples. Basically, almost all epgsql end-user APIs are implemented as a commands.
|
|
|
11 |
|
|
|
12 |
It is possible to send many commands without waiting for the results of previously sent ones
|
|
|
13 |
(pipelining) by using `epgsqla` or `epgsqli` interfaces.
|
|
|
14 |
|
|
|
15 |
## Possible usecases
|
|
|
16 |
|
|
|
17 |
Why you may want to implement a custom command? Some ideas:
|
|
|
18 |
|
|
|
19 |
* You are not satisfied by performance or functionality of epgsql's built-in commands
|
|
|
20 |
* To create a version of equery with built-in statement cache
|
|
|
21 |
* To create a single-roundtrip equery (currently equery works by combining `parse` and
|
|
|
22 |
`equery` commands)
|
|
|
23 |
* To construct some tricky batching commands, eg, bulk-inserts
|
|
|
24 |
|
|
|
25 |
## This can be done by following steps
|
|
|
26 |
|
|
|
27 |
If you are not familiar with the PostgreSQL wire protocol, please, read at least the
|
|
|
28 |
[Message Flow](https://www.postgresql.org/docs/current/static/protocol-flow.html) and
|
|
|
29 |
[Message Formats](https://www.postgresql.org/docs/current/static/protocol-message-formats.html)
|
|
|
30 |
sections of the PostgreSQL documentation.
|
|
|
31 |
The entire [Frontend/Backend Protocol](https://www.postgresql.org/docs/current/static/protocol.html)
|
|
|
32 |
would be nice to know.
|
|
|
33 |
|
|
|
34 |
### Implement epgsql_command behaviour callback module
|
|
|
35 |
|
|
|
36 |
See [epgsql_command](src/epgsql_command.erl).
|
|
|
37 |
|
|
|
38 |
This module should have the following functions exported:
|
|
|
39 |
|
|
|
40 |
```erlang
|
|
|
41 |
init(any()) -> state().
|
|
|
42 |
```
|
|
|
43 |
|
|
|
44 |
Called only once when the command is received and is about to be executed by the epgsql connection
|
|
|
45 |
process. Command's arguments are passed as the callback's arguments, see `epgsql_sock:sync_command/3` and
|
|
|
46 |
`epgsql_sock:async_command/4`. Should initialize and return command's state that will be
|
|
|
47 |
passed to all subsequent callbacks. No PostgreSQL interactions should be done here.
|
|
|
48 |
|
|
|
49 |
```erlang
|
|
|
50 |
execute(pg_sock(), state()) ->
|
|
|
51 |
{ok, pg_sock(), state()}
|
|
|
52 |
| {stop, Reason :: any(), Response :: any(), pg_sock()}.
|
|
|
53 |
|
|
|
54 |
```
|
|
|
55 |
|
|
|
56 |
Client -> Server packets should be sent from this callback by `epgsql_sock:send_multi/2` or
|
|
|
57 |
`epgsql_sock:send/3`. `epgsql_wire` module is usually used to create wire protocol packets.
|
|
|
58 |
Please note that many packets might be sent at once. See `epgsql_cmd_equery` as an example.
|
|
|
59 |
|
|
|
60 |
This callback might be executed more than once for a single command execution if your command
|
|
|
61 |
requires a response for some of the packets to send next packet (more than one round-trip).
|
|
|
62 |
Since epgsql is asynchronous under the hood, you can't just do blocking `receive`.
|
|
|
63 |
See `handle_message/4 -> {requeue, ...}` and `epgsql_cmd_connect` as an example.
|
|
|
64 |
|
|
|
65 |
`pg_sock()` is an opaque state of a `epgsql_sock` process. There are some APIs to get or
|
|
|
66 |
set some fields on it in `epgsql_sock` module.
|
|
|
67 |
|
|
|
68 |
```erlang
|
|
|
69 |
handle_message(Type :: byte(), Payload :: binary() | query_error(),
|
|
|
70 |
pg_sock(), state()) ->
|
|
|
71 |
{noaction, pg_sock()}
|
|
|
72 |
| {noaction, pg_sock(), state()}
|
|
|
73 |
| {add_row, tuple(), pg_sock(), state()}
|
|
|
74 |
| {add_result, Data :: any(), Notification :: any(), pg_sock(), state()}
|
|
|
75 |
| {finish, Result :: any(), Notification :: any(), pg_sock()}
|
|
|
76 |
| {requeue, pg_sock(), state()}
|
|
|
77 |
| {stop, Reason :: any(), Response :: any(), pg_sock()}
|
|
|
78 |
| {sync_required, Why :: any()}
|
|
|
79 |
| unknown.
|
|
|
80 |
|
|
|
81 |
```
|
|
|
82 |
|
|
|
83 |
Server -> Client packet handling code. Packet `Type` byte is the integer ID of a
|
|
|
84 |
[protocol packet](https://www.postgresql.org/docs/current/static/protocol-message-formats.html), basically
|
|
|
85 |
the 1st byte of a packet. And `Payload` is the remaining bytes of a packet. `epgsql_wire` module
|
|
|
86 |
has some helpers that might help decode the packet payload.
|
|
|
87 |
|
|
|
88 |
In the case when the epgsql connection gets an error packet from the server, it will be decoded and `Payload`
|
|
|
89 |
will be `query_error()` instead of binary.
|
|
|
90 |
|
|
|
91 |
**NEVER** call `epgsql_sock:send/3`/`epgsql_sock:send_multi/2` from this callback! Use
|
|
|
92 |
`requeue` return instead: otherwise you will break pipelining!
|
|
|
93 |
|
|
|
94 |
This callback should return one of the following responses to control command's behaviour:
|
|
|
95 |
|
|
|
96 |
- `{noaction, pg_sock()}` - to do nothing (this usualy means that packet was ignored)
|
|
|
97 |
- `{noaction, pg_sock(), state()}` - do nothing, but update command's state
|
|
|
98 |
- `{add_row, tuple(), pg_sock(), state()}` - add a row to current resultset rows accumulator.
|
|
|
99 |
You may get the current accumulated resultset by `epgsql_sock::get_rows(pg_sock())` (except
|
|
|
100 |
when `epgsqli` interface is used).
|
|
|
101 |
- `{add_result, Result :: any(), Notification :: any(), pg_sock(), state()}` - add a
|
|
|
102 |
new result to the list of results. Usualy all commands have only a single result, except `squery`, when
|
|
|
103 |
multiple SQL queries were passed, separated by a semicolon and `execute_batch`.
|
|
|
104 |
You will usually will just return something like `{ok, epgsql_sock:get_rows(PgSock)}` or an error as a result. `Notification` is used for `epgsqli` interface.
|
|
|
105 |
You may get the current list of accumulated results with `epgsql_sock:get_results(pg_sock())`.
|
|
|
106 |
- `{finish, Results, Notification, pg_sock(), state()}` - returned when command was successfuly
|
|
|
107 |
executed and no more actions needed. `Results` will be returned to a client as a result of command
|
|
|
108 |
execution and the command will be descheduled from epgsql connection process.
|
|
|
109 |
You usually use the result of `epgsql_sock:get_results/1` as a `Results`.
|
|
|
110 |
`Notification` is used for `epgsqli` interface.
|
|
|
111 |
- `{requeue, pg_sock(), state()}` - asks the epgsql process to put this command in the execution queue
|
|
|
112 |
once again (with a new state). This means that the `execute/2` callback will be executed again and
|
|
|
113 |
new packets may be sent from client to server. This way you can implement chatty commands with
|
|
|
114 |
multiple `request -> response` sequences. See `epgsql_cmd_connect` as an example.
|
|
|
115 |
- `{stop, Reason, Response, pg_sock()}` - returned when some unrecoverable error occured and
|
|
|
116 |
you want to terminate epgsql connection process. `Response` will be returned as a command result
|
|
|
117 |
and `Reason` will be process termination reason.
|
|
|
118 |
Please, try to avoid use of this response if possible.
|
|
|
119 |
- `{sync_required, Why}` - returned to finish command execution, flush enqueued but not yet
|
|
|
120 |
executed commands and to set epgsql process to `sync_required` state. In this state it
|
|
|
121 |
will not accept any commands except `epgsql_cmd_sync`.
|
|
|
122 |
This usualy means that multipacket protocol sequence was done out-of-order (eg, `bind` before `parse`),
|
|
|
123 |
so, client and server states are out-of-sync and we need to reset them.
|
|
|
124 |
- `unknown` - command got unexpected packet. Connection process will be terminated with
|
|
|
125 |
`{error, {unexpected_message, Type, Payload, state()}}`. Usualy returned from a
|
|
|
126 |
catch-all last clause.
|
|
|
127 |
|
|
|
128 |
### Command now can be executed
|
|
|
129 |
|
|
|
130 |
By calling
|
|
|
131 |
|
|
|
132 |
- `epgsql_sock:sync_command(connection(), command(), Args :: any())` for a
|
|
|
133 |
`gen_server:call`-style, synchronous behaviour (`epgsql`-like API)
|
|
|
134 |
- `epgsql_sock:async_command(connection(), cast, command(), Args :: any())` for asynchronous
|
|
|
135 |
behaviour when whole resultset will be delivered as a single erlang message (`epgsqla`-like API)
|
|
|
136 |
- `epgsql_sock:async_command(connection(), incremental, command(), Args :: any())` for
|
|
|
137 |
asynchronous behaviour when **each row** and some status info will be delivered as separate erlang
|
|
|
138 |
messages (`epgsqli`-like API)
|
|
|
139 |
|
|
|
140 |
`command()` is the name of a module, implementing `epgsql_command` behaviour.
|
|
|
141 |
`Args` may be any (eg, SQL query / arguments / options), they will be passed to `init/1` callback as-is.
|
|
|
142 |
|
|
|
143 |
## Tips
|
|
|
144 |
|
|
|
145 |
* If you are implementing your command outside of a epgsql main tree, it might be handy to
|
|
|
146 |
add `do(Conn, Arg1, Arg2...) -> epgsql_sock:sync_command(Conn, ?MODULE, Args).` to
|
|
|
147 |
incapsulate `epgsql_sock` calls and provide end-user API.
|
|
|
148 |
* Don't be afraid of `requeue`. It might make your code more complex, but will make it possible to
|
|
|
149 |
implement complex multistep logic inside of a single command
|
|
|
150 |
* `epgsql_sock` module has some APIs that might be used from within commands. Refer to that module's
|
|
|
151 |
source code. `epgsql_wire` has some helpers to encode/decode wire protocol and data packets.
|
|
|
152 |
* Packet IDs are defined in `include/protocol.hrl`
|
|
|
153 |
* Again, never try to send packets from `handle_message/4` or `init/1` callbacks!
|
|
|
154 |
* Note that any error in callback functions will crash the epgsql connection process!
|