Current section
18 Versions
Jump to
Current section
18 Versions
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…