Current section

18 Versions

Jump to

Compare versions

53 files changed
+3578 additions
-1648 deletions
  @@ -69,6 +69,7 @@ see `CHANGES` for full list.
69 69 {ssl_opts, SslOptions :: [ssl:ssl_option()]} | % @see OTP ssl app, ssl_api.hrl
70 70 {timeout, TimeoutMs :: timeout()} | % default: 5000 ms
71 71 {async, Receiver :: pid() | atom()} | % process to receive LISTEN/NOTIFY msgs
72 + {codecs, Codecs :: [{epgsql_codec:codec_mod(), any()}]} |
72 73 {replication, Replication :: string()}. % Pass "database" to connect in replication mode
73 74
74 75 -spec connect(host(), string(), string(), [connect_option()] | map())
  @@ -78,7 +79,7 @@ see `CHANGES` for full list.
78 79 %% `Host' - host to connect to
79 80 %% `Username' - username to connect as, defaults to `$USER'
80 81 %% `Password' - optional password to authenticate with
81 - %% `Opts' - proplist of extra options
82 + %% `Opts' - proplist or map of extra options
82 83 %% returns `{ok, Connection}' otherwise `{error, Reason}'
83 84 connect(Host, Username, Password, Opts) -> ...
84 85 ```
  @@ -139,49 +140,40 @@ squery(Connection, SqlQuery) -> ...
139 140 ```
140 141 examples:
141 142 ```erlang
142 - InsertRes = epgsql:squery(C, "insert into account (name) values ('alice'), ('bob')"),
143 - io:format("~p~n", [InsertRes]),
144 - ```
145 - > ```
146 - {ok,2}
143 + epgsql:squery(C, "insert into account (name) values ('alice'), ('bob')").
144 + > {ok,2}
147 145 ```
148 146
149 147 ```erlang
150 - SelectRes = epgsql:squery(C, "select * from account"),
151 - io:format("~p~n", [SelectRes]).
152 - ```
153 - > ```
154 - {ok,
148 + epgsql:squery(C, "select * from account").
149 + > {ok,
155 150 [{column,<<"id">>,int4,4,-1,0},{column,<<"name">>,text,-1,-1,0}],
156 151 [{<<"1">>,<<"alice">>},{<<"2">>,<<"bob">>}]
157 152 }
158 153 ```
159 154
160 155 ```erlang
161 - InsertReturningRes = epgsql:squery(C,
156 + epgsql:squery(C,
162 157 "insert into account(name)"
163 158 " values ('joe'), (null)"
164 - " returning *"),
165 - io:format("~p~n", [InsertReturningRes]).
166 - ```
167 - > ```
168 - {ok,2,
159 + " returning *").
160 + > {ok,2,
169 161 [{column,<<"id">>,int4,4,-1,0}, {column,<<"name">>,text,-1,-1,0}],
170 162 [{<<"3">>,<<"joe">>},{<<"4">>,null}]
171 163 }
172 164 ```
173 165
174 166 ```erlang
175 - {error, Reason} = epgsql:squery(C, "insert into account values (1, 'bad_pkey')"),
176 - io:format("~p~n", [Reason]).
177 - ```
178 - > ```
179 - {error,
180 - error,
181 - <<"23505">>,
182 - <<"duplicate key value violates unique constraint \"account_pkey\"">>,
183 - [{detail,<<"Key (id)=(1) already exists.">>}]
184 - }
167 + -include_lib("epgsql/include/epgsql.hrl").
168 + epgsql:squery(C, "SELECT * FROM _nowhere_").
169 + > {error,
170 + #error{severity = error,code = <<"42P01">>,
171 + codename = undefined_table,
172 + message = <<"relation \"_nowhere_\" does not exist">>,
173 + extra = [{file,<<"parse_relation.c">>},
174 + {line,<<"1160">>},
175 + {position,<<"15">>},
176 + {routine,<<"parserOpenTable">>}]}}
185 177 ```
186 178
187 179 The simple query protocol returns all columns as binary strings
  @@ -190,7 +182,7 @@ and does not support parameters binding.
190 182 Several queries separated by semicolon can be executed by squery.
191 183
192 184 ```erlang
193 - [{ok, _, [{<<"1">>}]}, {ok, _, [{<<"2">>}]}] = epgsql:squery(C, "select 1; select 2").
185 + [{ok, _, [{<<"1">>}]}, {ok, _, [{<<"2">>}]}] = epgsql:squery(C, "select 1; select 2").
194 186 ```
195 187
196 188 `epgsqla:squery/2` returns result as a single message:
  @@ -246,11 +238,8 @@ the unnamed prepared statement and portal. A `select` statement returns
246 238 an error occurs, all statements result in `{error, #error{}}`.
247 239
248 240 ```erlang
249 - SelectRes = epgsql:equery(C, "select id from account where name = $1", ["alice"]),
250 - io:format("~p~n", [SelectRes]).
251 - ```
252 - > ```
253 - {ok,
241 + epgsql:equery(C, "select id from account where name = $1", ["alice"]),
242 + > {ok,
254 243 [{column,<<"id">>,int4,4,-1,1}],
255 244 [{1}]
256 245 }
  @@ -400,13 +389,17 @@ example:
400 389 - `{C, Ref, done}` - execution of all queries from Batch has finished
401 390
402 391 ## Data Representation
392 +
393 + Data representation may be configured using [pluggable datatype codecs](pluggable_types.md),
394 + so following is just default mapping:
395 +
403 396 PG type | Representation
404 397 --------------|-------------------------------------
405 398 null | `null`
406 399 bool | `true` | `false`
407 400 char | `$A` | `binary`
408 401 intX | `1`
409 - floatX | `1.0`
402 + floatX | `1.0` | `nan` | `minus_infinity` | `plus_infinity`
410 403 date | `{Year, Month, Day}`
411 404 time | `{Hour, Minute, Second.Microsecond}`
412 405 timetz | `{time, Timezone}`
  @@ -420,13 +413,18 @@ PG type | Representation
420 413 record | `{int2, time, text, ...}` (decode only)
421 414 point | `{10.2, 100.12}`
422 415 int4range | `[1,5)`
423 - hstore | `{list({binary(), binary() | null})}`
416 + hstore | `{[ {binary(), binary() \| null} ]}`
424 417 json/jsonb | `<<"{ \"key\": [ 1, 1.0, true, \"string\" ] }">>`
418 + uuid | `<<"123e4567-e89b-12d3-a456-426655440000">>`
419 + inet | `inet:ip_address()`
420 + cidr | `{ip_address(), Mask :: 0..32}`
421 + macaddr(8) | tuple of 6 or 8 `byte()`
422 + geometry | `ewkb:geometry()`
425 423
426 424
427 425 `timestamp` and `timestamptz` parameters can take `erlang:now()` format: `{MegaSeconds, Seconds, MicroSeconds}`
428 426
429 - `int4range` is a range type for ints (bigint not supported yet) that obeys inclusive/exclusive semantics,
427 + `int4range` is a range type for ints that obeys inclusive/exclusive semantics,
430 428 bracket and parentheses respectively. Additionally, infinities are represented by the atoms `minus_infinity`
431 429 and `plus_infinity`
432 430
  @@ -485,6 +483,33 @@ Message formats:
485 483
486 484 ## Utility functions
487 485
486 + ### Transaction helpers
487 +
488 + ```erlang
489 + with_transaction(connection(), fun((connection()) -> Result :: any()), Opts) ->
490 + Result | {rollback, Reason :: any()} when
491 + Opts :: [{reraise, boolean()},
492 + {ensure_committed, boolean()},
493 + {begin_opts, iodata()}] | map().
494 + ```
495 +
496 + Executes a function in a PostgreSQL transaction. It executes `BEGIN` prior to executing the function,
497 + `ROLLBACK` if the function raises an exception and `COMMIT` if the function returns without an error.
498 + If it is successful, it returns the result of the function. The failure case may differ, depending on
499 + the options passed.
500 + Options (proplist or map):
501 + - `reraise` (default `true`): when set to true, the original exception will be re-thrown after rollback,
502 + otherwise `{rollback, ErrorReason}` will be returned
503 + - `ensure_committed` (default `false`): even when the callback returns without exception,
504 + check that transaction was committed by checking the `CommandComplete` status
505 + of the `COMMIT` command. If the transaction was rolled back, the status will be
506 + `rollback` instead of `commit` and an `ensure_committed_failed` error will be generated.
507 + - `begin_opts` (default `""`): append extra options to `BEGIN` command (see
508 + https://www.postgresql.org/docs/current/static/sql-begin.html) as a string by just
509 + appending them to `"BEGIN "` string. Eg `{begin_opts, "ISOLATION LEVEL SERIALIZABLE"}`.
510 + Beware of SQL injection! The value of `begin_opts` is not escaped!
511 +
512 +
488 513 ### Command status
489 514
490 515 `epgsql{a,i}:get_cmd_status(C) -> undefined | atom() | {atom(), integer()}`
  @@ -516,6 +541,13 @@ Parameter's value may change during connection's lifetime.
516 541
517 542 See [streaming.md](streaming.md).
518 543
544 + ## Pluggable commands
545 +
546 + See [pluggable_commands.md](pluggable_commands.md)
547 +
548 + ## Pluggable datatype codecs
549 +
550 + See [pluggable_types.md](pluggable_types.md)
519 551
520 552 ## Mailing list
  @@ -1,18 +1,48 @@
1 1 {<<"name">>,<<"epgsql">>}.
2 - {<<"version">>,<<"3.4.0">>}.
2 + {<<"version">>,<<"4.0.0">>}.
3 3 {<<"requirements">>,#{}}.
4 4 {<<"app">>,<<"epgsql">>}.
5 5 {<<"precompiled">>,false}.
6 6 {<<"description">>,<<"PostgreSQL Client">>}.
7 7 {<<"files">>,
8 8 [<<"src/epgsql.app.src">>,<<"LICENSE">>,<<"README.md">>,
9 - <<"include/epgsql.hrl">>,<<"include/epgsql_binary.hrl">>,
10 - <<"include/epgsql_geometry.hrl">>,<<"rebar.config">>,<<"rebar.lock">>,
11 - <<"src/epgsql.erl">>,<<"src/epgsql_binary.erl">>,
12 - <<"src/epgsql_errcodes.erl">>,<<"src/epgsql_fdatetime.erl">>,
13 - <<"src/epgsql_idatetime.erl">>,<<"src/epgsql_sock.erl">>,
14 - <<"src/epgsql_types.erl">>,<<"src/epgsql_wire.erl">>,<<"src/epgsqla.erl">>,
15 - <<"src/epgsqli.erl">>,<<"src/ewkb.erl">>]}.
9 + <<"include/epgsql.hrl">>,<<"include/epgsql_geometry.hrl">>,
10 + <<"include/protocol.hrl">>,<<"rebar.config">>,<<"rebar.config.script">>,
11 + <<"rebar.lock">>,<<"src/commands/epgsql_cmd_batch.erl">>,
12 + <<"src/commands/epgsql_cmd_bind.erl">>,
13 + <<"src/commands/epgsql_cmd_close.erl">>,
14 + <<"src/commands/epgsql_cmd_connect.erl">>,
15 + <<"src/commands/epgsql_cmd_describe_portal.erl">>,
16 + <<"src/commands/epgsql_cmd_describe_statement.erl">>,
17 + <<"src/commands/epgsql_cmd_equery.erl">>,
18 + <<"src/commands/epgsql_cmd_execute.erl">>,
19 + <<"src/commands/epgsql_cmd_parse.erl">>,
20 + <<"src/commands/epgsql_cmd_prepared_query.erl">>,
21 + <<"src/commands/epgsql_cmd_squery.erl">>,
22 + <<"src/commands/epgsql_cmd_start_replication.erl">>,
23 + <<"src/commands/epgsql_cmd_sync.erl">>,
24 + <<"src/commands/epgsql_cmd_update_type_cache.erl">>,
25 + <<"src/datatypes/epgsql_codec_boolean.erl">>,
26 + <<"src/datatypes/epgsql_codec_bpchar.erl">>,
27 + <<"src/datatypes/epgsql_codec_datetime.erl">>,
28 + <<"src/datatypes/epgsql_codec_float.erl">>,
29 + <<"src/datatypes/epgsql_codec_geometric.erl">>,
30 + <<"src/datatypes/epgsql_codec_hstore.erl">>,
31 + <<"src/datatypes/epgsql_codec_integer.erl">>,
32 + <<"src/datatypes/epgsql_codec_intrange.erl">>,
33 + <<"src/datatypes/epgsql_codec_json.erl">>,
34 + <<"src/datatypes/epgsql_codec_net.erl">>,
35 + <<"src/datatypes/epgsql_codec_noop.erl">>,
36 + <<"src/datatypes/epgsql_codec_postgis.erl">>,
37 + <<"src/datatypes/epgsql_codec_text.erl">>,
38 + <<"src/datatypes/epgsql_codec_uuid.erl">>,<<"src/epgsql.erl">>,
39 + <<"src/epgsql_binary.erl">>,<<"src/epgsql_codec.erl">>,
40 + <<"src/epgsql_command.erl">>,<<"src/epgsql_errcodes.erl">>,
41 + <<"src/epgsql_fdatetime.erl">>,<<"src/epgsql_idatetime.erl">>,
42 + <<"src/epgsql_oid_db.erl">>,<<"src/epgsql_replication.hrl">>,
43 + <<"src/epgsql_scram.erl">>,<<"src/epgsql_sock.erl">>,
44 + <<"src/epgsql_wire.erl">>,<<"src/epgsqla.erl">>,<<"src/epgsqli.erl">>,
45 + <<"src/ewkb.erl">>]}.
16 46 {<<"licenses">>,[<<"BSD">>]}.
17 47 {<<"links">>,[{<<"Github">>,<<"https://github.com/epgsql/epgsql">>}]}.
18 48 {<<"build_tools">>,[<<"rebar3">>]}.
  @@ -1,8 +1,7 @@
1 - -type epgsql_type() :: atom() | {array, atom()} | {unknown_oid, integer()}.
2 -
3 1 -record(column, {
4 2 name :: binary(),
5 - type :: epgsql_type(),
3 + type :: epgsql:epgsql_type(),
4 + oid :: integer(),
6 5 size :: -1 | pos_integer(),
7 6 modifier :: -1 | pos_integer(),
8 7 format :: integer()
  @@ -11,7 +10,8 @@
11 10 -record(statement, {
12 11 name :: string(),
13 12 columns :: [#column{}],
14 - types :: [epgsql_type()]
13 + types :: [epgsql:epgsql_type()],
14 + parameter_info :: [epgsql_oid_db:oid_entry()]
15 15 }).
16 16
17 17 -record(error, {
  @@ -1,3 +0,0 @@
1 - -define(int16, 1/big-signed-unit:16).
2 - -define(int32, 1/big-signed-unit:32).
3 - -define(int64, 1/big-signed-unit:64).
  @@ -1,4 +1,3 @@
1 - -type point_type() :: '2d' | '3d' | '2dm' | '3dm'.
2 1
3 2 -record(point,{
4 3 point_type :: any(),
  @@ -8,114 +7,67 @@
8 7 m :: float() | undefined
9 8 }).
10 9
11 - -type point(PointType) :: #point{ point_type :: PointType }.
12 -
13 10 -record(multi_point,{
14 11 point_type :: any(),
15 - points :: [point(point_type())]
12 + points :: [ewkb:point(ewkb:point_type())]
16 13 }).
17 14
18 - -type multi_point(PointType) :: #multi_point{ point_type :: PointType }.
19 -
20 15 -record(line_string,{
21 16 point_type :: any(),
22 - points :: [point(point_type())]
17 + points :: [ewkb:point(ewkb:point_type())]
23 18 }).
24 19
25 - -type line_string(PointType) :: #line_string{ point_type :: PointType }.
26 -
27 20 -record(multi_line_string,{
28 21 point_type :: any(),
29 - line_strings :: [line_string(point_type())]
22 + line_strings :: [ewkb:line_string(ewkb:point_type())]
30 23 }).
31 24
32 - -type multi_line_string(PointType) :: #multi_line_string{ point_type :: PointType }.
33 -
34 25 -record(circular_string,{
35 26 point_type :: any(),
36 - points :: [point(point_type())]
27 + points :: [ewkb:point(ewkb:point_type())]
37 28 }).
38 29
39 - -type basic_string(PointType) :: #circular_string{ point_type :: PointType } | #line_string{ point_type :: PointType }.
40 -
41 30 -record(compound_curve,{
42 31 point_type :: any(),
43 - lines :: [basic_string(point_type())]
32 + lines :: [ewkb:basic_string(ewkb:point_type())]
44 33 }).
45 34
46 - -type curve(PointType) :: #circular_string{ point_type :: PointType } | #line_string{ point_type :: PointType } | #compound_curve{ point_type :: PointType }.
47 -
48 35 -record(multi_curve,{
49 36 point_type :: any(),
50 - curves :: [curve(point_type())]
37 + curves :: [ewkb:curve(ewkb:point_type())]
51 38 }).
52 39
53 - -type multi_curve(PointType) :: #multi_curve{ point_type :: PointType }.
54 -
55 40 -record(polygon,{
56 41 point_type :: any(),
57 - rings :: [line_string(point_type())]
42 + rings :: [ewkb:line_string(ewkb:point_type())]
58 43 }).
59 44
60 - -type polygon(PointType) :: #polygon{ point_type :: PointType }.
61 -
62 45 -record(multi_polygon,{
63 46 point_type :: any(),
64 - polygons :: [polygon(point_type())]
47 + polygons :: [ewkb:polygon(ewkb:point_type())]
65 48 }).
66 49
67 - -type multi_polygon(PointType) :: #multi_polygon{ point_type :: PointType }.
68 -
69 50 -record(triangle,{
70 51 point_type :: any(),
71 - rings :: [line_string(point_type())]
52 + rings :: [ewkb:line_string(ewkb:point_type())]
72 53 }).
73 54
74 - -type triangle(PointType) :: #triangle{ point_type :: PointType }.
75 -
76 55 -record(curve_polygon,{
77 56 point_type :: any(),
78 - rings :: [curve(point_type())]
57 + rings :: [ewkb:curve(ewkb:point_type())]
79 58 }).
80 59
81 - -type curve_polygon(PointType) :: #curve_polygon{ point_type :: PointType }.
82 -
83 60 -record(polyhedral_surface,{
84 61 point_type :: any(),
85 - polygons :: [polygon(point_type())]
62 + polygons :: [ewkb:polygon(ewkb:point_type())]
86 63 }).
87 64
88 - -type polyhedral_surface(PointType) :: #polyhedral_surface{ point_type :: PointType }.
89 -
90 - -type surface(PointType) :: polygon(PointType) | curve_polygon(PointType) | polyhedral_surface(PointType).
91 -
92 65 -record(multi_surface,{
93 66 point_type :: any(),
94 - surfaces :: [surface(point_type())]
67 + surfaces :: [ewkb:surface(ewkb:point_type())]
95 68 }).
96 69
97 - -type multi_surface(PointType) :: #multi_surface{ point_type :: PointType }.
98 -
99 70 -record(tin,{
100 71 point_type :: any(),
101 - triangles :: [triangle(point_type())]
72 + triangles :: [ewkb:triangle(ewkb:point_type())]
102 73 }).
103 -
104 - -type tin(PointType) :: #tin{ point_type :: PointType }.
105 -
106 - -type geometry(PointType) :: point(PointType) |
107 - line_string(PointType) |
108 - triangle(PointType) |
109 - tin(PointType) |
110 - curve(PointType) |
111 - surface(PointType) |
112 - multi_point(PointType) |
113 - multi_line_string(PointType) |
114 - multi_polygon(PointType) |
115 - multi_curve(PointType) |
116 - multi_surface(PointType) |
117 - geometry_collection(PointType).
118 -
119 - -type geometry() :: geometry(point_type()).
120 -
121 - -type geometry_collection(PointType) :: [geometry(PointType)].
Loading more files…