├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── rebar ├── rebar.config ├── rebar.config.script ├── src ├── pgsql.app.src ├── pgsql.appup ├── pgsql_app.erl ├── pgsql_connection.erl ├── pgsql_connection_sup.erl ├── pgsql_error.erl ├── pgsql_internal.hrl ├── pgsql_protocol.erl └── pgsql_sup.erl └── test └── pgsql_connection_test.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | ebin 3 | .DS_Store 4 | .persistent_state 5 | target 6 | rebar.lock 7 | _build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 20.3 4 | - 20.0 5 | - 19.3 6 | - 19.0 7 | - 18.3 8 | - 18.0 9 | - 17.5 10 | - 17.0 11 | services: postgresql 12 | before_script: 13 | - psql -c 'CREATE ROLE test LOGIN; ALTER USER test WITH SUPERUSER;' -U postgres 14 | - psql -c 'CREATE DATABASE test WITH OWNER=test;' -U postgres 15 | before_install: 16 | - "test x\"$TRAVIS_POSTGRESQL_VERSION\" = x\"\" || (sudo service postgresql stop && sudo service postgresql start $TRAVIS_POSTGRESQL_VERSION)" 17 | matrix: 18 | include: 19 | - otp_release: 20.3 20 | env: TRAVIS_POSTGRESQL_VERSION=9.2 21 | - otp_release: 20.3 22 | env: TRAVIS_POSTGRESQL_VERSION=9.3 23 | - otp_release: 20.3 24 | env: TRAVIS_POSTGRESQL_VERSION=9.4 25 | - otp_release: 20.3 26 | env: TRAVIS_POSTGRESQL_VERSION=9.5 27 | - otp_release: 20.3 28 | env: TRAVIS_POSTGRESQL_VERSION=9.6 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2014, Semiocast. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Erlang PostgreSQL Driver [![Build Status](https://travis-ci.org/semiocast/pgsql.png)](https://travis-ci.org/semiocast/pgsql) 2 | ======================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | This driver is an OTP-compliant PostgreSQL driver. Connections are OTP-supervised processes. 8 | 9 | This implementation was initially inspired and derived from existing database drivers, and especially Will Glozer's and Christian Sunesson's PostgreSQL drivers, yet has eventually little in common with those. API and features are different. 10 | 11 | In particular, this driver has the following features: 12 | 13 | * OTP-supervision and OTP-upgrades; 14 | * transparently handling many PostgreSQL types, including arrays, numerics and geometric types; 15 | * cancellation of running queries using out-of-band protocol; 16 | * SSL support; 17 | * timeout for queries; 18 | * iteration on results using protocol-level implicit portals and cursors, with fold, map and foreach; 19 | * mapping of types to Erlang using a mapping of known types, handling new types that may arise through the life of the connection (this feature is an improvement of oidmap handling in Christian Sunesson's driver); 20 | * handling both floating point and integer datetimes (this feature is an improvement of timestamp handling in Will Glozer's driver). 21 | 22 | Compilation 23 | ----------- 24 | 25 | Driver can be compiled with [rebar](https://github.com/basho/rebar) or [rebar3](http://rebar3.org/). 26 | 27 | erlc -o ebin/ src/*.erl 28 | 29 | also works. 30 | 31 | API and usage 32 | ------------- 33 | 34 | The application must be started before connections can be made to a PostgreSQL server. 35 | 36 | ### Opening and closing connections ### 37 | 38 | The main module is ```pgsql_connection```. Connections are opened with one of ```pgsql_connection:open/1,2,3,4,5``` functions. Please refer to ```pgsql_connection.erl``` for details. 39 | 40 | Connection objects are ```{pgsql_connection, pid()}``` tuples. As a result, and following other database APIs, connection objects can be used as [parametrized modules](http://erlang.se/workshop/2003/paper/p29-carlsson.pdf). API functions taking a connection as the last parameter can be called with the connection as the parametrized module. For example, a connection can be closed with ```pgsql_connection:close(Connection)``` or with ```Connection:close()```. 41 | 42 | ### Performing queries ### 43 | 44 | Two APIs are available. The current, "native" API is composed of the following functions: 45 | 46 | #### ```simple_query/2,3,4``` #### 47 | 48 | Perform a simple query (in PostgreSQL parlance), i.e. a query with no parameter. Results are cast to Erlang-types. Text is returned as binaries (```unicode:unicode_binary()```). Enums are typed ```{atom() :: TypeName, unicode:unicode_binary()}```. See below for details. 49 | 50 | #### ```extended_query/3,4,5``` #### 51 | 52 | Perform an extended query, i.e. a query with bound parameters. Parameters must be represented in the query with $1, ..., $n placeholders. Most erlang types are supported. Binaries are passed as-is and are interpreted by PostgreSQL as text or binary depending on what is expected. Lists are interpreted as strings, arrays are represented as ```{array, [any()]}```. See below for details. 53 | 54 | #### ```batch_query/3,4,5``` #### 55 | 56 | Perform an extended query several times with different parameters. Saves some network I/O with the server. 57 | 58 | #### ```fold/4,5,6,7```, ```map/3,4,5,6```, ```foreach/3,4,5,6``` #### 59 | 60 | Perform an extended query and fold (resp. map, execute a function on each row). This opens an implicit cursor with the server and iterates on results. 61 | 62 | #### ```cancel/1``` #### 63 | 64 | Cancel the current running query. This opens a new connection to cancel the query (out-of-band cancelation). 65 | 66 | ### Data types ### 67 | 68 | The following table summarizes the PostgreSQL types currently handled by the driver and their format on input (parameters of ```extended_query/3,4,5```) and on output (e.g. results of select queries). 69 | 70 | All types are tested in ```pgsql_connection_test.erl```. 71 | 72 | | SQL | Erlang | Notes | 73 | |--------------|----------------------------------------|--------------------------------------------------------------------------| 74 | | NULL | `'null'` | | 75 | | integer | `integer()` | SQL is limited to up to 131072 digits for numeric type | 76 | | float | `float()` | SQL decimal and numeric values lose precision when converted to double() | 77 | | NaN | `'NaN'` | | 78 | | ±Infinity | `'Infinity'`, `'-Infinity'` | | 79 | | text | `unicode:unicode_binary() \| string()` | Driver expects everything is UTF-8 encoded, Strings (lists of integers < 256) can be used on input | 80 | | bytea | `binary()` | | 81 | | date | `calendar:date()` | | 82 | | time | `calendar:time()` | | 83 | | timestamp | `calendar:datetime() \| {{Y,Mo,D},{H,M,S}} with S::float()` | Conversion on output with seconds as integer or float is driven by `datetime_float_seconds` option | 84 | | boolean | `boolean()` | | 85 | | enums | `{atom(),unicode:unicode_binary()} \| {integer(),unicode:unicode_binary()} \| unicode:unicode_binary() \| string()` | Values can be used directly on input (as text). Output is always tagged. Within the transaction creating the enum, the tag can be the OID | 86 | | point | `{point,{number(),number()}}` | Coordinates can be passed as integers and are always returned as floats | 87 | | lseg | `{lseg,{number(),number()},{number(),number()}}` | | 88 | | box | `{box,{number(),number()},{number(),number()}}` | | 89 | | path | `{path,open\|close,[{number(),number()}]}` | | 90 | | polygon | `{polygon,[{number(),number()}]}` | | 91 | | inet | `{inet,inet:address()}` | Supports both IPv4 and IPv6 | 92 | | cidr | `{cidr,inet:address(),0..128}` | | 93 | | uuid | `unicode:unicode_binary()` | UUID are binaries in AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE format | 94 | | json | `{json,unicode:unicode_binary()}` | | 95 | | jsonb | `{jsonb,unicode:unicode_binary()}` | | 96 | | arrays | `{array,list()}` | | 97 | 98 | Anything else is not handled yet. Parameters as binaries are passed as is to PostgreSQL, in binary format, and unknown types are returned as `{atom(), binary()}` or `{integer(), binary()}` for unknown OIDs. Theoretically, handling of data types unknown to the driver could be handled by the client. However, please note in this case that the driver can return values in text or binary format depending on the sub-protocol used. Typically, `simple_query` functions will get results in "text format", while `extended_query` will get results in "binary format". 99 | 100 | ### COPY support ### 101 | 102 | ```COPY``` is a PostgreSQL-specific command for bulk transfers of table contents. It is less structured and more brittle to user errors. 103 | 104 | #### copying from a table #### 105 | 106 | ```Connection:simple_query("COPY mytable TO STDOUT")``` will return ```{{copy, ListSize},[<>, ...]}```. ```ListSize``` is the number of elements in the associated list, which often corresponds to the number of rows in the table. The actual contents of the returned data depend on the COPY query (for example, specifying a format). 107 | 108 | ```extended_query```, ```fold```, ```map```, and ```foreach``` may also be used, but note that Postgres does not permit parameter-binding in COPY queries. 109 | 110 | #### copying to a table #### 111 | 112 | Begin a copy using `simple_query` (not `extended_query`) to copy from "stdin". Then perform any number of `send_copy_data/2` to transmit data to Postgres' stdin. Finally, `send_copy_end/1` to have Postgres process the data. 113 | 114 | ```erlang 115 | {copy_in,_} = Connection:simple_query("COPY people(fn,ln) FROM STDIN"), 116 | ok = Connection:send_copy_data(<<"Donald\tChamberlin\nRaymond\tBoyce\nMi">>), 117 | ok = Connection:send_copy_data(<<"chael\tStonebraker\n">>), 118 | {copy,3} = Connection:send_copy_end() 119 | ``` 120 | 121 | Using `COPY` is generally the fastest method to bulk-insert data by a large margin. 122 | 123 | ### ODBC-like API ### 124 | 125 | The driver also has an ODBC-like interface, which is deprecated. It is composed of ```sql_query/2,3,4```, ```param_query/3,4,5``` and utility function ```convert_statement/1``` 126 | 127 | ### Asynchronous operations ### 128 | 129 | Besides cancelation, the driver supports what PostgreSQL documentation calls [Asynchronous operations](http://www.postgresql.org/docs/current/static/protocol-flow.html#PROTOCOL-ASYNC). Namely, it can receive several types of messages when the connection is idle or at any point in the protocol. This is especially meaningful for notifications and notices. These messages are ignored by default but can be sent to subscribers. To subscribe when opening a connection, simply use ```{async, pid()}``` option. To subscribe later, use ```pgsql_connection:subscribe/2```. Subscribers can presently receive two types of Erlang messages: 130 | 131 | * ```{pgsql, Connection, {notice, Fields :: [{atom(), binary()}]}}``` for notices, where ```Connection``` is the connection tuple and Fields describes the notice (typically including ```{severity, <<"NOTICE">>}``` and ```{message, NoticeMessage}```). 132 | * ```{pgsql, Connection, {notification, ProcID :: pos_integer(), Channel :: binary(), Payload :: binary()}}``` for notifications, where ```ProcID``` is the sender backend id. 133 | 134 | Implementers of subscribers are encouraged to handle any message of the form ```{pgsql, Connection, _}``` to cope with future features. 135 | 136 | Tests 137 | ----- 138 | 139 | Tests require a user 'test' on postgres, with super privileges, and a test database. 140 | For example: 141 | 142 | psql -h localhost -U postgres 143 | ```SQL 144 | create user test; 145 | alter user test with superuser; 146 | create database test with owner=test; 147 | ``` 148 | 149 | OTP upgrades 150 | ------------ 151 | 152 | Application upgrade file ([pgsql.appup](https://github.com/semiocast/pgsql/blob/master/src/pgsql.appup)) is updated for OTP release upgrades, so this application can easily be upgraded without any downtime, even with long running queries. This file is updated for each [release tags](https://github.com/semiocast/pgsql/releases). 153 | 154 | License 155 | ------- 156 | 157 | Copyright (c) 2009-2018, Semiocast. 158 | All rights reserved. 159 | 160 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 161 | 162 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 163 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 164 | 165 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 166 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semiocast/pgsql/c7d98673459052b2c48526f16dab11ab34c23891/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {xref_warnings, false}. 2 | {xref_checks, [exports_not_used, undefined_function_calls]}. 3 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | Release = erlang:system_info(otp_release), 2 | case Release of 3 | [$R | _] when Release =< "R15B01" -> 4 | HashDefine = [{d,old_hash}], 5 | case lists:keysearch(erl_opts, 1, CONFIG) of 6 | {value, {erl_opts, Opts}} -> 7 | lists:keyreplace(erl_opts,1,CONFIG,{erl_opts,Opts++HashDefine}); 8 | false -> 9 | CONFIG ++ [{erl_opts, HashDefine}] 10 | end; 11 | _ -> CONFIG 12 | end. 13 | -------------------------------------------------------------------------------- /src/pgsql.app.src: -------------------------------------------------------------------------------- 1 | {application, pgsql, 2 | [ 3 | {description, "pgsql driver"}, 4 | {id, "pgsql"}, 5 | {vsn, "26.0.2"}, 6 | {modules, [ 7 | pgsql_app, 8 | pgsql_connection, 9 | pgsql_connection_sup, 10 | pgsql_error, 11 | pgsql_protocol, 12 | pgsql_sup 13 | ]}, 14 | {registered, [pgsql_sup, pgsql_connection_sup]}, 15 | {applications, [kernel, stdlib, crypto]}, 16 | {mod, {pgsql_app, []}}, 17 | 18 | % hex.pm metadata 19 | {maintainers, ["Paul Guyot"]}, 20 | {licenses, ["BSD"]}, 21 | {links, [{"Github", "https://github.com/semiocast/pgsql"}]} 22 | ]}. 23 | -------------------------------------------------------------------------------- /src/pgsql.appup: -------------------------------------------------------------------------------- 1 | {"26.0.2", 2 | [ 3 | {"26.0.1", [ 4 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 5 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 6 | ]}, 7 | {"26.0.0", [ 8 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 9 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 10 | ]}, 11 | {"26", [ 12 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 13 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 14 | ]}, 15 | {"25", [ 16 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 17 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 18 | ]}, 19 | {"24", [ 20 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 21 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 22 | ]}, 23 | {"23", [ 24 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 25 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 26 | ]}, 27 | {"22", [ 28 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 29 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 30 | {load_module, pgsql_error, soft_purge, soft_purge, []} 31 | ]}, 32 | {"21", [ 33 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 34 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 35 | {load_module, pgsql_error, soft_purge, soft_purge, []} 36 | ]}, 37 | {"20", [ 38 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 39 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 40 | {load_module, pgsql_error, soft_purge, soft_purge, []} 41 | ]}, 42 | {"18", [ 43 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 44 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 45 | {load_module, pgsql_error, soft_purge, soft_purge, []} 46 | ]} 47 | ], 48 | [ 49 | {"26.0.1", [ 50 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 51 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 52 | ]}, 53 | {"26.0.0", [ 54 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 55 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 56 | ]}, 57 | {"26", [ 58 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 59 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 60 | ]}, 61 | {"25", [ 62 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 63 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 64 | ]}, 65 | {"24", [ 66 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 67 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 68 | ]}, 69 | {"23", [ 70 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 71 | {load_module, pgsql_protocol, soft_purge, soft_purge, []} 72 | ]}, 73 | {"22", [ 74 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 75 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 76 | {load_module, pgsql_error, soft_purge, soft_purge, []} 77 | ]}, 78 | {"21", [ 79 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 80 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 81 | {load_module, pgsql_error, soft_purge, soft_purge, []} 82 | ]}, 83 | {"20", [ 84 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 85 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 86 | {load_module, pgsql_error, soft_purge, soft_purge, []} 87 | ]}, 88 | {"18", [ 89 | {load_module, pgsql_connection, soft_purge, soft_purge, []}, 90 | {load_module, pgsql_protocol, soft_purge, soft_purge, []}, 91 | {load_module, pgsql_error, soft_purge, soft_purge, []} 92 | ]} 93 | ] 94 | }. 95 | -------------------------------------------------------------------------------- /src/pgsql_app.erl: -------------------------------------------------------------------------------- 1 | %% @doc Application for the pgsql application. 2 | 3 | -module(pgsql_app). 4 | -vsn(1). 5 | -behaviour(application). 6 | 7 | %% application API 8 | -export([start/2, stop/1]). 9 | 10 | %% @doc Start the application. 11 | -spec start(normal | {takeover, node()} | {failover, node()}, any()) -> {ok, pid()} | {ok, pid(), any()} | {error, any()}. 12 | start(_Type, _StartArgs) -> 13 | pgsql_sup:start_link(). 14 | 15 | %% @doc Stop the application. 16 | -spec stop(any()) -> stop. 17 | stop(_State) -> 18 | stop. 19 | -------------------------------------------------------------------------------- /src/pgsql_connection.erl: -------------------------------------------------------------------------------- 1 | %% @doc PostgreSQL connection (high level functions). 2 | -module(pgsql_connection). 3 | -vsn("9"). 4 | -behaviour(gen_server). 5 | -include("pgsql_internal.hrl"). 6 | 7 | -export([ 8 | % API 9 | open/1, 10 | open/2, 11 | open/3, 12 | open/4, 13 | open/5, 14 | 15 | close/1, 16 | 17 | % Native API 18 | simple_query/2, 19 | simple_query/3, 20 | simple_query/4, 21 | 22 | extended_query/3, 23 | extended_query/4, 24 | extended_query/5, 25 | 26 | batch_query/3, 27 | batch_query/4, 28 | batch_query/5, 29 | 30 | fold/4, 31 | fold/5, 32 | fold/6, 33 | fold/7, 34 | 35 | map/3, 36 | map/4, 37 | map/5, 38 | map/6, 39 | 40 | foreach/3, 41 | foreach/4, 42 | foreach/5, 43 | foreach/6, 44 | 45 | send_copy_data/2, 46 | send_copy_end/1, 47 | 48 | % Cancel current query 49 | cancel/1, 50 | 51 | % Subscribe to notifications. 52 | subscribe/2, 53 | unsubscribe/2, 54 | 55 | % Compatibility (deprecated) API 56 | sql_query/2, 57 | sql_query/3, 58 | sql_query/4, 59 | 60 | param_query/3, 61 | param_query/4, 62 | param_query/5, 63 | 64 | convert_statement/1, 65 | 66 | % supervisor API 67 | start_link/1, 68 | 69 | % gen_server API 70 | init/1, 71 | handle_call/3, 72 | handle_cast/2, 73 | code_change/3, 74 | handle_info/2, 75 | terminate/2, 76 | 77 | % url parser 78 | parse_url/1 79 | ]). 80 | 81 | -export_type([ 82 | row/0, 83 | rows/0, 84 | result_tuple/0, 85 | pgsql_connection/0]). 86 | 87 | %%-------------------------------------------------------------------- 88 | %% Default settings 89 | %%-------------------------------------------------------------------- 90 | 91 | -define(REQUEST_TIMEOUT, infinity). 92 | -define(DEFAULT_HOST, "127.0.0.1"). 93 | -define(DEFAULT_PORT, 5432). 94 | -define(DEFAULT_USER, "storage"). 95 | -define(DEFAULT_PASSWORD, ""). 96 | -define(DEFAULT_MAX_ROWS_STEP, 1000). 97 | 98 | -define(TIMEOUT_GEN_SERVER_CALL_DELTA, 5000). 99 | 100 | %% ========================================================================= %% 101 | %% Types 102 | %% ========================================================================= %% 103 | 104 | -type pgsql_connection() :: {pgsql_connection, pid()}. 105 | 106 | -type n_rows() :: integer(). 107 | -type row() :: tuple() | map(). 108 | -type rows() :: [row()]. 109 | -type odbc_result_tuple() :: {updated, n_rows()} | {updated, n_rows(), rows()} | {selected, rows()}. 110 | % Column descriptions are returned with return_descriptions query option, an 111 | % experimental API. Column name (unicode:unicode_binary()) is the second element 112 | % of the tuple. 113 | -type column_description() :: #row_description_field{}. 114 | -type column_descriptions() :: [column_description()]. 115 | 116 | -type result_tuple() :: 117 | {'begin' | commit | 'do' | listen | unlisten | notify | rollback | set | {declare, cursor} | {lock, table} | comment, []} 118 | | {{insert, integer(), integer()}, rows()} 119 | | {{copy | delete | fetch | move | select | update, integer()}, rows()} 120 | | {{insert, integer(), integer()}, column_descriptions(), rows()} 121 | | {{copy | delete | fetch | move | select | update, integer()}, column_descriptions(), rows()} 122 | | {{alter | create | drop, atom()} | {start, transaction}, []} 123 | | {copy_in, [pgsql_format()]}. 124 | 125 | % A gen_tcp or SSL socket. 126 | -type prim_socket() :: port() | tuple(). 127 | -type socket_module() :: gen_tcp | ssl. 128 | -type socket() :: {socket_module(), prim_socket()}. 129 | 130 | % driver options. 131 | -type open_option() :: 132 | {url, iodata()} % default: none 133 | | {host, inet:ip_address() | inet:hostname()} % default: ?DEFAULT_HOST 134 | | {port, integer()} % default: ?DEFAULT_PORT 135 | | {database, iodata()} % default: user 136 | | {user, iodata()} % default: ?DEFAULT_USER 137 | | {password, iodata()} % default: none 138 | | {fetch_oid_map, boolean()} % default: true 139 | | {ssl, boolean()} % default: false 140 | | {ssl_options, [ssl:ssl_option()]} % default: [] 141 | | {reconnect, boolean()} % default: true 142 | | {application_name, atom() | iodata()} % default: node() 143 | | {timezone, iodata() | undefined} % default: undefined (not set) 144 | | {async, pid()} % subscribe to notifications (default: no) 145 | | proplists:property(). % undocumented ! 146 | -type open_options() :: [open_option()]. 147 | -type query_option() :: 148 | {max_rows_step, non_neg_integer()} % default: ?DEFAULT_MAX_ROWS_STEP 149 | | {retry, boolean()} % default: false 150 | | {return_descriptions, boolean()} % default: false 151 | | {return_maps, boolean()} % default: false 152 | | {datetime_float_seconds, round | always | as_available} % default: as_available 153 | | proplists:property(). % undocumented. 154 | -type query_options() :: [query_option()]. 155 | 156 | % gen_server:call From tag. 157 | -type from() :: {pid(), term()}. 158 | 159 | -record(state, { 160 | options :: open_options(), 161 | socket :: socket() | closed, %% gen_tcp or ssl socket 162 | subscribers :: [{pid(), reference()}], 163 | backend_procid :: integer() | undefined, 164 | backend_secret :: integer() | undefined, 165 | integer_datetimes :: boolean() | undefined, 166 | oidmap :: pgsql_oid_map(), 167 | current :: {tuple(), reference(), from()} | undefined | {tuple(), from()}, 168 | pending :: [{tuple(), reference(), from()}] | [{tuple(), from()}], 169 | statement_timeout :: non_neg_integer() | undefined %% to pipeline statements with timeouts, currently unused 170 | }). 171 | 172 | -define(MESSAGE_HEADER_SIZE, 5). 173 | 174 | % pgsql extended query states. 175 | -type extended_query_mode() :: all | batch | {cursor, non_neg_integer()}. 176 | -type extended_query_loop_state() :: 177 | % expect parse_complete message 178 | parse_complete 179 | | {parse_complete_with_params, extended_query_mode(), [any()]} 180 | % expect parameter_description 181 | | {parameter_description_with_params, extended_query_mode(), [any()]} 182 | % expect row_description or no_data 183 | | pre_bind_row_description 184 | % expect bind_complete 185 | | bind_complete 186 | % expect row_description or no_data 187 | | row_description 188 | % expect data_row or command_complete 189 | | {rows, [#row_description_field{}]} 190 | % expect command_complete 191 | | no_data 192 | % expect ready_for_query 193 | | {result, any()} 194 | % expect copy_data or copy_done 195 | | {copy, [pgsql_format()]}. 196 | 197 | -define(binary_to_integer(Bin), list_to_integer(binary_to_list(Bin))). 198 | 199 | %%-------------------------------------------------------------------- 200 | %% @doc Open a connection to a database, throws an error if it failed. 201 | %% 202 | -spec open(iodata() | open_options()) -> pgsql_connection(). 203 | open([Option | _OptionsT] = Options) when is_tuple(Option) orelse is_atom(Option) -> 204 | open0(Options); 205 | open(("postgres://" ++ _) = Url) -> 206 | open([{url, Url}]); 207 | open(Database) -> 208 | open(Database, ?DEFAULT_USER). 209 | 210 | %%-------------------------------------------------------------------- 211 | %% @doc Open a connection to a database, throws an error if it failed. 212 | %% 213 | -spec open(iodata(), iodata()) -> pgsql_connection(). 214 | open(Database, User) -> 215 | open(Database, User, ?DEFAULT_PASSWORD). 216 | 217 | %%-------------------------------------------------------------------- 218 | %% @doc Open a connection to a database, throws an error if it failed. 219 | %% 220 | -spec open(iodata(), iodata(), iodata()) -> pgsql_connection(). 221 | open(Database, User, Password) -> 222 | open(?DEFAULT_HOST, Database, User, Password). 223 | 224 | %%-------------------------------------------------------------------- 225 | %% @doc Open a connection to a database, throws an error if it failed. 226 | %% 227 | -spec open(string(), string(), string(), string()) -> pgsql_connection(). 228 | open(Host, Database, User, Password) -> 229 | open(Host, Database, User, Password, []). 230 | 231 | %%-------------------------------------------------------------------- 232 | %% @doc Open a connection to a database, throws an error if it failed. 233 | %% 234 | -spec open(string(), string(), string(), string(), open_options()) -> pgsql_connection(). 235 | open(Host, Database, User, Password, Options0) -> 236 | Options = [{host, Host}, {database, Database}, {user, User}, {password, Password} | Options0], 237 | open0(Options). 238 | 239 | open0(Options) -> 240 | case pgsql_connection_sup:start_child(Options) of 241 | {ok, Pid} -> 242 | {pgsql_connection, Pid}; 243 | {error, Error} -> 244 | throw(Error) 245 | end. 246 | 247 | %%-------------------------------------------------------------------- 248 | %% @doc Close a connection. 249 | %% 250 | -spec close(pgsql_connection()) -> ok. 251 | close({pgsql_connection, Pid}) -> 252 | MonitorRef = erlang:monitor(process, Pid), 253 | exit(Pid, shutdown), 254 | receive {'DOWN', MonitorRef, process, Pid, _Info} -> ok end. 255 | 256 | 257 | %%-------------------------------------------------------------------- 258 | %% @doc Perform a query. 259 | %% This function creates a statement and runs step as many times as 260 | %% required. The result is: 261 | %% 265 | %% (the return types are compatible with ODBC's sql_query function). 266 | %% 267 | -spec sql_query(iodata(), pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 268 | sql_query(Query, Connection) -> 269 | sql_query(Query, [], Connection). 270 | 271 | -spec sql_query(iodata(), query_options(), pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 272 | sql_query(Query, QueryOptions, Connection) -> 273 | sql_query(Query, QueryOptions, ?REQUEST_TIMEOUT, Connection). 274 | 275 | -spec sql_query(iodata(), query_options(), timeout(), pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 276 | sql_query(Query, QueryOptions, Timeout, Connection) -> 277 | Result = simple_query(Query, QueryOptions, Timeout, Connection), 278 | native_to_odbc(Result). 279 | 280 | %%-------------------------------------------------------------------- 281 | %% @doc Perform a query with parameters. 282 | %% 283 | -spec param_query(iodata(), [any()], pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 284 | param_query(Query, Parameters, Connection) -> 285 | param_query(Query, Parameters, [], Connection). 286 | 287 | -spec param_query(iodata(), [any()], query_options(), pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 288 | param_query(Query, Parameters, QueryOptions, Connection) -> 289 | param_query(Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 290 | 291 | -spec param_query(iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> odbc_result_tuple() | {error, any()}. 292 | param_query(Query, Parameters, QueryOptions, Timeout, Connection) -> 293 | ConvertedQuery = convert_statement(Query), 294 | Result = extended_query(ConvertedQuery, Parameters, QueryOptions, Timeout, Connection), 295 | native_to_odbc(Result). 296 | 297 | %%-------------------------------------------------------------------- 298 | %% @doc Perform a simple query. 299 | %% 300 | -spec simple_query(iodata(), pgsql_connection()) -> 301 | result_tuple() | {error, any()} | [result_tuple() | {error, any()}]. 302 | simple_query(Query, Connection) -> 303 | simple_query(Query, [], Connection). 304 | 305 | -spec simple_query(iodata(), query_options(), pgsql_connection()) -> 306 | result_tuple() | {error, any()} | [result_tuple() | {error, any()}]. 307 | simple_query(Query, QueryOptions, Connection) -> 308 | simple_query(Query, QueryOptions, ?REQUEST_TIMEOUT, Connection). 309 | 310 | %% @doc Perform a simple query with query options and a timeout. 311 | %% Issuing SET statement_timeout or altering default in postgresql.conf 312 | %% will confuse timeout logic and such manual handling of statement_timeout 313 | %% should not be mixed with calls to simple_query/4. 314 | %% 315 | -spec simple_query(iodata(), query_options(), timeout(), pgsql_connection()) -> 316 | result_tuple() | {error, any()} | [result_tuple() | {error, any()}]. 317 | simple_query(Query, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 318 | call_and_retry(ConnectionPid, {simple_query, Query, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 319 | 320 | %%-------------------------------------------------------------------- 321 | %% @doc Perform a query with parameters. 322 | %% 323 | -spec extended_query(iodata(), [any()], pgsql_connection()) -> result_tuple() | {error, any()}. 324 | extended_query(Query, Parameters, Connection) -> 325 | extended_query(Query, Parameters, [], Connection). 326 | 327 | -spec extended_query(iodata(), [any()], query_options(), pgsql_connection()) -> result_tuple() | {error, any()}. 328 | extended_query(Query, Parameters, QueryOptions, Connection) -> 329 | extended_query(Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 330 | 331 | %% @doc Perform an extended query with query options and a timeout. 332 | %% See discussion of simple_query/4 about timeout values. 333 | %% 334 | -spec extended_query(iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> result_tuple() | {error, any()}. 335 | extended_query(Query, Parameters, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 336 | call_and_retry(ConnectionPid, {extended_query, Query, Parameters, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 337 | 338 | %%-------------------------------------------------------------------- 339 | %% @doc Perform a query several times with parameters. 340 | %% 341 | -spec batch_query(iodata(), [any()], pgsql_connection()) -> 342 | [result_tuple()] | {error, any()} | [result_tuple() | {error, any()}]. 343 | batch_query(Query, Parameters, Connection) -> 344 | batch_query(Query, Parameters, [], Connection). 345 | 346 | -spec batch_query(iodata(), [any()], query_options(), pgsql_connection()) -> 347 | [result_tuple()] | {error, any()} | [result_tuple() | {error, any()}]. 348 | batch_query(Query, Parameters, QueryOptions, Connection) -> 349 | batch_query(Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 350 | 351 | -spec batch_query(iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> 352 | [result_tuple()] | {error, any()} | [result_tuple() | {error, any()}]. 353 | batch_query(Query, Parameters, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 354 | call_and_retry(ConnectionPid, {batch_query, Query, Parameters, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 355 | 356 | %%-------------------------------------------------------------------- 357 | %% @doc Fold over results of a given query. 358 | %% The function is evaluated within the connection's process. 359 | %% 360 | -spec fold(fun((tuple(), Acc) -> Acc), Acc, iodata(), pgsql_connection()) -> {ok, Acc} | {error, any()}. 361 | fold(Function, Acc0, Query, Connection) -> 362 | fold(Function, Acc0, Query, [], Connection). 363 | 364 | -spec fold(fun((tuple(), Acc) -> Acc), Acc, iodata(), [any()], pgsql_connection()) -> {ok, Acc} | {error, any()}. 365 | fold(Function, Acc0, Query, Parameters, Connection) -> 366 | fold(Function, Acc0, Query, Parameters, [], Connection). 367 | 368 | -spec fold(fun((tuple(), Acc) -> Acc), Acc, iodata(), [any()], query_options(), pgsql_connection()) -> {ok, Acc} | {error, any()}. 369 | fold(Function, Acc0, Query, Parameters, QueryOptions, Connection) -> 370 | fold(Function, Acc0, Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 371 | 372 | -spec fold(fun((tuple(), Acc) -> Acc), Acc, iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> {ok, Acc} | {error, any()}. 373 | fold(Function, Acc0, Query, Parameters, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 374 | call_and_retry(ConnectionPid, {fold, Query, Parameters, Function, Acc0, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 375 | 376 | %%-------------------------------------------------------------------- 377 | %% @doc Map results of a given query. 378 | %% The function is evaluated within the connection's process. 379 | %% 380 | -spec map(fun((tuple()) -> Any), iodata(), pgsql_connection()) -> {ok, [Any]} | {error, any()}. 381 | map(Function, Query, Connection) -> 382 | map(Function, Query, [], Connection). 383 | 384 | -spec map(fun((tuple()) -> Any), iodata(), [any()], pgsql_connection()) -> {ok, [Any]} | {error, any()}. 385 | map(Function, Query, Parameters, Connection) -> 386 | map(Function, Query, Parameters, [], Connection). 387 | 388 | -spec map(fun((tuple()) -> Any), iodata(), [any()], query_options(), pgsql_connection()) -> {ok, [Any]} | {error, any()}. 389 | map(Function, Query, Parameters, QueryOptions, Connection) -> 390 | map(Function, Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 391 | 392 | -spec map(fun((tuple()) -> Any), iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> {ok, [Any]} | {error, any()}. 393 | map(Function, Query, Parameters, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 394 | call_and_retry(ConnectionPid, {map, Query, Parameters, Function, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 395 | 396 | %%-------------------------------------------------------------------- 397 | %% @doc Iterate on results of a given query. 398 | %% The function is evaluated within the connection's process. 399 | %% 400 | -spec foreach(fun((tuple()) -> any()), iodata(), pgsql_connection()) -> ok | {error, any()}. 401 | foreach(Function, Query, Connection) -> 402 | foreach(Function, Query, [], Connection). 403 | 404 | -spec foreach(fun((tuple()) -> any()), iodata(), [any()], pgsql_connection()) -> ok | {error, any()}. 405 | foreach(Function, Query, Parameters, Connection) -> 406 | foreach(Function, Query, Parameters, [], Connection). 407 | 408 | -spec foreach(fun((tuple()) -> any()), iodata(), [any()], query_options(), pgsql_connection()) -> ok | {error, any()}. 409 | foreach(Function, Query, Parameters, QueryOptions, Connection) -> 410 | foreach(Function, Query, Parameters, QueryOptions, ?REQUEST_TIMEOUT, Connection). 411 | 412 | -spec foreach(fun((tuple()) -> any()), iodata(), [any()], query_options(), timeout(), pgsql_connection()) -> ok | {error, any()}. 413 | foreach(Function, Query, Parameters, QueryOptions, Timeout, {pgsql_connection, ConnectionPid}) -> 414 | call_and_retry(ConnectionPid, {foreach, Query, Parameters, Function, QueryOptions, Timeout}, proplists:get_bool(retry, QueryOptions), adjust_timeout(Timeout)). 415 | 416 | %%-------------------------------------------------------------------- 417 | %% @doc Send some binary data after starting a COPY 418 | %% 419 | -spec send_copy_data(iodata(), pgsql_connection()) -> ok | {error, any()}. 420 | send_copy_data(Data, {pgsql_connection, ConnectionPid}) -> 421 | call_and_retry(ConnectionPid, {send_copy_data, Data}, false, infinity). 422 | 423 | %%-------------------------------------------------------------------- 424 | %% @doc Finish a COPY 425 | %% 426 | -spec send_copy_end(pgsql_connection()) -> {copy, integer()} | {error, any()}. 427 | send_copy_end({pgsql_connection, ConnectionPid}) -> 428 | call_and_retry(ConnectionPid, {send_copy_end}, false, infinity). 429 | 430 | %%-------------------------------------------------------------------- 431 | %% @doc Cancel the current query. 432 | %% 433 | -spec cancel(pgsql_connection()) -> ok | {error, any()}. 434 | cancel({pgsql_connection, ConnectionPid}) -> 435 | gen_server:call(ConnectionPid, cancel, ?REQUEST_TIMEOUT). 436 | 437 | %%-------------------------------------------------------------------- 438 | %% @doc Subscribe to notifications. Subscribers get notifications as 439 | %% {pgsql, Connection, {notification, ProcID, Channel, Payload}} 440 | %% 441 | -spec subscribe(pid(), pgsql_connection()) -> ok | {error, any()}. 442 | subscribe(Pid, {pgsql_connection, ConnectionPid}) -> 443 | gen_server:cast(ConnectionPid, {subscribe, Pid}). 444 | 445 | %%-------------------------------------------------------------------- 446 | %% @doc Unsubscribe to notifications. 447 | %% 448 | -spec unsubscribe(pid(), pgsql_connection()) -> ok | {error, any()}. 449 | unsubscribe(Pid, {pgsql_connection, ConnectionPid}) -> 450 | gen_server:cast(ConnectionPid, {unsubscribe, Pid}). 451 | 452 | %%==================================================================== 453 | %% Supervisor API 454 | %%==================================================================== 455 | 456 | %%-------------------------------------------------------------------- 457 | %% Starts a pgsql_connection process. 458 | %% 459 | -spec start_link(open_options()) -> {ok, pid()} | {error, any()}. 460 | start_link(Options) -> 461 | gen_server:start_link(?MODULE, Options, []). 462 | 463 | %% ========================================================================= %% 464 | %% gen_server API 465 | %% ========================================================================= %% 466 | 467 | %%-------------------------------------------------------------------- 468 | %% @doc gen_server's init callback. 469 | %% 470 | -spec init(open_options()) -> {ok, #state{}} | {stop, any()}. 471 | init(Options) -> 472 | process_flag(trap_exit, true), 473 | Subscribers = case lists:keyfind(async, 1, Options) of 474 | false -> []; 475 | {async, SubscriberPid} -> do_subscribe(SubscriberPid, []) 476 | end, 477 | State0 = #state{ 478 | options = Options, 479 | socket = closed, 480 | subscribers = Subscribers, 481 | oidmap = gb_trees:from_orddict(orddict:from_list(?PG_TYPE_H_TYPES_DICT)), 482 | pending = [] 483 | }, 484 | case pgsql_open(State0) of 485 | {ok, State1} -> 486 | set_active_once(State1), 487 | {ok, State1}; 488 | {error, OpenErrorReason} -> {stop, OpenErrorReason} 489 | end. 490 | 491 | %%-------------------------------------------------------------------- 492 | %% @doc Handle a synchronous message. 493 | %% 494 | -spec handle_call(any(), from(), #state{}) -> {noreply, #state{}} | {reply, any(), #state{}}. 495 | handle_call({do_query, Command}, From, #state{} = State0) -> 496 | State1 = do_query(Command, From, State0), 497 | {noreply, State1}; 498 | handle_call(cancel, _From, #state{socket = closed} = State0) -> 499 | {reply, {error, closed}, State0}; 500 | handle_call(cancel, _From, #state{} = State0) -> 501 | Result = oob_cancel(State0), 502 | {reply, Result, State0}. 503 | 504 | %%-------------------------------------------------------------------- 505 | %% @doc Handle an asynchronous message. 506 | %% 507 | -spec handle_cast(any(), #state{}) -> {noreply, #state{}}. 508 | handle_cast({set_parameter, Key, Value}, State0) -> 509 | State1 = handle_parameter(Key, Value, sync, State0), 510 | {noreply, State1}; 511 | handle_cast({socket_closed, Socket}, #state{socket = Socket} = State) -> 512 | {noreply, State#state{socket = closed}}; 513 | handle_cast({socket_closed, _ClosedSocket}, #state{socket = _OtherSocket} = State) -> 514 | {noreply, State}; 515 | handle_cast({command_completed, CurrentCommand}, #state{} = State0) -> 516 | State1 = command_completed(CurrentCommand, State0), 517 | {noreply, State1}; 518 | handle_cast({subscribe, Pid}, #state{subscribers = Subscribers0} = State0) -> 519 | Subscribers1 = do_subscribe(Pid, Subscribers0), 520 | State1 = State0#state{subscribers = Subscribers1}, 521 | {noreply, State1}; 522 | handle_cast({unsubscribe, Pid}, #state{subscribers = Subscribers0} = State0) -> 523 | Subscribers1 = do_unsubscribe(Pid, Subscribers0), 524 | State1 = State0#state{subscribers = Subscribers1}, 525 | {noreply, State1}. 526 | 527 | %%-------------------------------------------------------------------- 528 | %% @doc handle system messages. 529 | %% 530 | -spec handle_info(any(), #state{}) -> {noreply, #state{}} | {stop, any(), #state{}}. 531 | handle_info({'EXIT', _From, normal}, State) -> 532 | {noreply, State}; 533 | handle_info({'EXIT', _From, Reason}, State) -> 534 | {stop, Reason, State}; 535 | handle_info({'DOWN', MonitorRef, process, _Pid, _Info}, #state{subscribers = Subscribers0} = State0) -> 536 | Subscribers1 = lists:keydelete(MonitorRef, 2, Subscribers0), 537 | State1 = State0#state{subscribers = Subscribers1}, 538 | {noreply, State1}; 539 | handle_info({_Tag, Socket, Data}, #state{socket = {_SocketModule, Socket}} = State0) -> 540 | State1 = process_active_data(Data, State0), 541 | set_active_once(State1), 542 | {noreply, State1}; 543 | handle_info({ClosedTag, Socket}, #state{socket = {_SocketModule, Socket}} = State0) when ClosedTag =:= tcp_closed orelse ClosedTag =:= ssl_closed -> 544 | State1 = State0#state{socket = closed}, 545 | {noreply, State1}; 546 | handle_info({ErrorTag, Socket, _SocketError}, #state{socket = {SocketModule, Socket}} = State0) when ErrorTag =:= tcp_error orelse ErrorTag =:= ssl_error -> 547 | _ = SocketModule:close(Socket), 548 | State1 = State0#state{socket = closed}, 549 | {noreply, State1}; 550 | handle_info({Tag, _OtherSocket, _Data}, State0) when Tag =:= tcp orelse Tag =:= ssl -> 551 | {noreply, State0}; 552 | handle_info({ClosedTag, _OtherSocket}, State0) when ClosedTag =:= tcp_closed orelse ClosedTag =:= ssl_closed -> 553 | {noreply, State0}; 554 | handle_info({ErrorTag, _OtherSocket, _SocketError}, State0) when ErrorTag =:= tcp_error orelse ErrorTag =:= ssl_error -> 555 | {noreply, State0}. 556 | 557 | %%-------------------------------------------------------------------- 558 | %% @doc handle code change. 559 | %% 560 | -spec code_change(string() | {down, string()}, any(), any()) -> {ok, #state{}}. 561 | code_change(Vsn, State, Extra) -> 562 | error_logger:info_msg("~p: unknown code_change (~p, ~p, ~p)~n", [?MODULE, Vsn, State, Extra]), 563 | {ok, State}. 564 | 565 | %%-------------------------------------------------------------------- 566 | %% @doc handle termination. 567 | %% 568 | -spec terminate(any(), #state{}) -> ok. 569 | terminate(_Reason, #state{socket = closed}) -> 570 | ok; 571 | terminate(_Reason, #state{socket = {SocketModule, Socket}}) -> 572 | SocketModule:close(Socket), 573 | ok. 574 | 575 | %%-------------------------------------------------------------------- 576 | %% @doc Parse a postgres connection URL into components 577 | %% 578 | %% Example: postgres://username:password@host:port/database 579 | -spec parse_url(iodata()) -> []. 580 | parse_url("postgres://"++_RemainingUrl) -> 581 | [Credentials, HostPortDatabase] = split_to_second_part(string:split(_RemainingUrl, "@")), 582 | [Username, Password] = split_to_first_part(string:split(Credentials, ":")), 583 | [HostPort, Database] = split_to_first_part(string:split(HostPortDatabase, "/")), 584 | [Host, Port] = split_to_first_part(string:split(HostPort, ":")), 585 | ParsedOptionsEmpty = [], 586 | ParsedOptionsWithUserName = case string:is_empty(Username) of 587 | false -> ParsedOptionsEmpty ++ [{user, Username}]; 588 | true -> ParsedOptionsEmpty 589 | end, 590 | ParsedOptionsWithPassword = case string:is_empty(Password) of 591 | false -> ParsedOptionsWithUserName ++ [{password, Password}]; 592 | true -> ParsedOptionsWithUserName 593 | end, 594 | ParsedOptionsWithHost = case string:is_empty(Host) of 595 | false -> ParsedOptionsWithPassword ++ [{host, Host}]; 596 | true -> ParsedOptionsWithPassword 597 | end, 598 | ParsedOptionsWithPort = case string:is_empty(Port) of 599 | false -> case string:to_integer(Port) of 600 | {Portnum, _} -> ParsedOptionsWithHost ++ [{port, Portnum}]; 601 | {error, Reason} -> throw(io_lib:format("Unable to parse port ~p into an integer due to: ~p", Port, Reason)) 602 | end; 603 | true -> ParsedOptionsWithHost 604 | end, 605 | ParsedOptionsWithDatabase = case string:is_empty(Database) of 606 | false -> ParsedOptionsWithPort ++ [{database, Database}]; 607 | true -> ParsedOptionsWithPort 608 | end, 609 | ParsedOptionsWithDatabase. 610 | 611 | %%==================================================================== 612 | %% Private functions 613 | %%==================================================================== 614 | 615 | 616 | %% @doc splits string, but when unsplittable, assigns result 617 | %% to second part. First part is an empty string (""). 618 | split_to_second_part([OnlyOne]) -> 619 | ["", OnlyOne]; 620 | split_to_second_part([One, Two]) -> 621 | [One, Two]. 622 | 623 | %% @doc splits string, but when unsplittable, assigns result 624 | %% to second part. First part is an empty string (""). 625 | split_to_first_part([OnlyOne]) -> 626 | [OnlyOne, ""]; 627 | split_to_first_part([One, Two]) -> 628 | [One, Two]. 629 | 630 | %%-------------------------------------------------------------------- 631 | %% @doc Actually open (or re-open) the connection. 632 | %% 633 | pgsql_open(#state{options = RawOptions} = RawState0) -> 634 | Url = proplists:get_value(url, RawOptions, ""), 635 | NotEmpty = not string:is_empty(Url), 636 | Options = if NotEmpty -> 637 | ParsedFromUrlOptions = parse_url(Url), 638 | OriginalWithoutUrlOptions = proplists:delete(url, RawOptions), 639 | UnifiedOptions = ParsedFromUrlOptions++OriginalWithoutUrlOptions, 640 | UnifiedOptions; 641 | true -> RawOptions 642 | end, 643 | State0=#state{ 644 | options = Options, 645 | socket = RawState0#state.socket, 646 | subscribers = RawState0#state.subscribers, 647 | backend_procid = RawState0#state.backend_procid, 648 | backend_secret = RawState0#state.backend_secret, 649 | integer_datetimes = RawState0#state.integer_datetimes, 650 | oidmap = RawState0#state.oidmap, 651 | current = RawState0#state.current, 652 | pending = RawState0#state.pending, 653 | statement_timeout = RawState0#state.statement_timeout 654 | }, 655 | Host = proplists:get_value(host, Options, ?DEFAULT_HOST), 656 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 657 | % First open a TCP connection 658 | case gen_tcp:connect(Host, Port, [binary, {packet, raw}, {active, false}]) of 659 | {ok, Sock} -> 660 | case pgsql_setup(Sock, State0) of 661 | {ok, State1} -> 662 | case proplists:get_value(fetch_oid_map, Options, true) of 663 | true -> 664 | State2 = update_oid_map(State1), 665 | {ok, State2}; 666 | false -> 667 | {ok, State1} 668 | end; 669 | {error, _} = SetupError -> SetupError 670 | end; 671 | {error, _} = ConnectError -> ConnectError 672 | end. 673 | 674 | %%-------------------------------------------------------------------- 675 | %% @doc Setup the connection, handling the authentication handshake. 676 | %% 677 | -spec pgsql_setup(port(), #state{}) -> {ok, #state{}} | {error, any()}. 678 | pgsql_setup(Sock, #state{options = Options} = State0) -> 679 | case proplists:get_bool(ssl, Options) of 680 | false -> 681 | pgsql_setup_startup(State0#state{socket = {gen_tcp, Sock}}); 682 | true -> 683 | pgsql_setup_ssl(Sock, State0) 684 | end. 685 | 686 | pgsql_setup_ssl(Sock, #state{options = Options} = State0) -> 687 | SSLRequestMessage = pgsql_protocol:encode_ssl_request_message(), 688 | case gen_tcp:send(Sock, SSLRequestMessage) of 689 | ok -> 690 | case gen_tcp:recv(Sock, 1) of 691 | {ok, <<$S>>} -> 692 | % upgrade socket. 693 | SSLOptions = proplists:get_value(ssl_options, Options, []), 694 | case ssl:connect(Sock, [binary, {packet, raw}, {active, false}] ++ SSLOptions) of 695 | {ok, SSLSocket} -> 696 | pgsql_setup_startup(State0#state{socket = {ssl, SSLSocket}}); 697 | {error, _} = SSLConnectErr -> SSLConnectErr 698 | end; 699 | {ok, <<$N>>} -> 700 | % server is unwilling 701 | {error, ssl_refused} 702 | end; 703 | {error, _} = SendSSLRequestError -> SendSSLRequestError 704 | end. 705 | 706 | pgsql_setup_startup(#state{socket = {SockModule, Sock} = Socket, options = Options, subscribers = Subscribers} = State0) -> 707 | % Send startup packet connection packet. 708 | User = proplists:get_value(user, Options, ?DEFAULT_USER), 709 | Database = proplists:get_value(database, Options, User), 710 | ApplicationName = case proplists:get_value(application_name, Options, node()) of 711 | ApplicationNameAtom when is_atom(ApplicationNameAtom) -> atom_to_binary(ApplicationNameAtom, utf8); 712 | ApplicationNameString -> ApplicationNameString 713 | end, 714 | TZOpt = case proplists:get_value(timezone, Options, undefined) of 715 | undefined -> []; 716 | Timezone -> [{<<"timezone">>, Timezone}] 717 | end, 718 | StartupMessage = pgsql_protocol:encode_startup_message([{<<"user">>, User}, {<<"database">>, Database}, {<<"application_name">>, ApplicationName} | TZOpt]), 719 | case SockModule:send(Sock, StartupMessage) of 720 | ok -> 721 | case receive_message(Socket, sync, Subscribers) of 722 | {ok, #error_response{fields = Fields}} -> 723 | {error, {pgsql_error, Fields}}; 724 | {ok, #authentication_ok{}} -> 725 | pgsql_setup_finish(Socket, State0); 726 | {ok, #authentication_kerberos_v5{}} -> 727 | {error, {unimplemented, authentication_kerberos_v5}}; 728 | {ok, #authentication_cleartext_password{}} -> 729 | pgsql_setup_authenticate_cleartext_password(Socket, State0); 730 | {ok, #authentication_md5_password{salt = Salt}} -> 731 | pgsql_setup_authenticate_md5_password(Socket, Salt, State0); 732 | {ok, #authentication_scm_credential{}} -> 733 | {error, {unimplemented, authentication_scm}}; 734 | {ok, #authentication_gss{}} -> 735 | {error, {unimplemented, authentication_gss}}; 736 | {ok, #authentication_sspi{}} -> 737 | {error, {unimplemented, authentication_sspi}}; 738 | {ok, #authentication_gss_continue{}} -> 739 | {error, {unimplemented, authentication_sspi}}; 740 | {ok, Message} -> 741 | {error, {unexpected_message, Message}}; 742 | {error, _} = ReceiveError -> ReceiveError 743 | end; 744 | {error, _} = SendError -> SendError 745 | end. 746 | 747 | pgsql_setup_authenticate_cleartext_password(Socket, #state{options = Options} = State0) -> 748 | Password = proplists:get_value(password, Options, ?DEFAULT_PASSWORD), 749 | pgsql_setup_authenticate_password(Socket, Password, State0). 750 | 751 | -ifndef(old_hash). 752 | pgsql_setup_authenticate_md5_password(Socket, Salt, #state{options = Options} = State0) -> 753 | User = proplists:get_value(user, Options, ?DEFAULT_USER), 754 | Password = proplists:get_value(password, Options, ?DEFAULT_PASSWORD), 755 | % concat('md5', md5(concat(md5(concat(password, username)), random-salt))) 756 | <> = crypto:hash(md5, [Password, User]), 757 | MD51Hex = io_lib:format("~32.16.0b", [MD51Int]), 758 | <> = crypto:hash(md5, [MD51Hex, Salt]), 759 | MD52Hex = io_lib:format("~32.16.0b", [MD52Int]), 760 | MD5ChallengeResponse = ["md5", MD52Hex], 761 | pgsql_setup_authenticate_password(Socket, MD5ChallengeResponse, State0). 762 | -else. 763 | pgsql_setup_authenticate_md5_password(Socket, Salt, #state{options = Options} = State0) -> 764 | User = proplists:get_value(user, Options, ?DEFAULT_USER), 765 | Password = proplists:get_value(password, Options, ?DEFAULT_PASSWORD), 766 | % concat('md5', md5(concat(md5(concat(password, username)), random-salt))) 767 | <> = crypto:md5([Password, User]), 768 | MD51Hex = io_lib:format("~32.16.0b", [MD51Int]), 769 | <> = crypto:md5([MD51Hex, Salt]), 770 | MD52Hex = io_lib:format("~32.16.0b", [MD52Int]), 771 | MD5ChallengeResponse = ["md5", MD52Hex], 772 | pgsql_setup_authenticate_password(Socket, MD5ChallengeResponse, State0). 773 | -endif. 774 | 775 | pgsql_setup_authenticate_password({SockModule, Sock} = Socket, Password, #state{subscribers = Subscribers} = State0) -> 776 | Message = pgsql_protocol:encode_password_message(Password), 777 | case SockModule:send(Sock, Message) of 778 | ok -> 779 | case receive_message(Socket, sync, Subscribers) of 780 | {ok, #error_response{fields = Fields}} -> 781 | {error, {pgsql_error, Fields}}; 782 | {ok, #authentication_ok{}} -> 783 | pgsql_setup_finish(Socket, State0); 784 | {ok, UnexpectedMessage} -> 785 | {error, {unexpected_message, UnexpectedMessage}}; 786 | {error, _} = ReceiveError -> ReceiveError 787 | end; 788 | {error, _} = SendError -> SendError 789 | end. 790 | 791 | pgsql_setup_finish(Socket, #state{subscribers = Subscribers} = State0) -> 792 | case receive_message(Socket, sync, Subscribers) of 793 | {ok, #parameter_status{name = Name, value = Value}} -> 794 | State1 = handle_parameter(Name, Value, sync, State0), 795 | pgsql_setup_finish(Socket, State1); 796 | {ok, #backend_key_data{procid = ProcID, secret = Secret}} -> 797 | pgsql_setup_finish(Socket, State0#state{backend_procid = ProcID, backend_secret = Secret}); 798 | {ok, #ready_for_query{}} -> 799 | {ok, State0}; 800 | {ok, #error_response{fields = Fields}} -> 801 | {error, {pgsql_error, Fields}}; 802 | {ok, Message} -> 803 | {error, {unexpected_message, Message}}; 804 | {error, _} = ReceiveError -> ReceiveError 805 | end. 806 | 807 | pgsql_simple_query(Query, QueryOptions, Timeout, From, #state{socket = {SockModule, Sock}} = State0) -> 808 | % If timeout is not infinity, change the parameter before and after the 809 | % query. While we could catenate the query, it seems easier to send 810 | % separate query messages, as we don't have to deal with errors. 811 | ConnPid = self(), 812 | CurrentCommand = State0#state.current, 813 | case Timeout of 814 | infinity -> 815 | spawn_link(fun() -> 816 | pgsql_simple_query0(Query, {async, ConnPid, fun(Result) -> 817 | gen_server:reply(From, Result), 818 | gen_server:cast(ConnPid, {command_completed, CurrentCommand}) 819 | end}, QueryOptions, State0) 820 | end), 821 | State0; 822 | Value -> 823 | Queries = [ 824 | io_lib:format("set statement_timeout = ~B", [Value]), 825 | Query, 826 | "set statement_timeout to default"], 827 | SinglePacket = [pgsql_protocol:encode_query_message(AQuery) || AQuery <- Queries], 828 | case SockModule:send(Sock, SinglePacket) of 829 | ok -> 830 | {SetResult, State1} = pgsql_simple_query_loop([], [], sync, QueryOptions, State0), 831 | true = set_succeeded_or_within_failed_transaction(SetResult), 832 | spawn_link(fun() -> 833 | pgsql_simple_query_loop([], [], {async, ConnPid, fun(QueryResult) -> 834 | pgsql_simple_query_loop([], [], {async, ConnPid, fun(ResetResult) -> 835 | true = set_succeeded_or_within_failed_transaction(ResetResult), 836 | gen_server:reply(From, QueryResult), 837 | gen_server:cast(ConnPid, {command_completed, CurrentCommand}) 838 | end}, QueryOptions, State1) 839 | end}, QueryOptions, State1) 840 | end), 841 | State1; 842 | {error, closed} = SendQueryError -> 843 | gen_server:reply(From, SendQueryError), 844 | State1 = State0#state{socket = closed}, 845 | command_completed(CurrentCommand, State1); 846 | {error, _} = SendQueryError -> 847 | gen_server:reply(From, SendQueryError), 848 | command_completed(CurrentCommand, State0) 849 | end 850 | end. 851 | 852 | % This function should always return true as set or reset may only fail because 853 | % we are within a failed transaction. 854 | % If set failed because the transaction was aborted, the query will fail 855 | % (unless it is a rollback). 856 | % If set succeeded within a transaction, but the query failed, the reset may 857 | % fail but set only applies to the transaction anyway. 858 | -spec set_succeeded_or_within_failed_transaction({set, []} | {error, pgsql_error:pgsql_error()}) -> boolean(). 859 | set_succeeded_or_within_failed_transaction({set, []}) -> true; 860 | set_succeeded_or_within_failed_transaction({error, {pgsql_error, _} = Error}) -> 861 | pgsql_error:is_in_failed_sql_transaction(Error). 862 | 863 | -spec pgsql_simple_query0(iodata(), sync, query_options(), #state{}) -> {tuple(), #state{}}; 864 | (iodata(), {async, pid(), fun((any()) -> ok)}, query_options(), #state{}) -> ok. 865 | pgsql_simple_query0(Query, AsyncT, QueryOptions, #state{socket = {SockModule, Sock}} = State) -> 866 | QueryMessage = pgsql_protocol:encode_query_message(Query), 867 | case SockModule:send(Sock, QueryMessage) of 868 | ok -> pgsql_simple_query_loop([], [], AsyncT, QueryOptions, State); 869 | {error, _} = SendQueryError -> 870 | return_async(SendQueryError, AsyncT, State) 871 | end. 872 | 873 | pgsql_simple_query_loop(Result0, Acc, AsyncT, QueryOptions, #state{socket = Socket, subscribers = Subscribers} = State0) -> 874 | case receive_message(Socket, AsyncT, Subscribers) of 875 | {ok, #parameter_status{name = Name, value = Value}} -> 876 | State1 = handle_parameter(Name, Value, AsyncT, State0), 877 | pgsql_simple_query_loop(Result0, Acc, AsyncT, QueryOptions, State1); 878 | {ok, #row_description{fields = Fields}} when Result0 =:= [] -> 879 | State1 = oob_update_oid_map_from_fields_if_required(Fields, State0), 880 | pgsql_simple_query_loop({rows, Fields, []}, Acc, AsyncT, QueryOptions, State1); 881 | {ok, #data_row{values = Values}} when is_tuple(Result0) andalso element(1, Result0) =:= rows -> 882 | {rows, Fields, AccRows0} = Result0, 883 | DecodedRow = pgsql_protocol:decode_row(Fields, Values, State0#state.oidmap, [{integer_datetimes, State0#state.integer_datetimes} | QueryOptions]), 884 | AccRows1 = [DecodedRow | AccRows0], 885 | pgsql_simple_query_loop({rows, Fields, AccRows1}, Acc, AsyncT, QueryOptions, State0); 886 | {ok, #copy_out_response{format = Format}} when Result0 =:= [] -> 887 | Fields = [Format], 888 | pgsql_simple_query_loop({copy, Fields, []}, Acc, AsyncT, QueryOptions, State0); 889 | {ok, #copy_data{data = Data}} when is_tuple(Result0) andalso element(1, Result0) =:= copy -> 890 | {copy, Fields, AccData0} = Result0, 891 | AccData1 = [Data | AccData0], 892 | pgsql_simple_query_loop({copy, Fields, AccData1}, Acc, AsyncT, QueryOptions, State0); 893 | {ok, #copy_done{}} -> 894 | pgsql_simple_query_loop(Result0, Acc, AsyncT, QueryOptions, State0); 895 | {ok, #copy_in_response{format = Format}} when Result0 =:= [] -> 896 | Fields = [Format], 897 | return_async({copy_in, Fields}, AsyncT, State0); 898 | {ok, #command_complete{command_tag = Tag}} -> 899 | ResultRows = case Result0 of 900 | {rows, _Descs, AccRows} -> lists:reverse(AccRows); 901 | {copy, _Descs, AccData} -> lists:reverse(AccData); 902 | [] -> [] 903 | end, 904 | DecodedTag = decode_tag(Tag), 905 | Result = case proplists:get_bool(return_descriptions, QueryOptions) of 906 | true when is_tuple(Result0) -> {DecodedTag, element(2, Result0), ResultRows}; 907 | true when Result0 =:= [] -> {DecodedTag, [], []}; 908 | false -> {DecodedTag, ResultRows} 909 | end, 910 | Acc1 = [Result | Acc], 911 | pgsql_simple_query_loop([], Acc1, AsyncT, QueryOptions, State0); 912 | {ok, #empty_query_response{}} -> 913 | pgsql_simple_query_loop(Result0, Acc, AsyncT, QueryOptions, State0); 914 | {ok, #error_response{fields = Fields}} -> 915 | Error = {error, {pgsql_error, Fields}}, 916 | Acc1 = [Error | Acc], 917 | pgsql_simple_query_loop([], Acc1, AsyncT, QueryOptions, State0); 918 | {ok, #ready_for_query{}} -> 919 | Result = case Acc of 920 | [SingleResult] -> SingleResult; 921 | MultipleResults -> MultipleResults 922 | end, 923 | return_async(Result, AsyncT, State0); 924 | {ok, Message} -> 925 | Result = {error, {unexpected_message, Message}}, 926 | return_async(Result, AsyncT, State0); 927 | {error, _} = ReceiveError -> 928 | return_async(ReceiveError, AsyncT, State0) 929 | end. 930 | 931 | pgsql_extended_query(Query, Parameters, Fun, Acc0, FinalizeFun, Mode, QueryOptions, Timeout, From, State0) -> 932 | % If timeout is not infinity, change the parameter before and after the 933 | % query. While we could catenate the query, it seems easier to send 934 | % separate query messages, as we don't have to deal with errors. 935 | ConnPid = self(), 936 | CurrentCommand = State0#state.current, 937 | case Timeout of 938 | infinity -> 939 | spawn_link(fun() -> 940 | pgsql_extended_query0(Query, Parameters, Fun, Acc0, FinalizeFun, Mode, {async, ConnPid, fun(Result) -> 941 | gen_server:reply(From, Result), 942 | gen_server:cast(ConnPid, {command_completed, CurrentCommand}) 943 | end}, QueryOptions, State0) 944 | end), 945 | State0; 946 | Value -> 947 | {SetResult, State1} = pgsql_simple_query0(io_lib:format("set statement_timeout = ~B", [Value]), sync, [], State0), 948 | true = set_succeeded_or_within_failed_transaction(SetResult), 949 | spawn_link(fun() -> 950 | pgsql_extended_query0(Query, Parameters, Fun, Acc0, FinalizeFun, Mode, {async, ConnPid, fun(QueryResult) -> 951 | pgsql_simple_query0("set statement_timeout to default", {async, ConnPid, fun(ResetResult) -> 952 | true = set_succeeded_or_within_failed_transaction(ResetResult), 953 | gen_server:reply(From, QueryResult), 954 | gen_server:cast(ConnPid, {command_completed, CurrentCommand}) 955 | end}, [], State1) 956 | end}, QueryOptions, State1) 957 | end), 958 | State1 959 | end. 960 | 961 | -spec pgsql_extended_query0(iodata(), [any()], fun(), any(), fun(), all | batch | {cursor, non_neg_integer()}, sync, query_options(), #state{}) -> {any(), #state{}}; 962 | (iodata(), [any()], fun(), any(), fun(), all | batch | {cursor, non_neg_integer()}, {async, pid(), fun((any()) -> ok)}, query_options(), #state{}) -> ok. 963 | pgsql_extended_query0(Query, Parameters, Fun, Acc0, FinalizeFun, Mode, AsyncT, QueryOptions, #state{socket = {SockModule, Sock}, oidmap = OIDMap, integer_datetimes = IntegerDateTimes} = State) -> 964 | ParseMessage = pgsql_protocol:encode_parse_message("", Query, []), 965 | % We ask for a description of parameters only if required. 966 | NeedStatementDescription = requires_statement_description(Mode, Parameters), 967 | PacketT = case NeedStatementDescription of 968 | true -> 969 | DescribeStatementMessage = pgsql_protocol:encode_describe_message(statement, ""), 970 | FlushMessage = pgsql_protocol:encode_flush_message(), 971 | LoopState0 = {parse_complete_with_params, Mode, Parameters}, 972 | {ok, [ParseMessage, DescribeStatementMessage, FlushMessage], LoopState0}; 973 | false -> 974 | case encode_bind_describe_execute(Mode, Parameters, [], OIDMap, IntegerDateTimes) of 975 | {ok, BindExecute} -> 976 | {ok, [ParseMessage, BindExecute], parse_complete}; 977 | {error, _} = Error -> Error 978 | end 979 | end, 980 | case PacketT of 981 | {ok, SinglePacket, LoopState} -> 982 | case SockModule:send(Sock, SinglePacket) of 983 | ok -> 984 | case Mode of 985 | batch -> 986 | {_, ResultRL, FinalState} = lists:foldl(fun(_ParametersBatch, {AccLoopState, AccResults, AccState}) -> 987 | {Result, AccState1} = pgsql_extended_query_receive_loop(AccLoopState, Fun, Acc0, FinalizeFun, 0, sync, QueryOptions, AccState), 988 | {bind_complete, [Result | AccResults], AccState1} 989 | end, {LoopState, [], State}, Parameters), 990 | Result = lists:reverse(ResultRL), 991 | return_async(Result, AsyncT, FinalState); 992 | all -> 993 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, 0, AsyncT, QueryOptions, State); 994 | {cursor, MaxRowsStep} -> 995 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State) 996 | end; 997 | {error, _} = SendSinglePacketError -> 998 | return_async(SendSinglePacketError, AsyncT, State) 999 | end; 1000 | {error, _} -> 1001 | return_async(PacketT, AsyncT, State) 1002 | end. 1003 | 1004 | -spec encode_bind_describe_execute(all | {cursor, non_neg_integer()}, [any()], [pgsql_oid()], pgsql_oid_map(), boolean()) -> {ok, iodata()} | {error, any()}; 1005 | (batch, [[any()]], [pgsql_oid()], pgsql_oid_map(), boolean()) -> {ok, iodata()} | {error, any()}. 1006 | encode_bind_describe_execute(Mode, Parameters, ParameterDataTypes, OIDMap, IntegerDateTimes) -> 1007 | DescribeMessage = pgsql_protocol:encode_describe_message(portal, ""), 1008 | MaxRowsStep = case Mode of 1009 | all -> 0; 1010 | batch -> 0; 1011 | {cursor, MaxRowsStep0} -> MaxRowsStep0 1012 | end, 1013 | ExecuteMessage = pgsql_protocol:encode_execute_message("", MaxRowsStep), 1014 | SyncOrFlushMessage = if 1015 | MaxRowsStep > 0 -> pgsql_protocol:encode_flush_message(); 1016 | true -> pgsql_protocol:encode_sync_message() 1017 | end, 1018 | try 1019 | SinglePacket = case Mode of 1020 | batch -> 1021 | [ 1022 | [pgsql_protocol:encode_bind_message("", "", ParametersBatch, ParameterDataTypes, OIDMap, IntegerDateTimes), 1023 | DescribeMessage, ExecuteMessage, SyncOrFlushMessage] || ParametersBatch <- Parameters]; 1024 | _ -> 1025 | BindMessage = pgsql_protocol:encode_bind_message("", "", Parameters, ParameterDataTypes, OIDMap, IntegerDateTimes), 1026 | [BindMessage, DescribeMessage, ExecuteMessage, SyncOrFlushMessage] 1027 | end, 1028 | {ok, SinglePacket} 1029 | catch throw:Exception -> 1030 | {error, Exception} 1031 | end. 1032 | 1033 | requires_statement_description(batch, ParametersL) -> 1034 | lists:any(fun pgsql_protocol:bind_requires_statement_description/1, ParametersL); 1035 | requires_statement_description(_Mode, Parameters) -> 1036 | pgsql_protocol:bind_requires_statement_description(Parameters). 1037 | 1038 | -spec pgsql_extended_query_receive_loop(extended_query_loop_state(), fun(), any(), fun(), non_neg_integer(), sync, query_options(), #state{}) -> {any(), #state{}}; 1039 | (extended_query_loop_state(), fun(), any(), fun(), non_neg_integer(), {async, pid(), fun((any()) -> ok)}, query_options(), #state{}) -> ok. 1040 | pgsql_extended_query_receive_loop(_LoopState, _Fun, _Acc, _FinalizeFun, _MaxRowsStep, AsyncT, _QueryOptions, #state{socket = closed} = State0) -> 1041 | return_async({error, closed}, AsyncT, State0); 1042 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, #state{socket = Socket, subscribers = Subscribers} = State0) -> 1043 | case receive_message(Socket, AsyncT, Subscribers) of 1044 | {ok, Message} -> 1045 | pgsql_extended_query_receive_loop0(Message, LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1046 | {error, _} = ReceiveError -> 1047 | return_async(ReceiveError, AsyncT, State0) 1048 | end. 1049 | 1050 | -spec pgsql_extended_query_receive_loop0(pgsql_backend_message(), extended_query_loop_state(), fun(), any(), fun(), non_neg_integer(), sync, query_options(), #state{}) -> {any(), query_options(), #state{}}; 1051 | (pgsql_backend_message(), extended_query_loop_state(), fun(), any(), fun(), non_neg_integer(), {async, pid(), fun((any()) -> ok)}, query_options(), #state{}) -> ok. 1052 | pgsql_extended_query_receive_loop0(#parameter_status{name = Name, value = Value}, LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1053 | State1 = handle_parameter(Name, Value, AsyncT, State0), 1054 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State1); 1055 | pgsql_extended_query_receive_loop0(#parse_complete{}, parse_complete, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1056 | pgsql_extended_query_receive_loop(bind_complete, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1057 | 1058 | % Path where we ask the backend about what it expects. 1059 | % We ignore row descriptions sent before bind as the format codes are null. 1060 | pgsql_extended_query_receive_loop0(#parse_complete{}, {parse_complete_with_params, Mode, Parameters}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1061 | pgsql_extended_query_receive_loop({parameter_description_with_params, Mode, Parameters}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1062 | pgsql_extended_query_receive_loop0(#parameter_description{data_types = ParameterDataTypes}, {parameter_description_with_params, Mode, Parameters}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, #state{socket = {SockModule, Sock}} = State0) -> 1063 | State1 = oob_update_oid_map_if_required(ParameterDataTypes, State0), 1064 | PacketT = encode_bind_describe_execute(Mode, Parameters, ParameterDataTypes, State1#state.oidmap, State1#state.integer_datetimes), 1065 | case PacketT of 1066 | {ok, SinglePacket} -> 1067 | case SockModule:send(Sock, SinglePacket) of 1068 | ok -> 1069 | pgsql_extended_query_receive_loop(pre_bind_row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State1); 1070 | {error, _} = SendError -> 1071 | return_async(SendError, AsyncT, State1) 1072 | end; 1073 | {error, _} = Error -> 1074 | case SockModule:send(Sock, pgsql_protocol:encode_sync_message()) of 1075 | ok -> flush_until_ready_for_query(Error, AsyncT, State1); 1076 | {error, _} = SendSyncPacketError -> return_async(SendSyncPacketError, AsyncT, State1) 1077 | end 1078 | end; 1079 | pgsql_extended_query_receive_loop0(#row_description{}, pre_bind_row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1080 | pgsql_extended_query_receive_loop(bind_complete, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1081 | pgsql_extended_query_receive_loop0(#no_data{}, pre_bind_row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1082 | pgsql_extended_query_receive_loop(bind_complete, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1083 | 1084 | % Common paths after bind. 1085 | pgsql_extended_query_receive_loop0(#bind_complete{}, bind_complete, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1086 | pgsql_extended_query_receive_loop(row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1087 | pgsql_extended_query_receive_loop0(#no_data{}, row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1088 | pgsql_extended_query_receive_loop(no_data, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1089 | pgsql_extended_query_receive_loop0(#row_description{fields = Fields}, row_description, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1090 | State1 = oob_update_oid_map_from_fields_if_required(Fields, State0), 1091 | pgsql_extended_query_receive_loop({rows, Fields}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State1); 1092 | pgsql_extended_query_receive_loop0(#data_row{values = Values}, {rows, Fields} = LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1093 | DecodedRow = pgsql_protocol:decode_row(Fields, Values, State0#state.oidmap, [{integer_datetimes, State0#state.integer_datetimes} | QueryOptions]), 1094 | Acc1 = Fun(DecodedRow, Fields, QueryOptions, Acc0), 1095 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc1, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1096 | pgsql_extended_query_receive_loop0(#copy_out_response{format = Format}, _LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1097 | Fields = [Format], 1098 | pgsql_extended_query_receive_loop({copy, Fields}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1099 | pgsql_extended_query_receive_loop0(#copy_data{data = Data}, {copy, Fields} = LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1100 | Acc1 = Fun(Data, Fields, QueryOptions, Acc0), 1101 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc1, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1102 | pgsql_extended_query_receive_loop0(#copy_done{}, LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) -> 1103 | pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1104 | pgsql_extended_query_receive_loop0(#command_complete{command_tag = Tag}, _LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, #state{socket = {SockModule, Sock}} = State0) -> 1105 | Result = FinalizeFun(Tag, QueryOptions, Acc0), 1106 | if MaxRowsStep > 0 -> 1107 | case SockModule:send(Sock, pgsql_protocol:encode_sync_message()) of 1108 | ok -> pgsql_extended_query_receive_loop({result, Result}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1109 | {error, _} = SendSyncPacketError -> return_async(SendSyncPacketError, AsyncT, State0) 1110 | end; 1111 | true -> pgsql_extended_query_receive_loop({result, Result}, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0) 1112 | end; 1113 | pgsql_extended_query_receive_loop0(#portal_suspended{}, LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, #state{socket = {SockModule, Sock}} = State0) -> 1114 | ExecuteMessage = pgsql_protocol:encode_execute_message("", MaxRowsStep), 1115 | FlushMessage = pgsql_protocol:encode_flush_message(), 1116 | SinglePacket = [ExecuteMessage, FlushMessage], 1117 | case SockModule:send(Sock, SinglePacket) of 1118 | ok -> pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1119 | {error, _} = SendSinglePacketError -> 1120 | return_async(SendSinglePacketError, AsyncT, State0) 1121 | end; 1122 | pgsql_extended_query_receive_loop0(#ready_for_query{}, {result, Result}, _Fun, _Acc0, _FinalizeFun, _MaxRowsStep, AsyncT, _QueryOptions, State0) -> 1123 | return_async(Result, AsyncT, State0); 1124 | pgsql_extended_query_receive_loop0(#copy_in_response{}, LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, #state{socket={SockModule,Sock}}=State0) -> 1125 | ErrorMessage = <<"Cannot use COPY with extended_query">>, 1126 | Packet = [pgsql_protocol:encode_copy_fail(ErrorMessage),pgsql_protocol:encode_sync_message()], 1127 | Res= SockModule:send(Sock, Packet), 1128 | case Res of 1129 | ok -> pgsql_extended_query_receive_loop(LoopState, Fun, Acc0, FinalizeFun, MaxRowsStep, AsyncT, QueryOptions, State0); 1130 | {error,_} = SendError -> return_async(SendError, AsyncT, State0) 1131 | end; 1132 | pgsql_extended_query_receive_loop0(#error_response{fields = Fields}, LoopState, _Fun, _Acc0, _FinalizeFun, MaxRowsStep, AsyncT, _QueryOptions, #state{socket = {SockModule, Sock}} = State0) -> 1133 | Error = {error, {pgsql_error, Fields}}, 1134 | % We already sent a Sync except when we sent a Flush :-) 1135 | % - when we asked for the statement description 1136 | % - when MaxRowsStep > 0 1137 | NeedSync = case LoopState of 1138 | {parse_complete_with_params, _Mode, _Args} -> true; 1139 | {parameter_description_with_params, _Mode, _Parameters} -> true; 1140 | _ when MaxRowsStep > 0 -> true; 1141 | _ -> false 1142 | end, 1143 | case NeedSync of 1144 | true -> 1145 | case SockModule:send(Sock, pgsql_protocol:encode_sync_message()) of 1146 | ok -> flush_until_ready_for_query(Error, AsyncT, State0); 1147 | {error, _} = SendSyncPacketError -> return_async(SendSyncPacketError, AsyncT, State0) 1148 | end; 1149 | false -> 1150 | flush_until_ready_for_query(Error, AsyncT, State0) 1151 | end; 1152 | pgsql_extended_query_receive_loop0(#ready_for_query{} = Message, _LoopState, _Fun, _Acc0, _FinalizeFun, _MaxRowsStep, AsyncT, _QueryOptions, State0) -> 1153 | Result = {error, {unexpected_message, Message}}, 1154 | return_async(Result, AsyncT, State0); 1155 | pgsql_extended_query_receive_loop0(Message, _LoopState, _Fun, _Acc0, _FinalizeFun, _MaxRowsStep, AsyncT, _QueryOptions, State0) -> 1156 | Error = {error, {unexpected_message, Message}}, 1157 | flush_until_ready_for_query(Error, AsyncT, State0). 1158 | 1159 | pgsql_send_copy_data(Data, From, #state{socket = {SockModule, Sock}} = State0) -> 1160 | Message = pgsql_protocol:encode_copy_data_message(Data), 1161 | Result = SockModule:send(Sock, Message), 1162 | gen_server:reply(From, Result), 1163 | State0#state{current = undefined}. 1164 | 1165 | pgsql_send_copy_end(From, #state{socket = {SockModule, Sock}} = State0) -> 1166 | Message = pgsql_protocol:encode_copy_done(), 1167 | Result0 = SockModule:send(Sock, Message), 1168 | {Result1, State1} = pgsql_send_copy_end_flush(Result0, State0), 1169 | gen_server:reply(From, Result1), 1170 | State1. 1171 | pgsql_send_copy_end_flush(Result0, #state{socket = Socket, subscribers = Subscribers} = State0) -> 1172 | case receive_message(Socket, sync, Subscribers) of 1173 | {ok, {command_complete, <<"COPY ",CopyCount/binary>>}} -> 1174 | CopyCountNum = ?binary_to_integer(CopyCount), 1175 | pgsql_send_copy_end_flush({copy,CopyCountNum}, State0); 1176 | {ok, {ready_for_query, _}} -> 1177 | {Result0, State0#state{current = undefined}} 1178 | end. 1179 | 1180 | flush_until_ready_for_query(Result, AsyncT, #state{socket = Socket, subscribers = Subscribers} = State0) -> 1181 | case receive_message(Socket, AsyncT, Subscribers) of 1182 | {ok, #parameter_status{name = Name, value = Value}} -> 1183 | State1 = handle_parameter(Name, Value, AsyncT, State0), 1184 | flush_until_ready_for_query(Result, AsyncT, State1); 1185 | {ok, #ready_for_query{}} -> 1186 | return_async(Result, AsyncT, State0); 1187 | {ok, _OtherMessage} -> 1188 | flush_until_ready_for_query(Result, AsyncT, State0); 1189 | {error, _} = ReceiveError -> 1190 | return_async(ReceiveError, AsyncT, State0) 1191 | end. 1192 | 1193 | -spec return_async(any(), sync, #state{}) -> {any(), #state{}}; 1194 | (any(), {async, pid(), fun((any()) -> ok)}, #state{}) -> ok. 1195 | return_async({error, closed} = Error, sync, #state{} = State) -> 1196 | {Error, State#state{current = undefined, socket = closed}}; 1197 | return_async({error, closed} = Error, {async, ConnPid, Callback}, #state{socket = Socket}) -> 1198 | ok = Callback(Error), 1199 | gen_server:cast(ConnPid, {socket_closed, Socket}); 1200 | return_async(Result, sync, #state{} = State) -> 1201 | {Result, State}; 1202 | return_async(Result, {async, _ConnPid, Callback}, #state{}) -> 1203 | Callback(Result). 1204 | 1205 | extended_query_fn(Row, RowDescs, _QueryOptions, {_PreviousDescs, AccRows}) -> 1206 | {RowDescs, [Row | AccRows]}. 1207 | 1208 | extended_query_finalize(Tag, QueryOptions, {RowDescs, AccRows}) -> 1209 | DecodedTag = decode_tag(Tag), 1210 | Rows = lists:reverse(AccRows), 1211 | case proplists:get_bool(return_descriptions, QueryOptions) of 1212 | true -> 1213 | {DecodedTag, RowDescs, Rows}; 1214 | false -> 1215 | {DecodedTag, Rows} 1216 | end. 1217 | 1218 | fold_finalize(_Tag, _QueryOptions, Acc) -> 1219 | {ok, Acc}. 1220 | 1221 | map_fn(Row, _RowDesc, _QueryOptions, {Function, Acc}) -> {Function, [Function(Row) | Acc]}. 1222 | 1223 | map_finalize(_Tag, _QueryOptions, {_Function, Acc}) -> 1224 | {ok, lists:reverse(Acc)}. 1225 | 1226 | foreach_fn(Row, _RowDesc, _QueryOptions, Function) -> 1227 | Function(Row), 1228 | Function. 1229 | 1230 | foreach_finalize(_Tag, _QueryOptions, _Function) -> 1231 | ok. 1232 | 1233 | %%-------------------------------------------------------------------- 1234 | %% @doc Handle parameter status messages. These can happen anytime. 1235 | %% 1236 | handle_parameter(<<"integer_datetimes">> = Key, <<"on">> = Value, AsyncT, State0) -> 1237 | set_parameter_async(Key, Value, AsyncT), 1238 | State0#state{integer_datetimes = true}; 1239 | handle_parameter(<<"integer_datetimes">> = Key, <<"off">> = Value, AsyncT, State0) -> 1240 | set_parameter_async(Key, Value, AsyncT), 1241 | State0#state{integer_datetimes = false}; 1242 | handle_parameter(_Key, _Value, _AsyncT, State0) -> State0. 1243 | 1244 | set_parameter_async(_Key, _Value, sync) -> ok; 1245 | set_parameter_async(Key, Value, {async, ConnPid, _}) -> 1246 | gen_server:cast(ConnPid, {set_parameter, Key, Value}). 1247 | 1248 | %%-------------------------------------------------------------------- 1249 | %% @doc Convert a statement from the ? placeholder syntax to the $x placeholder 1250 | %% syntax. 1251 | %% 1252 | -spec convert_statement(binary() | string()) -> string(). 1253 | convert_statement(StatementStr) when is_list(StatementStr) -> 1254 | convert_statement_0(StatementStr, false, 1, []); 1255 | convert_statement(StatementStr) when is_binary(StatementStr) -> 1256 | convert_statement(binary_to_list(StatementStr)). 1257 | 1258 | convert_statement_0([], _InString, _PlaceholderIndex, Acc) -> lists:reverse(Acc); 1259 | convert_statement_0([$? | Tail], false, PlaceholderIndex, Acc) -> 1260 | convert_statement_0(Tail, false, PlaceholderIndex + 1, lists:reverse([$$ | integer_to_list(PlaceholderIndex)]) ++ Acc); 1261 | convert_statement_0([$' | Tail], InString, PlaceholderIndex, Acc) -> 1262 | convert_statement_0(Tail, not InString, PlaceholderIndex, [$' | Acc]); 1263 | convert_statement_0([H | Tail], InString, PlaceholderIndex, Acc) -> 1264 | convert_statement_0(Tail, InString, PlaceholderIndex, [H | Acc]). 1265 | 1266 | %%-------------------------------------------------------------------- 1267 | %% @doc Receive a single packet (in passive mode). Notifications and 1268 | %% notices are broadcast to subscribers. 1269 | %% 1270 | -spec receive_message(socket(), sync | {async, pid(), fun((any()) -> ok)}, [{pid(), reference()}]) -> {ok, pgsql_backend_message()} | {error, any()}. 1271 | receive_message({SockModule, Sock}, AsyncT, Subscribers) -> 1272 | Result0 = case SockModule:recv(Sock, ?MESSAGE_HEADER_SIZE) of 1273 | {ok, <>} -> 1274 | Payload = Size - 4, 1275 | case Payload of 1276 | 0 -> 1277 | pgsql_protocol:decode_message(Code, <<>>); 1278 | _ -> 1279 | case SockModule:recv(Sock, Payload) of 1280 | {ok, Rest} -> 1281 | pgsql_protocol:decode_message(Code, Rest); 1282 | {error, _} = ErrorRecvPacket -> ErrorRecvPacket 1283 | end 1284 | end; 1285 | {error, _} = ErrorRecvPacketHeader -> ErrorRecvPacketHeader 1286 | end, 1287 | case Result0 of 1288 | {ok, #notification_response{} = Notification} -> 1289 | broadcast_to_subscribers(Notification, AsyncT, Subscribers), 1290 | receive_message({SockModule, Sock}, AsyncT, Subscribers); 1291 | {ok, #notice_response{} = Notice} -> 1292 | broadcast_to_subscribers(Notice, AsyncT, Subscribers), 1293 | receive_message({SockModule, Sock}, AsyncT, Subscribers); 1294 | _ -> Result0 1295 | end. 1296 | 1297 | -spec broadcast_to_subscribers( 1298 | #notification_response{} | #notice_response{}, 1299 | sync | {async, pid(), fun((any()) -> ok)}, 1300 | [{pid(), reference()}]) -> ok. 1301 | broadcast_to_subscribers(Packet, AsyncT, Subscribers) -> 1302 | ConnPid = case AsyncT of 1303 | sync -> self(); 1304 | {async, Pid, _Fun} -> Pid 1305 | end, 1306 | Connection = {?MODULE, ConnPid}, 1307 | What = case Packet of 1308 | #notification_response{procid = ProcID, channel = Channel, payload = Payload} -> {notification, ProcID, Channel, Payload}; 1309 | #notice_response{fields = Fields} -> {notice, Fields} 1310 | end, 1311 | Message = {pgsql, Connection, What}, 1312 | lists:foreach(fun({Subscriber, _Ref}) -> 1313 | Subscriber ! Message 1314 | end, Subscribers). 1315 | 1316 | %%-------------------------------------------------------------------- 1317 | %% @doc Decode a command complete tag and result rows and form a result 1318 | %% according to the current API. 1319 | %% 1320 | decode_tag(Tag) -> 1321 | case binary:split(Tag, <<" ">>) of 1322 | [Verb, Object] -> 1323 | VerbDecoded = decode_verb(Verb), 1324 | ObjectL = decode_object(Object), 1325 | list_to_tuple([VerbDecoded | ObjectL]); 1326 | [Verb] -> decode_verb(Verb) 1327 | end. 1328 | 1329 | decode_verb(Verb) -> 1330 | VerbStr = binary_to_list(Verb), 1331 | VerbLC = string:to_lower(VerbStr), 1332 | list_to_atom(VerbLC). 1333 | 1334 | decode_object(<> = Object) when FirstByte =< $9 andalso FirstByte >= $0 -> 1335 | Words = binary:split(Object, <<" ">>, [global]), 1336 | [list_to_integer(binary_to_list(Word)) || Word <- Words]; 1337 | decode_object(Object) -> 1338 | ObjectUStr = re:replace(Object, <<" ">>, <<"_">>, [global, {return, list}]), 1339 | ObjectULC = string:to_lower(ObjectUStr), 1340 | [list_to_atom(ObjectULC)]. 1341 | 1342 | %%-------------------------------------------------------------------- 1343 | %% @doc Convert a native result to an odbc result. 1344 | %% 1345 | -spec native_to_odbc(result_tuple()) -> odbc_result_tuple() | {error, any()}. 1346 | native_to_odbc({error, _} = Error) -> Error; 1347 | native_to_odbc({{insert, _TableOID, Count}, []}) -> {updated, Count}; 1348 | native_to_odbc({{delete, Count}, []}) -> {updated, Count}; 1349 | native_to_odbc({{update, Count}, []}) -> {updated, Count}; 1350 | native_to_odbc({{move, Count}, []}) -> {updated, Count}; 1351 | native_to_odbc({{fetch, _Count}, []}) -> {updated, 0}; 1352 | native_to_odbc({{copy, Count}, []}) -> {updated, Count}; 1353 | native_to_odbc({{insert, _TableOID, Count}, Rows}) -> {updated, Count, Rows}; 1354 | native_to_odbc({{delete, Count}, Rows}) -> {updated, Count, Rows}; 1355 | native_to_odbc({{update, Count}, Rows}) -> {updated, Count, Rows}; 1356 | native_to_odbc({{select, _Count}, Rows}) -> {selected, Rows}; 1357 | native_to_odbc({{create, _What}, []}) -> {updated, 1}; 1358 | native_to_odbc({{drop, _What}, []}) -> {updated, 1}; 1359 | native_to_odbc({{alter, _What}, []}) -> {updated, 1}; 1360 | native_to_odbc({'begin', []}) -> {updated, 0}; 1361 | native_to_odbc({commit, []}) -> {updated, 0}; 1362 | %native_to_odbc({rollback, []}) -> {updated, 0}; -- make sure rollback fails. 1363 | native_to_odbc({set, []}) -> {updated, 0}; 1364 | native_to_odbc({listen, []}) -> {updated, 0}; 1365 | native_to_odbc({notify, []}) -> {updated, 0}; 1366 | native_to_odbc({'do', []}) -> {updated, 0}; 1367 | native_to_odbc({Other, []}) -> {error, {pgsql_error, {unknown_command, Other}}}. 1368 | 1369 | adjust_timeout(infinity) -> infinity; 1370 | adjust_timeout(Timeout) -> Timeout + ?TIMEOUT_GEN_SERVER_CALL_DELTA. 1371 | 1372 | %%-------------------------------------------------------------------- 1373 | %% @doc Cancel using a new connection. 1374 | %% 1375 | oob_cancel(#state{options = Options, backend_procid = ProcID, backend_secret = Secret}) -> 1376 | Host = proplists:get_value(host, Options, ?DEFAULT_HOST), 1377 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 1378 | % First open a TCP connection 1379 | case gen_tcp:connect(Host, Port, [binary, {packet, raw}, {active, false}]) of 1380 | {ok, Sock} -> 1381 | Message = pgsql_protocol:encode_cancel_message(ProcID, Secret), 1382 | case gen_tcp:send(Sock, Message) of 1383 | ok -> 1384 | gen_tcp:close(Sock); 1385 | {error, _} = SendError -> SendError 1386 | end; 1387 | {error, _} = ConnectError -> ConnectError 1388 | end. 1389 | 1390 | %%-------------------------------------------------------------------- 1391 | %% @doc Update the OID Map out of band, opening a new connection. 1392 | %% 1393 | -spec oob_update_oid_map_from_fields_if_required([#row_description_field{}], #state{}) -> #state{}. 1394 | oob_update_oid_map_from_fields_if_required(Fields, State0) -> 1395 | OIDs = [OID || #row_description_field{data_type_oid = OID} <- Fields], 1396 | oob_update_oid_map_if_required(OIDs, State0). 1397 | 1398 | -spec oob_update_oid_map_if_required([pgsql_oid()], #state{}) -> #state{}. 1399 | oob_update_oid_map_if_required(OIDs, #state{oidmap = OIDMap} = State0) -> 1400 | Required = lists:any(fun(OID) -> 1401 | not gb_trees:is_defined(OID, OIDMap) 1402 | end, OIDs), 1403 | case Required of 1404 | true -> oob_update_oid_map(State0); 1405 | false -> State0 1406 | end. 1407 | 1408 | oob_update_oid_map(#state{options = Options0} = State0) -> 1409 | OOBOptions = lists:keystore(fetch_oid_map, 1, Options0, {fetch_oid_map, false}), 1410 | {ok, Pid} = pgsql_connection_sup:start_child(OOBOptions), 1411 | SubConnection = {pgsql_connection, Pid}, 1412 | {ok, NewOIDMap} = fold(fun({Oid, Typename}, AccTypes) -> 1413 | gb_trees:enter(Oid, binary_to_atom(Typename, utf8), AccTypes) 1414 | end, State0#state.oidmap, "SELECT oid, typname FROM pg_type", SubConnection), 1415 | close(SubConnection), 1416 | State0#state{oidmap = NewOIDMap}. 1417 | 1418 | %%-------------------------------------------------------------------- 1419 | %% @doc Update the OID Map inline (at setup). 1420 | %% 1421 | update_oid_map(#state{} = State0) -> 1422 | {{ok, NewOIDMap}, State1} = pgsql_extended_query0(<<"SELECT oid, typname FROM pg_type">>, [], fun({Oid, Typename}, _RowDesc, _QueryOptions, AccTypes) -> 1423 | gb_trees:enter(Oid, binary_to_atom(Typename, utf8), AccTypes) 1424 | end, State0#state.oidmap, fun fold_finalize/3, all, sync, [], State0), 1425 | State1#state{oidmap = NewOIDMap}. 1426 | 1427 | %%-------------------------------------------------------------------- 1428 | %% @doc Prepare socket for sending query: set it in passive mode or 1429 | %% reconnect if it was closed and options allow it. 1430 | %% 1431 | -spec set_passive_or_reconnect_if_required(#state{}) -> #state{}. 1432 | set_passive_or_reconnect_if_required(#state{socket = closed, options = Options} = State0) -> 1433 | case proplists:get_value(reconnect, Options, true) of 1434 | true -> 1435 | case pgsql_open(State0) of 1436 | {ok, State1} -> State1; 1437 | {error, _} -> State0 1438 | end; 1439 | false -> State0 1440 | end; 1441 | set_passive_or_reconnect_if_required(#state{socket = {gen_tcp, Socket}} = State0) -> 1442 | _ = inet:setopts(Socket, [{active, false}]), 1443 | State0; 1444 | set_passive_or_reconnect_if_required(#state{socket = {ssl, Socket}} = State0) -> 1445 | _ = ssl:setopts(Socket, [{active, false}]), 1446 | State0. 1447 | 1448 | %%-------------------------------------------------------------------- 1449 | %% @doc Set the socket in active mode for a single packet (a notification). 1450 | %% 1451 | -spec set_active_once(#state{}) -> ok. 1452 | set_active_once(#state{socket = closed}) -> ok; 1453 | set_active_once(#state{socket = {gen_tcp, Socket}}) -> 1454 | _ = inet:setopts(Socket, [{active, once}]), 1455 | ok; 1456 | set_active_once(#state{socket = {ssl, Socket}}) -> 1457 | _ = ssl:setopts(Socket, [{active, once}]), 1458 | ok. 1459 | 1460 | %%-------------------------------------------------------------------- 1461 | %% @doc Process some active data. 1462 | %% 1463 | -spec process_active_data(binary(), #state{}) -> #state{}. 1464 | process_active_data(<>, #state{socket = {SockModule, Sock}, subscribers = Subscribers} = State0) -> 1465 | TailSize = byte_size(Tail), 1466 | Payload = Size - 4, 1467 | DecodeT = case Payload of 1468 | 0 -> 1469 | {pgsql_protocol:decode_message(Code, <<>>), Tail}; 1470 | _ when Payload =< TailSize -> 1471 | {PayloadBin, Rest0} = split_binary(Tail, Payload), 1472 | {pgsql_protocol:decode_message(Code, PayloadBin), Rest0}; 1473 | _ when Payload > TailSize -> 1474 | case SockModule:recv(Sock, Payload - TailSize) of 1475 | {ok, Missing} -> 1476 | {pgsql_protocol:decode_message(Code, list_to_binary([Tail, Missing])), <<>>}; 1477 | {error, _} = ErrorRecvPacket -> 1478 | {ErrorRecvPacket, <<>>} 1479 | end 1480 | end, 1481 | case DecodeT of 1482 | {{ok, #notification_response{} = Notification}, Rest} -> 1483 | broadcast_to_subscribers(Notification, sync, Subscribers), 1484 | process_active_data(Rest, State0); 1485 | {{ok, #notice_response{} = Notice}, Rest} -> 1486 | broadcast_to_subscribers(Notice, sync, Subscribers), 1487 | process_active_data(Rest, State0); 1488 | {{ok, #parameter_status{name = Name, value = Value}}, Rest} -> 1489 | State1 = handle_parameter(Name, Value, sync, State0), 1490 | process_active_data(Rest, State1); 1491 | {{ok, Message}, Rest} -> 1492 | error_logger:warning_msg("Unexpected asynchronous message\n~p\n", [Message]), 1493 | process_active_data(Rest, State0); 1494 | {{error, _} = Error, _Rest} -> 1495 | error_logger:error_msg("Unexpected asynchronous error\n~p\n", [Error]), 1496 | SockModule:close(Sock), 1497 | State0#state{socket = closed} 1498 | end; 1499 | process_active_data(<<>>, State0) -> State0; 1500 | process_active_data(PartialHeader, #state{socket = {SockModule, Sock}} = State0) -> 1501 | PartialHeaderSize = byte_size(PartialHeader), 1502 | case SockModule:recv(Sock, ?MESSAGE_HEADER_SIZE - PartialHeaderSize) of 1503 | {ok, Rest} -> 1504 | process_active_data(list_to_binary([PartialHeader, Rest]), State0); 1505 | {error, _} = Error -> 1506 | error_logger:error_msg("Unexpected asynchronous error\n~p\n", [Error]), 1507 | SockModule:close(Sock), 1508 | State0#state{socket = closed} 1509 | end. 1510 | 1511 | %%-------------------------------------------------------------------- 1512 | %% @doc Subscribe to notifications. We setup a monitor to clean the list up. 1513 | %% 1514 | do_subscribe(Pid, List) -> 1515 | MonitorRef = erlang:monitor(process, Pid), 1516 | [{Pid, MonitorRef} | List]. 1517 | 1518 | %%-------------------------------------------------------------------- 1519 | %% @doc Unsubscribe to notifications. Clear the monitor. 1520 | %% 1521 | do_unsubscribe(Pid, List) -> 1522 | case lists:keyfind(Pid, 1, List) of 1523 | {Pid, MonitorRef} -> 1524 | erlang:demonitor(MonitorRef), 1525 | lists:keydelete(Pid, 1, List); 1526 | false -> List 1527 | end. 1528 | 1529 | %%-------------------------------------------------------------------- 1530 | %% @doc Send a call message to the gen server, retrying if the result is 1531 | %% {error, closed} and the option retry is set to true. 1532 | %% 1533 | call_and_retry(ConnPid, Command, Retry, Timeout) -> 1534 | case gen_server:call(ConnPid, {do_query, Command}, Timeout) of 1535 | {error, closed} when Retry -> 1536 | call_and_retry(ConnPid, Command, Retry, Timeout); 1537 | Other -> 1538 | Other 1539 | end. 1540 | 1541 | %%-------------------------------------------------------------------- 1542 | %% @doc Perform a query. 1543 | %% 1544 | do_query(Command, From, #state{current = undefined} = State0) -> 1545 | State1 = State0#state{current = {Command, From}}, 1546 | State2 = set_passive_or_reconnect_if_required(State1), 1547 | case State2#state.socket of 1548 | closed -> 1549 | gen_server:reply(From, {error, closed}), 1550 | command_completed({Command, From}, State2); 1551 | _ -> 1552 | do_query0(Command, From, State2) 1553 | end; 1554 | do_query(Command, From, #state{pending = Pending} = State0) -> 1555 | State0#state{pending = [{Command, From} | Pending]}. 1556 | 1557 | do_query0({simple_query, Query, QueryOptions, Timeout}, From, State0) -> 1558 | pgsql_simple_query(Query, QueryOptions, Timeout, From, State0); 1559 | do_query0({extended_query, Query, Parameters, QueryOptions, Timeout}, From, State0) -> 1560 | pgsql_extended_query(Query, Parameters, fun extended_query_fn/4, {[], []}, fun extended_query_finalize/3, all, QueryOptions, Timeout, From, State0); 1561 | do_query0({batch_query, Query, ParametersList, QueryOptions, Timeout}, From, State0) -> 1562 | pgsql_extended_query(Query, ParametersList, fun extended_query_fn/4, {[], []}, fun extended_query_finalize/3, batch, QueryOptions, Timeout, From, State0); 1563 | do_query0({fold, Query, Parameters, Function, Acc0, QueryOptions, Timeout}, From, #state{} = State0) -> 1564 | MaxRowsStep = proplists:get_value(max_rows_step, QueryOptions, ?DEFAULT_MAX_ROWS_STEP), 1565 | pgsql_extended_query(Query, Parameters, fun(Row, _RowDesc, _QueryOptions, AccIn) -> Function(Row, AccIn) end, Acc0, fun fold_finalize/3, {cursor, MaxRowsStep}, QueryOptions, Timeout, From, State0); 1566 | do_query0({map, Query, Parameters, Function, QueryOptions, Timeout}, From, #state{} = State0) -> 1567 | MaxRowsStep = proplists:get_value(max_rows_step, QueryOptions, ?DEFAULT_MAX_ROWS_STEP), 1568 | pgsql_extended_query(Query, Parameters, fun map_fn/4, {Function, []}, fun map_finalize/3, {cursor, MaxRowsStep}, QueryOptions, Timeout, From, State0); 1569 | do_query0({foreach, Query, Parameters, Function, QueryOptions, Timeout}, From, #state{} = State0) -> 1570 | MaxRowsStep = proplists:get_value(max_rows_step, QueryOptions, ?DEFAULT_MAX_ROWS_STEP), 1571 | pgsql_extended_query(Query, Parameters, fun foreach_fn/4, Function, fun foreach_finalize/3, {cursor, MaxRowsStep}, QueryOptions, Timeout, From, State0); 1572 | do_query0({send_copy_data, Data}, From, State0) -> 1573 | pgsql_send_copy_data(Data, From, State0); 1574 | do_query0({send_copy_end}, From, State0) -> 1575 | pgsql_send_copy_end(From, State0). 1576 | 1577 | 1578 | command_completed(Command, #state{current = Command, pending = []} = State) -> 1579 | set_active_once(State), 1580 | State#state{current = undefined}; 1581 | command_completed(Command, #state{current = Command, pending = [{PendingCommand, PendingFrom} | PendingT]} = State0) -> 1582 | State1 = State0#state{current = undefined, pending = PendingT}, 1583 | do_query(PendingCommand, PendingFrom, State1). 1584 | -------------------------------------------------------------------------------- /src/pgsql_connection_sup.erl: -------------------------------------------------------------------------------- 1 | %% @doc Supervisor for PostgreSQL connections. 2 | -module(pgsql_connection_sup). 3 | -vsn("1"). 4 | -behaviour(supervisor). 5 | 6 | -export([ 7 | start_link/0, 8 | start_child/1, 9 | init/1 10 | ]). 11 | 12 | -define(SHUTDOWN_DELAY, 5000). 13 | % no more than 5 restarts per second. 14 | -define(MAX_RESTARTS, 5). 15 | -define(MAX_RESTARTS_PERIOD, 1). 16 | 17 | %%-------------------------------------------------------------------- 18 | %% @doc Start the supervisor. 19 | %% 20 | %% @spec(start() -> {ok, pid()} | {error, tuple()}) 21 | -spec start_link() -> {ok, pid()} | {error, tuple()}. 22 | start_link() -> 23 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @doc supervisor init callback. 27 | %% 28 | -spec init(any()) -> {ok, {{supervisor:strategy(), non_neg_integer(), pos_integer()}, [supervisor:child_spec()]}}. 29 | init(_Args) -> 30 | ChildSpec = {pgsql_connection, % id (ignored). 31 | {pgsql_connection, start_link, []}, % init function 32 | temporary, % do not restart children that crash 33 | ?SHUTDOWN_DELAY, worker, 34 | [pgsql_proto] % modules 35 | }, 36 | RestartStrategy = {simple_one_for_one, ?MAX_RESTARTS, ?MAX_RESTARTS_PERIOD}, 37 | {ok, {RestartStrategy, [ChildSpec]}}. 38 | 39 | %%---------------------------------------------------------------- 40 | %% @doc Start a server. 41 | %% 42 | -spec start_child([{atom(), string()}]) -> {ok, pid()} | {error, any()}. 43 | start_child(Options) -> 44 | Result = supervisor:start_child(?MODULE, [Options]), 45 | Result. 46 | -------------------------------------------------------------------------------- /src/pgsql_error.erl: -------------------------------------------------------------------------------- 1 | %% -*- coding: utf-8 -*- 2 | -module(pgsql_error). 3 | -vsn("1"). 4 | 5 | -export([ 6 | is_integrity_constraint_violation/1, 7 | is_in_failed_sql_transaction/1 8 | ]). 9 | 10 | -export_type([ 11 | pgsql_error/0, 12 | pgsql_error_and_mention_field/0, 13 | pgsql_error_and_mention_field_type/0 14 | ]). 15 | 16 | -type pgsql_error_and_mention_field_type() :: 17 | severity | code | message | detail | hint | position | internal_position 18 | | internal_query | where | file | line | routine 19 | | schema | table | column | data_type | constraint | {unknown, byte()}. 20 | -type pgsql_error_and_mention_field() :: 21 | {pgsql_error_and_mention_field_type(), binary()}. 22 | -type pgsql_error() :: {pgsql_error, [pgsql_error_and_mention_field()]}. 23 | 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | -spec is_integrity_constraint_violation(pgsql_error()) -> boolean(). 26 | is_integrity_constraint_violation({pgsql_error, Fields}) -> 27 | case lists:keyfind(code, 1, Fields) of 28 | {code, <<"23", _SubClass:3/binary>>} -> true; %% iso 9075-2 §22.1 29 | {code, <<_Other:5/binary>>} -> false; 30 | false -> false 31 | end. 32 | 33 | -spec is_in_failed_sql_transaction(pgsql_error()) -> boolean(). 34 | is_in_failed_sql_transaction({pgsql_error, Fields}) -> 35 | {code, <<"25P02">>} =:= lists:keyfind(code, 1, Fields). %% PostgreSQL extension 36 | -------------------------------------------------------------------------------- /src/pgsql_internal.hrl: -------------------------------------------------------------------------------- 1 | % Backend messages 2 | -type pgsql_oid() :: pos_integer(). 3 | -type pgsql_procid() :: integer(). 4 | -type pgsql_format() :: text | binary. 5 | -type pgsql_oid_map() :: gb_trees:tree(pgsql_oid(), atom()). 6 | 7 | -define(JSONB_VERSION_1, 1). 8 | 9 | % from pg_type.h 10 | -define(BOOLOID, 16). 11 | -define(BYTEAOID, 17). 12 | -define(CHAROID, 18). 13 | -define(NAMEOID, 19). 14 | -define(INT8OID, 20). 15 | -define(INT2OID, 21). 16 | -define(INT2VECTOROID, 22). 17 | -define(INT4OID, 23). 18 | -define(REGPROCOID, 24). 19 | -define(TEXTOID, 25). 20 | -define(OIDOID, 26). 21 | -define(TIDOID, 27). 22 | -define(XIDOID, 28). 23 | -define(CIDOID, 29). 24 | -define(OIDVECTOROID, 30). 25 | -define(JSONOID, 114). 26 | -define(JSONBOID, 3802). 27 | -define(XMLOID, 142). 28 | -define(PGNODETREEOID, 194). 29 | -define(POINTOID, 600). 30 | -define(LSEGOID, 601). 31 | -define(PATHOID, 602). 32 | -define(BOXOID, 603). 33 | -define(POLYGONOID, 604). 34 | -define(LINEOID, 628). 35 | -define(CIDRARRAYOID, 651). % not #defined 36 | -define(FLOAT4OID, 700). 37 | -define(FLOAT8OID, 701). 38 | -define(ABSTIMEOID, 702). 39 | -define(RELTIMEOID, 703). 40 | -define(TINTERVALOID, 704). 41 | -define(UNKNOWNOID, 705). 42 | -define(CIRCLEOID, 718). 43 | -define(CASHOID, 790). 44 | -define(MACADDROID, 829). 45 | -define(INETOID, 869). 46 | -define(CIDROID, 650). 47 | -define(BOOLARRAYOID, 1000). % not #defined 48 | -define(BYTEAARRAYOID, 1001). % not #defined 49 | -define(CHARARRAYOID, 1002). % not #defined 50 | -define(NAMEARRAYOID, 1003). % not #defined 51 | -define(INT2ARRAYOID, 1005). % not #defined 52 | -define(INT2VECTORARRAYOID, 1006). % not #defined 53 | -define(INT4ARRAYOID, 1007). 54 | -define(REGPROCARRAYOID, 1008). % not #defined 55 | -define(TEXTARRAYOID, 1009). 56 | -define(TIDARRAYOID, 1010). % not #defined 57 | -define(XIDARRAYOID, 1011). % not #defined 58 | -define(CIDARRAYOID, 1012). % not #defined 59 | -define(OIDVECTORARRAYOID, 1013). % not #defined 60 | -define(BPCHARARRAYOID, 1014). % not #defined 61 | -define(VARCHARARRAYOID, 1015). % not #defined 62 | -define(INT8ARRAYOID, 1016). % not #defined 63 | -define(POINTARRAYOID, 1017). % not #defined 64 | -define(LSEGARRAYOID, 1018). % not #defined 65 | -define(PATHARRAYOID, 1019). % not #defined 66 | -define(BOXARRAYOID, 1020). % not #defined 67 | -define(FLOAT4ARRAYOID, 1021). 68 | -define(FLOAT8ARRAYOID, 1022). % not #defined 69 | -define(ABSTIMEARRAYOID, 1023). % not #defined 70 | -define(RELTIMEARRAYOID, 1024). % not #defined 71 | -define(TINTERVALARRAYOID, 1025). % not #defined 72 | -define(POLYGONARRAYOID, 1027). % not #defined 73 | -define(OIDARRAYOID, 1028). % not #defined 74 | -define(ACLITEMOID, 1033). 75 | -define(ACLITEMARRAYOID, 1034). % not #defined 76 | -define(MACADDRARRAYOID, 1040). % not #defined 77 | -define(INETARRAYOID, 1041). % not #defined 78 | -define(BPCHAROID, 1042). 79 | -define(VARCHAROID, 1043). 80 | -define(DATEOID, 1082). 81 | -define(TIMEOID, 1083). 82 | -define(TIMESTAMPOID, 1114). 83 | -define(TIMESTAMPTZOID, 1184). 84 | -define(INTERVALOID, 1186). 85 | -define(CSTRINGARRAYOID, 1263). 86 | -define(TIMETZOID, 1266). 87 | -define(BITOID, 1560). 88 | -define(VARBITOID, 1562). 89 | -define(NUMERICOID, 1700). 90 | -define(REFCURSOROID, 1790). 91 | -define(REGPROCEDUREOID, 2202). 92 | -define(REGOPEROID, 2203). 93 | -define(REGOPERATOROID, 2204). 94 | -define(REGCLASSOID, 2205). 95 | -define(REGTYPEOID, 2206). 96 | -define(REGTYPEARRAYOID, 2211). 97 | -define(UUIDOID, 2950). % not #defined 98 | -define(TSVECTOROID, 3614). 99 | -define(GTSVECTOROID, 3642). 100 | -define(TSQUERYOID, 3615). 101 | -define(REGCONFIGOID, 3734). 102 | -define(REGDICTIONARYOID, 3769). 103 | -define(INT4RANGEOID, 3904). 104 | -define(RECORDOID, 2249). 105 | -define(RECORDARRAYOID, 2287). 106 | -define(CSTRINGOID, 2275). 107 | -define(ANYOID, 2276). 108 | -define(ANYARRAYOID, 2277). 109 | -define(VOIDOID, 2278). 110 | -define(TRIGGEROID, 2279). 111 | -define(LANGUAGE_HANDLEROID, 2280). 112 | -define(INTERNALOID, 2281). 113 | -define(OPAQUEOID, 2282). 114 | -define(ANYELEMENTOID, 2283). 115 | -define(ANYNONARRAYOID, 2776). 116 | -define(ANYENUMOID, 3500). 117 | -define(FDW_HANDLEROID, 3115). 118 | -define(ANYRANGEOID, 3831). 119 | 120 | -define(PG_TYPE_H_TYPES_DICT, [ 121 | {?BOOLOID, bool}, 122 | {?BYTEAOID, bytea}, 123 | {?CHAROID, char}, 124 | {?NAMEOID, name}, 125 | {?INT8OID, int8}, 126 | {?INT2OID, int2}, 127 | {?INT2VECTOROID, int2vector}, 128 | {?INT4OID, int4}, 129 | {?REGPROCOID, regproc}, 130 | {?TEXTOID, text}, 131 | {?OIDOID, oid}, 132 | {?TIDOID, tid}, 133 | {?XIDOID, xid}, 134 | {?CIDOID, cid}, 135 | {?OIDVECTOROID, oidvector}, 136 | {?JSONOID, json}, 137 | {?JSONBOID, jsonb}, 138 | {?XMLOID, xml}, 139 | {?PGNODETREEOID, pgnodetree}, 140 | {?POINTOID, point}, 141 | {?LSEGOID, lseg}, 142 | {?PATHOID, path}, 143 | {?BOXOID, box}, 144 | {?POLYGONOID, polygon}, 145 | {?LINEOID, line}, 146 | {?FLOAT4OID, float4}, 147 | {?FLOAT8OID, float8}, 148 | {?ABSTIMEOID, abstime}, 149 | {?RELTIMEOID, reltime}, 150 | {?TINTERVALOID, tinterval}, 151 | {?UNKNOWNOID, unknown}, 152 | {?CIRCLEOID, circle}, 153 | {?CASHOID, cash}, 154 | {?MACADDROID, macaddr}, 155 | {?INETOID, inet}, 156 | {?CIDROID, cidr}, 157 | {?INT4ARRAYOID, int4array}, 158 | {?TEXTARRAYOID, textarray}, 159 | {?FLOAT4ARRAYOID, float4array}, 160 | {?ACLITEMOID, aclitem}, 161 | {?CSTRINGARRAYOID, cstringarray}, 162 | {?BPCHAROID, bpchar}, 163 | {?VARCHAROID, varchar}, 164 | {?DATEOID, date}, 165 | {?TIMEOID, time}, 166 | {?TIMESTAMPOID, timestamp}, 167 | {?TIMESTAMPTZOID, timestamptz}, 168 | {?INTERVALOID, interval}, 169 | {?TIMETZOID, timetz}, 170 | {?BITOID, bit}, 171 | {?VARBITOID, varbit}, 172 | {?NUMERICOID, numeric}, 173 | {?REFCURSOROID, refcursor}, 174 | {?REGPROCEDUREOID, regprocedure}, 175 | {?REGOPEROID, regoper}, 176 | {?REGOPERATOROID, regoperator}, 177 | {?REGCLASSOID, regclass}, 178 | {?REGTYPEOID, regtype}, 179 | {?REGTYPEARRAYOID, regtypearray}, 180 | {?UUIDOID, uuid}, % not #defined 181 | {?TSVECTOROID, tsvector}, 182 | {?GTSVECTOROID, gtsvector}, 183 | {?TSQUERYOID, tsquery}, 184 | {?REGCONFIGOID, regconfig}, 185 | {?REGDICTIONARYOID, regdictionary}, 186 | {?INT4RANGEOID, int4range}, 187 | {?RECORDOID, record}, 188 | {?RECORDARRAYOID, recordarray}, 189 | {?CSTRINGOID, cstring}, 190 | {?ANYOID, any}, 191 | {?ANYARRAYOID, anyarray}, 192 | {?VOIDOID, void}, 193 | {?TRIGGEROID, trigger}, 194 | {?LANGUAGE_HANDLEROID, language_handler}, 195 | {?INTERNALOID, internal}, 196 | {?OPAQUEOID, opaque}, 197 | {?ANYELEMENTOID, anyelement}, 198 | {?ANYNONARRAYOID, anynonarray}, 199 | {?ANYENUMOID, anyenum}, 200 | {?FDW_HANDLEROID, fdw_handler}, 201 | {?ANYRANGEOID, anyrange} 202 | ]). 203 | 204 | -record(authentication_ok, {}). 205 | -record(authentication_kerberos_v5, {}). 206 | -record(authentication_cleartext_password, {}). 207 | -record(authentication_md5_password, { 208 | salt :: binary() 209 | }). 210 | -record(authentication_scm_credential, {}). 211 | -record(authentication_gss, {}). 212 | -record(authentication_sspi, {}). 213 | -record(authentication_gss_continue, { 214 | data :: binary() 215 | }). 216 | -record(backend_key_data, { 217 | procid :: pgsql_procid(), 218 | secret :: integer() 219 | }). 220 | -record(bind_complete, {}). 221 | -record(close_complete, {}). 222 | -record(command_complete, { 223 | command_tag :: iodata() 224 | }). 225 | -record(copy_data, { 226 | data :: any() 227 | }). 228 | -record(copy_done, {}). 229 | -record(copy_in_response, { 230 | format :: pgsql_format(), 231 | columns :: non_neg_integer(), 232 | column_formats :: [pgsql_format()] 233 | }). 234 | -record(copy_out_response, { 235 | format :: pgsql_format(), 236 | columns :: non_neg_integer(), 237 | column_formats :: [pgsql_format()] 238 | }). 239 | -record(copy_both_response, { 240 | format :: pgsql_format(), 241 | columns :: non_neg_integer(), 242 | column_formats :: [pgsql_format()] 243 | }). 244 | -record(data_row, { 245 | values :: [null | binary()] 246 | }). 247 | -record(empty_query_response, {}). 248 | -record(error_response, { 249 | fields :: [pgsql_error:pgsql_error_and_mention_field()] 250 | }). 251 | -record(function_call_response, { 252 | value :: null | iodata() 253 | }). 254 | -record(no_data, {}). 255 | -record(notice_response, { 256 | fields :: [pgsql_error:pgsql_error_and_mention_field()] 257 | }). 258 | -record(notification_response, { 259 | procid :: pgsql_procid(), 260 | channel :: iodata(), 261 | payload :: iodata() 262 | }). 263 | -record(parameter_description, { 264 | count :: non_neg_integer(), 265 | data_types :: [pgsql_oid()] 266 | }). 267 | -record(parameter_status, { 268 | name :: iodata(), 269 | value :: iodata() 270 | }). 271 | -record(parse_complete, {}). 272 | -record(portal_suspended, {}). 273 | -record(ready_for_query, { 274 | transaction_status :: idle | transaction | error 275 | }). 276 | -record(row_description_field, { 277 | name :: iodata(), 278 | table_oid :: pgsql_oid(), 279 | attr_number :: integer(), 280 | data_type_oid :: pgsql_oid(), 281 | data_type_size :: integer(), 282 | type_modifier :: integer(), 283 | format :: pgsql_format() 284 | }). 285 | -record(row_description, { 286 | count :: non_neg_integer(), 287 | fields :: [#row_description_field{}] 288 | }). 289 | 290 | -type pgsql_backend_message() :: 291 | #authentication_cleartext_password{} | 292 | #authentication_gss_continue{} | 293 | #authentication_gss{} | 294 | #authentication_kerberos_v5{} | 295 | #authentication_md5_password{} | 296 | #authentication_ok{} | 297 | #authentication_scm_credential{} | 298 | #authentication_sspi{} | 299 | #backend_key_data{} | 300 | #bind_complete{} | 301 | #close_complete{} | 302 | #command_complete{} | 303 | #copy_both_response{} | 304 | #copy_data{} | 305 | #copy_done{} | 306 | #copy_in_response{} | 307 | #copy_out_response{} | 308 | #data_row{} | 309 | #empty_query_response{} | 310 | #error_response{} | 311 | #function_call_response{} | 312 | #no_data{} | 313 | #notice_response{} | 314 | #notification_response{} | 315 | #parameter_description{} | 316 | #parameter_status{} | 317 | #parse_complete{} | 318 | #portal_suspended{} | 319 | #ready_for_query{} | 320 | #row_description{}. 321 | -------------------------------------------------------------------------------- /src/pgsql_protocol.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Module for packet encoding and decoding. 2 | %%% 3 | -module(pgsql_protocol). 4 | -vsn("3"). 5 | % -include_lib("commonlib/include/compile_time.hrl"). 6 | -include("pgsql_internal.hrl"). 7 | 8 | -export([ 9 | encode_startup_message/1, 10 | encode_ssl_request_message/0, 11 | encode_password_message/1, 12 | encode_query_message/1, 13 | encode_parse_message/3, 14 | encode_bind_message/4, % deprecated 15 | encode_bind_message/5, % deprecated 16 | encode_bind_message/6, 17 | encode_describe_message/2, 18 | encode_execute_message/2, 19 | encode_sync_message/0, 20 | encode_flush_message/0, 21 | encode_cancel_message/2, 22 | encode_copy_data_message/1, 23 | encode_copy_done/0, 24 | encode_copy_fail/1, 25 | 26 | decode_message/2, 27 | decode_row/4, 28 | 29 | bind_requires_statement_description/1 30 | ]). 31 | 32 | %%==================================================================== 33 | %% Constants 34 | %%==================================================================== 35 | -define(PROTOCOL_VERSION_MAJOR, 3). 36 | -define(PROTOCOL_VERSION_MINOR, 0). 37 | 38 | -define(POSTGRESQL_GD_EPOCH, 730485). % ?_value(calendar:date_to_gregorian_days({2000,1,1}))). 39 | -define(POSTGRESQL_GS_EPOCH, 63113904000). % ?_value(calendar:datetime_to_gregorian_seconds({{2000,1,1}, {0,0,0}}))). 40 | 41 | %%==================================================================== 42 | %% Public API 43 | %%==================================================================== 44 | 45 | %%-------------------------------------------------------------------- 46 | %% @doc Encode the startup message. 47 | %% 48 | -spec encode_startup_message([{iodata(), iodata()}]) -> binary(). 49 | encode_startup_message(Parameters) -> 50 | EncodedParams = [[iolist_to_binary(Key), 0, iolist_to_binary(Value), 0] || {Key, Value} <- Parameters], 51 | Packet = list_to_binary([<>, EncodedParams, 0]), 52 | Size = byte_size(Packet) + 4, 53 | <>. 54 | 55 | %%-------------------------------------------------------------------- 56 | %% @doc Encode the ssl request message. 57 | %% 58 | -spec encode_ssl_request_message() -> binary(). 59 | encode_ssl_request_message() -> 60 | <<8:32/integer, 1234:16/integer, 5679:16/integer>>. 61 | 62 | %%-------------------------------------------------------------------- 63 | %% @doc Encode a password. 64 | %% 65 | -spec encode_password_message(iodata()) -> binary(). 66 | encode_password_message(Password) -> 67 | encode_string_message($p, Password). 68 | 69 | %%-------------------------------------------------------------------- 70 | %% @doc Encode a query. 71 | %% 72 | -spec encode_query_message(iodata()) -> binary(). 73 | encode_query_message(Query) -> 74 | encode_string_message($Q, Query). 75 | 76 | %%-------------------------------------------------------------------- 77 | %% @doc Encode a data segment of a COPY operation 78 | %% 79 | -spec encode_copy_data_message(iodata()) -> binary(). 80 | encode_copy_data_message(Message) -> 81 | StringBin = iolist_to_binary(Message), 82 | MessageLen = byte_size(StringBin) + 4, 83 | <<$d, MessageLen:32/integer, StringBin/binary>>. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @doc Encode the end of a COPY operation 87 | %% 88 | -spec encode_copy_done() -> binary(). 89 | encode_copy_done() -> 90 | <<$c, 4:32/integer>>. 91 | 92 | %%-------------------------------------------------------------------- 93 | %% @doc Encode the cancellation of a COPY operation with the given 94 | %% failure message 95 | %% 96 | -spec encode_copy_fail(iodata()) -> binary(). 97 | encode_copy_fail(ErrorMessage) -> 98 | encode_string_message($f, ErrorMessage). 99 | 100 | %%-------------------------------------------------------------------- 101 | %% @doc Encode a parse message. 102 | %% 103 | -spec encode_parse_message(iodata(), iodata(), [pgsql_oid()]) -> binary(). 104 | encode_parse_message(PreparedStatementName, Query, DataTypes) -> 105 | PreparedStatementNameBin = iolist_to_binary(PreparedStatementName), 106 | QueryBin = iolist_to_binary(Query), 107 | DataTypesBin = list_to_binary([<> || DataTypeOid <- DataTypes]), 108 | DataTypesCount = length(DataTypes), 109 | Packet = <>, 110 | PacketLen = byte_size(Packet) + 4, 111 | <<$P, PacketLen:32/integer, Packet/binary>>. 112 | 113 | %%-------------------------------------------------------------------- 114 | %% @doc Encode a bind message. 115 | %% 116 | -spec encode_bind_message(iodata(), iodata(), [any()], boolean()) -> binary(). 117 | encode_bind_message(PortalName, StatementName, Parameters, IntegerDateTimes) -> 118 | encode_bind_message(PortalName, StatementName, Parameters, [], IntegerDateTimes). 119 | 120 | -spec encode_bind_message(iodata(), iodata(), [any()], [pgsql_oid()], boolean()) -> binary(). 121 | encode_bind_message(PortalName, StatementName, Parameters, ParametersDataTypes, IntegerDateTimes) -> 122 | encode_bind_message(PortalName, StatementName, Parameters, ParametersDataTypes, gb_trees:empty(), IntegerDateTimes). 123 | 124 | -spec encode_bind_message(iodata(), iodata(), [any()], [pgsql_oid()], pgsql_oid_map(), boolean()) -> binary(). 125 | encode_bind_message(PortalName, StatementName, Parameters, ParametersDataTypes, OIDMap, IntegerDateTimes) -> 126 | PortalNameBin = iolist_to_binary(PortalName), 127 | StatementNameBin = iolist_to_binary(StatementName), 128 | ParametersCount = length(Parameters), 129 | ParametersCountBin = <>, 130 | ParametersWithTypes = case ParametersDataTypes of 131 | [] -> [{Parameter, undefined} || Parameter <- Parameters]; 132 | _ -> lists:zip(Parameters, ParametersDataTypes) 133 | end, 134 | ParametersL = [encode_parameter(Parameter, Type, OIDMap, IntegerDateTimes) || {Parameter, Type} <- ParametersWithTypes], 135 | {ParametersFormats, ParametersValues} = lists:unzip(ParametersL), 136 | ParametersFormatsAllText = lists:all(fun(Format) -> Format =:= text end, ParametersFormats), 137 | ParametersFormatsBin = if 138 | ParametersFormatsAllText -> <<0:16/integer>>; 139 | true -> 140 | [ParametersCountBin | [encode_format(Format) || Format <- ParametersFormats]] 141 | end, 142 | Results = <<1:16/integer, 1:16/integer>>, % We want all results in binary format. 143 | Packet = list_to_binary([PortalNameBin, 0, StatementNameBin, 0, ParametersFormatsBin, ParametersCountBin, ParametersValues, Results]), 144 | PacketLen = byte_size(Packet) + 4, 145 | <<$B, PacketLen:32/integer, Packet/binary>>. 146 | 147 | encode_format(text) -> <<0:16/integer>>; 148 | encode_format(binary) -> <<1:16/integer>>. 149 | 150 | %%-------------------------------------------------------------------- 151 | %% @doc Encode a parameter. 152 | %% Most parameters are encoded in text format. 153 | %% Binaries are passed as binaries, unless they look like UUIDs. This is 154 | %% suitable for text and other types. 155 | %% JSON and INET/CIDR types are encoded as binaries. 156 | %% 157 | -spec encode_parameter(any(), pgsql_oid() | undefined, pgsql_oid_map(), boolean()) -> {text | binary, binary()}. 158 | encode_parameter({array, List}, Type, OIDMap, IntegerDateTimes) -> 159 | encode_array(List, Type, OIDMap, IntegerDateTimes); 160 | encode_parameter(Binary, ?TEXTOID, _OIDMap, _IntegerDateTimes) when is_binary(Binary) -> 161 | Size = byte_size(Binary), 162 | {binary, <>}; 163 | encode_parameter({json, Binary}, _Type, _OIDMap, _IntegerDateTimes) -> 164 | Size = byte_size(Binary), 165 | {binary, <>}; 166 | encode_parameter({jsonb, Binary}, _Type, _OIDMap, _IntegerDateTimes) -> 167 | Size = byte_size(Binary), 168 | {binary, <<(Size+1):32/integer, ?JSONB_VERSION_1:8, Binary/binary>>}; 169 | encode_parameter(Binary, _Type, _OIDMap, _IntegerDateTimes) when is_binary(Binary) -> 170 | % Encode the binary as text if it is a UUID. 171 | IsUUID = case Binary of 172 | <<_A:8/binary, $-, _B:4/binary, $-, _C:4/binary, $-, _D:4/binary, $-, _E:12/binary>> -> 173 | case io_lib:fread("~16u-~16u-~16u-~16u-~16u", binary_to_list(Binary)) of 174 | {ok,[_AI, _BI, _CI, _DI, _EI],[]} -> true; 175 | _ -> false 176 | end; 177 | _ -> false 178 | end, 179 | Type = if 180 | IsUUID -> text; 181 | true -> binary 182 | end, 183 | Size = byte_size(Binary), 184 | {Type, <>}; 185 | encode_parameter(String, _Type, _OIDMap, _IntegerDateTimes) when is_list(String) -> 186 | Binary = list_to_binary(String), 187 | Size = byte_size(Binary), 188 | {text, <>}; 189 | encode_parameter(Float, _Type, _OIDMap, _IntegerDateTimes) when is_float(Float) -> 190 | FloatStrBin = list_to_binary(float_to_list(Float)), 191 | Size = byte_size(FloatStrBin), 192 | {text, <>}; 193 | encode_parameter(Integer, _Type, _OIDMap, _IntegerDateTimes) when is_integer(Integer) -> 194 | IntegerStr = integer_to_list(Integer), 195 | IntegerStrBin = list_to_binary(IntegerStr), 196 | IntegerStrLen = byte_size(IntegerStrBin), 197 | {text, <>}; 198 | encode_parameter(null, _Type, _OIDMap, _IntegerDateTimes) -> 199 | {text, <<-1:32/integer>>}; 200 | encode_parameter(true, _Type, _OIDMap, _IntegerDateTimes) -> 201 | {text, <<1:32/integer, $t>>}; 202 | encode_parameter(false, _Type, _OIDMap, _IntegerDateTimes) -> 203 | {text, <<1:32/integer, $f>>}; 204 | encode_parameter('NaN', _Type, _OIDMap, _IntegerDateTimes) -> 205 | {text, <<3:32/integer, "NaN">>}; 206 | encode_parameter('Infinity', _Type, _OIDMap, _IntegerDateTimes) -> 207 | {text, <<8:32/integer, "Infinity">>}; 208 | encode_parameter('-Infinity', _Type, _OIDMap, _IntegerDateTimes) -> 209 | {text, <<9:32/integer, "-Infinity">>}; 210 | encode_parameter({{Year, Month, Day}, {Hour, Min, Sec}}, Type, OIDMap, IntegerDateTimes) when is_float(Sec) -> 211 | encode_parameter(lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~9.6.0f", [Year, Month, Day, Hour, Min, Sec])), Type, OIDMap, IntegerDateTimes); 212 | encode_parameter({{Year, Month, Day}, {Hour, Min, Sec}}, Type, OIDMap, IntegerDateTimes) -> 213 | encode_parameter(lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B", [Year, Month, Day, Hour, Min, Sec])), Type, OIDMap, IntegerDateTimes); 214 | encode_parameter({Hour, Min, Sec}, Type, OIDMap, IntegerDateTimes) when is_float(Sec) andalso Hour >= 0 andalso Hour < 24 andalso Min >= 0 andalso Min < 60 andalso Sec > 0 andalso Sec =< 60 -> 215 | encode_parameter(lists:flatten(io_lib:format("~2.10.0B:~2.10.0B:~9.6.0f", [Hour, Min, Sec])), Type, OIDMap, IntegerDateTimes); 216 | encode_parameter({Hour, Min, Sec}, Type, OIDMap, IntegerDateTimes) when Hour >= 0 andalso Hour < 24 andalso Min >= 0 andalso Min < 60 andalso Sec > 0 andalso Sec =< 60 -> 217 | encode_parameter(lists:flatten(io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Hour, Min, Sec])), Type, OIDMap, IntegerDateTimes); 218 | encode_parameter({Year, Month, Day}, Type, OIDMap, IntegerDateTimes) when Month > 0 andalso Month =< 12 andalso Day > 0 andalso Day =< 31 -> 219 | encode_parameter(lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B", [Year, Month, Day])), Type, OIDMap, IntegerDateTimes); 220 | encode_parameter({point, P}, _Type, _OIDMap, _IntegerDateTimes) -> 221 | Binary = encode_point_text(P), 222 | Size = byte_size(Binary), 223 | {text, <>}; 224 | encode_parameter({lseg, P1, P2}, _Type, _OIDMap, _IntegerDateTimes) -> 225 | P1Bin = encode_point_text(P1), 226 | P2Bin = encode_point_text(P2), 227 | Binary = <<$[, P1Bin/binary, $,, P2Bin/binary, $]>>, 228 | Size = byte_size(Binary), 229 | {text, <>}; 230 | encode_parameter({box, P1, P2}, _Type, _OIDMap, _IntegerDateTimes) -> 231 | P1Bin = encode_point_text(P1), 232 | P2Bin = encode_point_text(P2), 233 | Binary = <<$(, P1Bin/binary, $,, P2Bin/binary, $)>>, 234 | Size = byte_size(Binary), 235 | {text, <>}; 236 | encode_parameter({path, open, [_|_]=PList}, _Type, _OIDMap, _IntegerDateTimes) -> 237 | Binary = encode_points_text($[, $], PList), 238 | Size = byte_size(Binary), 239 | {text, <>}; 240 | encode_parameter({path, closed, [_|_]=PList}, _Type, _OIDMap, _IntegerDateTimes) -> 241 | Binary = encode_points_text($(, $), PList), 242 | Size = byte_size(Binary), 243 | {text, <>}; 244 | encode_parameter({polygon, [_|_]=PList}, _Type, _OIDMap, _IntegerDateTimes) -> 245 | Binary = encode_points_text($(, $), PList), 246 | Size = byte_size(Binary), 247 | {text, <>}; 248 | encode_parameter({inet, {X, Y, Z, T}}, _Type, _OIDMap, _IntegerDateTimes) -> 249 | Binary = <<2, 32, 0, 4, X, Y, Z, T>>, 250 | Size = byte_size(Binary), 251 | {binary, <>}; 252 | encode_parameter({inet, {A, B, C, D, E, F, G, H}}, _Type, _OIDMap, _IntegerDateTimes) -> 253 | Binary = <<3, 128, 0, 16, A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, 254 | Size = byte_size(Binary), 255 | {binary, <>}; 256 | encode_parameter({cidr, {X, Y, Z, T}, Bits}, _Type, _OIDMap, _IntegerDateTimes) -> 257 | Binary = <<2, Bits, 1, 4, X, Y, Z, T>>, 258 | Size = byte_size(Binary), 259 | {binary, <>}; 260 | encode_parameter({cidr, {A, B, C, D, E, F, G, H}, Bits}, _Type, _OIDMap, _IntegerDateTimes) -> 261 | Binary = <<3, Bits, 1, 16, A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, 262 | Size = byte_size(Binary), 263 | {binary, <>}; 264 | encode_parameter({Atom, Value}, _Type, _OIDMap, _IntegerDateTimes) when is_atom(Atom), is_binary(Value) -> 265 | Size = byte_size(Value), 266 | {text, <>}; 267 | encode_parameter(Value, _Type, _OIDMap, _IntegerDateTimes) -> 268 | throw({badarg, Value}). 269 | 270 | encode_point_text({X, Y}) -> 271 | XBin = list_to_binary(case is_integer(X) of true -> integer_to_list(X); false -> float_to_list(X) end), 272 | YBin = list_to_binary(case is_integer(Y) of true -> integer_to_list(Y); false -> float_to_list(Y) end), 273 | <<$(, XBin/binary, $,, YBin/binary, $)>>. 274 | 275 | encode_points_text(Prefix, Suffix, [PHead|PTail]) -> 276 | PHeadBin = encode_point_text(PHead), 277 | PTailBin = list_to_binary([begin PBin = encode_point_text(P), <<$,, PBin/binary>> end || P <- PTail]), 278 | <>. 279 | 280 | encode_array(Elements, ArrayType, OIDMap, IntegerDateTimes) -> 281 | ElementType = array_type_to_element_type(ArrayType, OIDMap), 282 | {EncodingType, ArrayElements} = encode_array_elements(Elements, ElementType, OIDMap, IntegerDateTimes, undefined, []), 283 | case EncodingType of 284 | binary when ElementType =/= undefined -> 285 | encode_array_binary(ArrayElements, ElementType); 286 | undefined when ElementType =/= undefined -> 287 | encode_array_binary(ArrayElements, ElementType); 288 | _ -> 289 | encode_array_text(ArrayElements, []) 290 | end. 291 | 292 | array_type_to_element_type(undefined, _OIDMap) -> undefined; 293 | array_type_to_element_type(?CIDRARRAYOID, _OIDMap) -> ?CIDROID; 294 | array_type_to_element_type(?BOOLARRAYOID, _OIDMap) -> ?BOOLOID; 295 | array_type_to_element_type(?BYTEAARRAYOID, _OIDMap) -> ?BYTEAOID; 296 | array_type_to_element_type(?CHARARRAYOID, _OIDMap) -> ?CHAROID; 297 | array_type_to_element_type(?NAMEARRAYOID, _OIDMap) -> ?NAMEOID; 298 | array_type_to_element_type(?INT2ARRAYOID, _OIDMap) -> ?INT2OID; 299 | array_type_to_element_type(?INT2VECTORARRAYOID, _OIDMap) -> ?INT2VECTOROID; 300 | array_type_to_element_type(?INT4ARRAYOID, _OIDMap) -> ?INT4OID; 301 | array_type_to_element_type(?REGPROCARRAYOID, _OIDMap) -> ?REGPROCOID; 302 | array_type_to_element_type(?TEXTARRAYOID, _OIDMap) -> ?TEXTOID; 303 | array_type_to_element_type(?TIDARRAYOID, _OIDMap) -> ?TIDOID; 304 | array_type_to_element_type(?XIDARRAYOID, _OIDMap) -> ?XIDOID; 305 | array_type_to_element_type(?CIDARRAYOID, _OIDMap) -> ?CIDOID; 306 | array_type_to_element_type(?OIDVECTORARRAYOID, _OIDMap) -> ?OIDVECTOROID; 307 | array_type_to_element_type(?BPCHARARRAYOID, _OIDMap) -> ?BPCHAROID; 308 | array_type_to_element_type(?VARCHARARRAYOID, _OIDMap) -> ?VARCHAROID; 309 | array_type_to_element_type(?INT8ARRAYOID, _OIDMap) -> ?INT8OID; 310 | array_type_to_element_type(?POINTARRAYOID, _OIDMap) -> ?POINTOID; 311 | array_type_to_element_type(?LSEGARRAYOID, _OIDMap) -> ?LSEGOID; 312 | array_type_to_element_type(?PATHARRAYOID, _OIDMap) -> ?PATHOID; 313 | array_type_to_element_type(?BOXARRAYOID, _OIDMap) -> ?BOXOID; 314 | array_type_to_element_type(?FLOAT4ARRAYOID, _OIDMap) -> ?FLOAT4OID; 315 | array_type_to_element_type(?FLOAT8ARRAYOID, _OIDMap) -> ?FLOAT8OID; 316 | array_type_to_element_type(?ABSTIMEARRAYOID, _OIDMap) -> ?ABSTIMEOID; 317 | array_type_to_element_type(?RELTIMEARRAYOID, _OIDMap) -> ?RELTIMEOID; 318 | array_type_to_element_type(?TINTERVALARRAYOID, _OIDMap) -> ?TINTERVALOID; 319 | array_type_to_element_type(?POLYGONARRAYOID, _OIDMap) -> ?POLYGONOID; 320 | array_type_to_element_type(?OIDARRAYOID, _OIDMap) -> ?OIDOID; 321 | array_type_to_element_type(?ACLITEMARRAYOID, _OIDMap) -> ?ACLITEMOID; 322 | array_type_to_element_type(?MACADDRARRAYOID, _OIDMap) -> ?MACADDROID; 323 | array_type_to_element_type(?INETARRAYOID, _OIDMap) -> ?INETOID; 324 | array_type_to_element_type(?CSTRINGARRAYOID, _OIDMap) -> ?CSTRINGOID; 325 | array_type_to_element_type(TypeOID, OIDMap) -> 326 | Type = decode_oid(TypeOID, OIDMap), 327 | if not is_atom(Type) -> undefined; 328 | true -> 329 | case atom_to_list(Type) of 330 | [$_ | ContentType] -> % Array 331 | OIDContentType = type_to_oid(list_to_atom(ContentType), OIDMap), 332 | OIDContentType; 333 | _ -> undefined 334 | end 335 | end. 336 | 337 | encode_array_elements([{array, SubArray} | Tail], ElementType, OIDMap, IntegerDateTimes, EncodingType, Acc) -> 338 | {NewEncodingType, SubArrayElements} = encode_array_elements(SubArray, ElementType, OIDMap, IntegerDateTimes, EncodingType, []), 339 | encode_array_elements(Tail, ElementType, OIDMap, IntegerDateTimes, NewEncodingType, [{array, SubArrayElements} | Acc]); 340 | encode_array_elements([null | Tail], ElementType, OIDMap, IntegerDateTimes, EncodingType, Acc) -> 341 | encode_array_elements(Tail, ElementType, OIDMap, IntegerDateTimes, EncodingType, [null | Acc]); 342 | encode_array_elements([Element | Tail], ElementType, OIDMap, IntegerDateTimes, undefined, Acc) -> 343 | {EncodingType, Encoded} = encode_parameter(Element, ElementType, OIDMap, IntegerDateTimes), 344 | encode_array_elements(Tail, ElementType, OIDMap, IntegerDateTimes, EncodingType, [Encoded | Acc]); 345 | encode_array_elements([Element | Tail], ElementType, OIDMap, IntegerDateTimes, EncodingType, Acc) -> 346 | {EncodingType, Encoded} = encode_parameter(Element, ElementType, OIDMap, IntegerDateTimes), 347 | encode_array_elements(Tail, ElementType, OIDMap, IntegerDateTimes, EncodingType, [Encoded | Acc]); 348 | encode_array_elements([], _ElementType, _OIDMap, _IntegerDateTimes, EncodingType, Acc) -> 349 | {EncodingType, lists:reverse(Acc)}. 350 | 351 | encode_array_text([null | Tail], Acc) -> 352 | encode_array_text(Tail, [<<"NULL">> | Acc]); 353 | encode_array_text([<<_TextSize:32/integer, Text/binary>> | Tail], Acc) -> 354 | Escaped = escape_array_text(Text), 355 | encode_array_text(Tail, [Escaped | Acc]); 356 | encode_array_text([{array, SubArray} | Tail], Acc) when is_list(SubArray) -> 357 | {text, <<_TextSize:32/integer, SubArrayEncoded/binary>>} = encode_array_text(SubArray, []), 358 | encode_array_text(Tail, [SubArrayEncoded | Acc]); 359 | encode_array_text([], Acc) -> 360 | StrList = lists:reverse(Acc), 361 | JoinedStrings = case StrList of 362 | [] -> []; 363 | [StrListHead | StrListTail] -> [StrListHead, [[<<",">>, Str] || Str <- StrListTail]] 364 | end, 365 | Binary = list_to_binary([<<"{">>, JoinedStrings, <<"}">>]), 366 | Size = byte_size(Binary), 367 | {text, <>}. 368 | 369 | escape_array_text(Text) when byte_size(Text) =:= 4 -> 370 | case string:to_lower(unicode:characters_to_list(Text)) of 371 | "null" -> <<$", Text/binary, $">>; 372 | _ -> escape_array_text0(Text, []) 373 | end; 374 | escape_array_text(Text) -> escape_array_text0(Text, []). 375 | 376 | escape_array_text0(Text, Acc) -> 377 | case binary:match(Text, [<<$">>, <<$,>>, <<$ >>, <<$\\>>]) of 378 | nomatch when Acc =:= [] -> Text; 379 | nomatch -> 380 | list_to_binary([<<$">>, lists:reverse(Acc, [Text]), <<$">>]); 381 | {Index, 1} -> 382 | {Prefix, Rest1} = split_binary(Text, Index), 383 | {ToEscape, Rest2} = split_binary(Rest1, 1), 384 | NewAcc = [<> | Acc], 385 | escape_array_text0(Rest2, NewAcc) 386 | end. 387 | 388 | encode_array_binary(ArrayElements, ElementTypeOID) -> 389 | {HasNulls, Rows} = encode_array_binary_row(ArrayElements, false, []), 390 | Dims = get_array_dims(ArrayElements), 391 | Header = encode_array_binary_header(Dims, HasNulls, ElementTypeOID), 392 | Encoded = list_to_binary([Header, Rows]), 393 | Size = byte_size(Encoded), 394 | {binary, <>}. 395 | 396 | encode_array_binary_row([null | Tail], _HasNull, Acc) -> 397 | encode_array_binary_row(Tail, true, [<<-1:32/integer>> | Acc]); 398 | encode_array_binary_row([<<_BinarySize:32/integer, _BinaryVal/binary>> = Binary | Tail], HasNull, Acc) -> 399 | encode_array_binary_row(Tail, HasNull, [Binary | Acc]); 400 | encode_array_binary_row([{array, Elements} | Tail], HasNull, Acc) -> 401 | {NewHasNull, Row} = encode_array_binary_row(Elements, HasNull, []), 402 | encode_array_binary_row(Tail, NewHasNull, [Row | Acc]); 403 | encode_array_binary_row([], HasNull, AccRow) -> 404 | {HasNull, lists:reverse(AccRow)}. 405 | 406 | get_array_dims([{array, SubElements} | _] = Row) -> 407 | Dims0 = get_array_dims(SubElements), 408 | Dim = length(Row), 409 | [Dim | Dims0]; 410 | get_array_dims(Row) -> 411 | Dim = length(Row), 412 | [Dim]. 413 | 414 | encode_array_binary_header(Dims, HasNulls, ElementTypeOID) -> 415 | NDims = length(Dims), 416 | Flags = if 417 | HasNulls -> 1; 418 | true -> 0 419 | end, 420 | EncodedDimensions = [<> || Dim <- Dims], 421 | [<< 422 | NDims:32/integer, 423 | Flags:32/integer, 424 | ElementTypeOID:32/integer 425 | >>, 426 | EncodedDimensions]. 427 | 428 | %%-------------------------------------------------------------------- 429 | %% @doc Determine if we need the statement description with these parameters. 430 | %% We currently only require statement descriptions if we have arrays of 431 | %% binaries. 432 | -spec bind_requires_statement_description([any()]) -> boolean(). 433 | bind_requires_statement_description([]) -> false; 434 | bind_requires_statement_description([{array, [{array, SubArrayElems} | SubArrayT]} | Tail]) -> 435 | bind_requires_statement_description([{array, SubArrayElems}, {array, SubArrayT} | Tail]); 436 | bind_requires_statement_description([{array, [ArrayElem | _]} | _]) when is_binary(ArrayElem) -> true; 437 | bind_requires_statement_description([{array, [null | ArrayElemsT]} | Tail]) -> 438 | bind_requires_statement_description([{array, ArrayElemsT} | Tail]); 439 | bind_requires_statement_description([{array, []} | Tail]) -> 440 | bind_requires_statement_description(Tail); 441 | bind_requires_statement_description([_OtherParam | Tail]) -> 442 | bind_requires_statement_description(Tail). 443 | 444 | %%-------------------------------------------------------------------- 445 | %% @doc Encode a describe message. 446 | %% 447 | -spec encode_describe_message(portal | statement, iodata()) -> binary(). 448 | encode_describe_message(PortalOrStatement, Name) -> 449 | NameBin = iolist_to_binary(Name), 450 | MessageLen = byte_size(NameBin) + 6, 451 | WhatByte = case PortalOrStatement of 452 | portal -> $P; 453 | statement -> $S 454 | end, 455 | <<$D, MessageLen:32/integer, WhatByte, NameBin/binary, 0>>. 456 | 457 | %%-------------------------------------------------------------------- 458 | %% @doc Encode an execute message. 459 | %% 460 | -spec encode_execute_message(iodata(), non_neg_integer()) -> binary(). 461 | encode_execute_message(PortalName, MaxRows) -> 462 | PortalNameBin = iolist_to_binary(PortalName), 463 | MessageLen = byte_size(PortalNameBin) + 9, 464 | <<$E, MessageLen:32/integer, PortalNameBin/binary, 0, MaxRows:32/integer>>. 465 | 466 | %%-------------------------------------------------------------------- 467 | %% @doc Encode a sync message. 468 | %% 469 | -spec encode_sync_message() -> binary(). 470 | encode_sync_message() -> 471 | <<$S, 4:32/integer>>. 472 | 473 | %%-------------------------------------------------------------------- 474 | %% @doc Encode a flush message. 475 | %% 476 | -spec encode_flush_message() -> binary(). 477 | encode_flush_message() -> 478 | <<$H, 4:32/integer>>. 479 | 480 | %%-------------------------------------------------------------------- 481 | %% @doc Encode a flush message. 482 | %% 483 | -spec encode_cancel_message(integer(), integer()) -> binary(). 484 | encode_cancel_message(ProcID, Secret) -> 485 | <<16:32/integer, 80877102:32/integer, ProcID:32/integer, Secret:32/integer>>. 486 | 487 | %%-------------------------------------------------------------------- 488 | %% @doc Encode a string message. 489 | %% 490 | -spec encode_string_message(byte(), iodata()) -> binary(). 491 | encode_string_message(Identifier, String) -> 492 | StringBin = iolist_to_binary(String), 493 | MessageLen = byte_size(StringBin) + 5, 494 | <>. 495 | 496 | %%-------------------------------------------------------------------- 497 | %% @doc Decode a message. 498 | %% 499 | -spec decode_message(byte(), binary()) -> {ok, pgsql_backend_message()} | {error, any()}. 500 | decode_message($R, Payload) -> decode_authentication_message(Payload); 501 | decode_message($K, Payload) -> decode_backend_key_data_message(Payload); 502 | decode_message($2, Payload) -> decode_bind_complete_message(Payload); 503 | decode_message($3, Payload) -> decode_close_complete_message(Payload); 504 | decode_message($C, Payload) -> decode_command_complete_message(Payload); 505 | decode_message($d, Payload) -> decode_copy_data_message(Payload); 506 | decode_message($c, Payload) -> decode_copy_done_message(Payload); 507 | decode_message($G, Payload) -> decode_copy_in_response_message(Payload); 508 | decode_message($H, Payload) -> decode_copy_out_response_message(Payload); 509 | decode_message($W, Payload) -> decode_copy_both_response_message(Payload); 510 | decode_message($D, Payload) -> decode_data_row_message(Payload); 511 | decode_message($I, Payload) -> decode_empty_query_response_message(Payload); 512 | decode_message($E, Payload) -> decode_error_response_message(Payload); 513 | decode_message($V, Payload) -> decode_function_call_response_message(Payload); 514 | decode_message($n, Payload) -> decode_no_data_message(Payload); 515 | decode_message($N, Payload) -> decode_notice_response_message(Payload); 516 | decode_message($A, Payload) -> decode_notification_response_message(Payload); 517 | decode_message($t, Payload) -> decode_parameter_description_message(Payload); 518 | decode_message($S, Payload) -> decode_parameter_status_message(Payload); 519 | decode_message($1, Payload) -> decode_parse_complete_message(Payload); 520 | decode_message($s, Payload) -> decode_portal_suspended_message(Payload); 521 | decode_message($Z, Payload) -> decode_ready_for_query_message(Payload); 522 | decode_message($T, Payload) -> decode_row_description_message(Payload); 523 | decode_message(Other, _) -> 524 | {error, {unknown_message_type, Other}}. 525 | 526 | decode_authentication_message(<<0:32/integer>>) -> 527 | {ok, #authentication_ok{}}; 528 | decode_authentication_message(<<2:32/integer>>) -> 529 | {ok, #authentication_kerberos_v5{}}; 530 | decode_authentication_message(<<3:32/integer>>) -> 531 | {ok, #authentication_cleartext_password{}}; 532 | decode_authentication_message(<<5:32/integer, Salt:4/binary>>) -> 533 | {ok, #authentication_md5_password{salt = Salt}}; 534 | decode_authentication_message(<<6:32/integer>>) -> 535 | {ok, #authentication_scm_credential{}}; 536 | decode_authentication_message(<<7:32/integer>>) -> 537 | {ok, #authentication_gss{}}; 538 | decode_authentication_message(<<9:32/integer>>) -> 539 | {ok, #authentication_sspi{}}; 540 | decode_authentication_message(<<8:32/integer, Rest/binary>>) -> 541 | {ok, #authentication_gss_continue{data = Rest}}; 542 | decode_authentication_message(Payload) -> 543 | {error, {unknown_message, authentication, Payload}}. 544 | 545 | decode_backend_key_data_message(<>) -> 546 | {ok, #backend_key_data{procid = ProcID, secret = Secret}}; 547 | decode_backend_key_data_message(Payload) -> 548 | {error, {unknown_message, backend_key_data, Payload}}. 549 | 550 | decode_bind_complete_message(<<>>) -> {ok, #bind_complete{}}; 551 | decode_bind_complete_message(Payload) -> 552 | {error, {unknown_message, bind_complete, Payload}}. 553 | 554 | decode_close_complete_message(<<>>) -> {ok, #close_complete{}}; 555 | decode_close_complete_message(Payload) -> 556 | {error, {unknown_message, close_complete, Payload}}. 557 | 558 | decode_command_complete_message(Payload) -> 559 | case decode_string(Payload) of 560 | {ok, String, <<>>} -> {ok, #command_complete{command_tag = String}}; 561 | _ -> {error, {unknown_message, command_complete, Payload}} 562 | end. 563 | 564 | decode_copy_data_message(Payload) -> {ok, #copy_data{data = Payload}}. 565 | 566 | decode_copy_done_message(<<>>) -> {ok, #copy_done{}}; 567 | decode_copy_done_message(Payload) -> 568 | {error, {unknown_message, copy_done, Payload}}. 569 | 570 | decode_copy_in_response_message(Payload) -> 571 | case decode_copy_response_message(Payload) of 572 | {ok, {OverallFormat, N, ColumnFormats}} -> {ok, #copy_in_response{format = OverallFormat, columns = N, column_formats = ColumnFormats}}; 573 | {error, _} -> {error, {unknow_message, copy_in_response, Payload}} 574 | end. 575 | 576 | decode_copy_out_response_message(Payload) -> 577 | case decode_copy_response_message(Payload) of 578 | {ok, {OverallFormat, N, ColumnFormats}} -> {ok, #copy_out_response{format = OverallFormat, columns = N, column_formats = ColumnFormats}}; 579 | {error, _} -> {error, {unknow_message, copy_out_response, Payload}} 580 | end. 581 | 582 | decode_copy_both_response_message(Payload) -> 583 | case decode_copy_response_message(Payload) of 584 | {ok, {OverallFormat, N, ColumnFormats}} -> {ok, #copy_both_response{format = OverallFormat, columns = N, column_formats = ColumnFormats}}; 585 | {error, _} -> {error, {unknow_message, copy_both_response, Payload}} 586 | end. 587 | 588 | decode_data_row_message(<> = Payload) -> 589 | case decode_data_row_values(N, Rest) of 590 | {ok, Values} -> {ok, #data_row{values = Values}}; 591 | {error, _} -> 592 | {error, {unknow_message, data_row, Payload}} 593 | end; 594 | decode_data_row_message(Payload) -> 595 | {error, {unknow_message, data_row, Payload}}. 596 | 597 | decode_data_row_values(Columns, Binary) -> 598 | decode_data_row_values0(Binary, Columns, []). 599 | 600 | decode_data_row_values0(<<>>, 0, Acc) -> {ok, lists:reverse(Acc)}; 601 | decode_data_row_values0(<<-1:32/signed-integer, Rest/binary>>, N, Acc) when N > 0 -> 602 | decode_data_row_values0(Rest, N - 1, [null | Acc]); 603 | decode_data_row_values0(<>, N, Acc) when N > 0 -> 604 | decode_data_row_values0(Rest, N - 1, [ValueBin | Acc]); 605 | decode_data_row_values0(<<_/binary>>, _N, _Acc) -> {error, invalid_value_len}. 606 | 607 | decode_empty_query_response_message(<<>>) -> {ok, #empty_query_response{}}; 608 | decode_empty_query_response_message(Payload) -> 609 | {error, {unknown_message, empty_query_response, Payload}}. 610 | 611 | decode_error_response_message(Payload) -> 612 | case decode_error_and_notice_message_fields(Payload) of 613 | {ok, Fields} -> {ok, #error_response{fields = Fields}}; 614 | {error, _} -> {error, {unknown_message, error_response, Payload}} 615 | end. 616 | 617 | decode_function_call_response_message(<<-1:32/signed-integer>>) -> {ok, #function_call_response{value = null}}; 618 | decode_function_call_response_message(<>) -> {ok, #function_call_response{value = Value}}; 619 | decode_function_call_response_message(Payload) -> 620 | {error, {unknown_message, function_call_response, Payload}}. 621 | 622 | decode_no_data_message(<<>>) -> {ok, #no_data{}}; 623 | decode_no_data_message(Payload) -> 624 | {error, {unknown_message, no_data, Payload}}. 625 | 626 | decode_notice_response_message(Payload) -> 627 | case decode_error_and_notice_message_fields(Payload) of 628 | {ok, Fields} -> {ok, #notice_response{fields = Fields}}; 629 | {error, _} -> {error, {unknown_message, notice_response, Payload}} 630 | end. 631 | 632 | decode_notification_response_message(<> = Payload) -> 633 | case decode_string(Rest0) of 634 | {ok, Channel, Rest1} -> 635 | case decode_string(Rest1) of 636 | {ok, PayloadStr, <<>>} -> {ok, #notification_response{procid = ProcID, channel = Channel, payload = PayloadStr}}; 637 | {error, _} -> {error, {unknown_message, notification_response, Payload}} 638 | end; 639 | {error, _} -> {error, {unknown_message, notification_response, Payload}} 640 | end; 641 | decode_notification_response_message(Payload) -> 642 | {error, {unknown_message, notification_response, Payload}}. 643 | 644 | decode_parameter_description_message(<> = Payload) -> 645 | ParameterDataTypes = decode_parameter_data_types(Rest), 646 | if 647 | Count =:= length(ParameterDataTypes) -> 648 | {ok, #parameter_description{count = Count, data_types = ParameterDataTypes}}; 649 | true -> 650 | {error, {unknown_message, parameter_description, Payload}} 651 | end; 652 | decode_parameter_description_message(Payload) -> 653 | {error, {unknown_message, parameter_description, Payload}}. 654 | 655 | decode_parameter_status_message(Payload) -> 656 | case decode_string(Payload) of 657 | {ok, Name, Rest0} -> 658 | case decode_string(Rest0) of 659 | {ok, Value, <<>>} -> {ok, #parameter_status{name = Name, value = Value}}; 660 | {error, _} -> {error, {unknown_message, parameter_status, Payload}} 661 | end; 662 | {error, _} -> {error, {unknown_message, parameter_status, Payload}} 663 | end. 664 | 665 | decode_parse_complete_message(<<>>) -> {ok, #parse_complete{}}; 666 | decode_parse_complete_message(Payload) -> 667 | {error, {unknown_message, parse_complete, Payload}}. 668 | 669 | decode_portal_suspended_message(<<>>) -> {ok, #portal_suspended{}}; 670 | decode_portal_suspended_message(Payload) -> 671 | {error, {unknown_message, portal_suspended, Payload}}. 672 | 673 | decode_ready_for_query_message(<<$I>>) -> {ok, #ready_for_query{transaction_status = idle}}; 674 | decode_ready_for_query_message(<<$T>>) -> {ok, #ready_for_query{transaction_status = transaction}}; 675 | decode_ready_for_query_message(<<$E>>) -> {ok, #ready_for_query{transaction_status = error}}; 676 | decode_ready_for_query_message(Payload) -> 677 | {error, {unknown_message, ready_for_query, Payload}}. 678 | 679 | decode_row_description_message(<> = Payload) when Count >= 0 -> 680 | case decode_row_description_message0(Count, Rest, []) of 681 | {ok, Fields} -> {ok, #row_description{count = Count, fields = Fields}}; 682 | {error, _} -> 683 | {error, {unknown_message, row_description, Payload}} 684 | end; 685 | decode_row_description_message(Payload) -> 686 | {error, {unknown_message, row_description, Payload}}. 687 | 688 | decode_row_description_message0(0, <<>>, Acc) -> {ok, lists:reverse(Acc)}; 689 | decode_row_description_message0(Count, Binary, Acc) -> 690 | case decode_string(Binary) of 691 | {ok, FieldName, <>} -> 692 | case decode_format_code(FormatCode) of 693 | {ok, Format} -> 694 | Field = #row_description_field{ 695 | name = FieldName, 696 | table_oid = TableOid, 697 | attr_number = AttrNum, 698 | data_type_oid = DataTypeOid, 699 | data_type_size = DataTypeSize, 700 | type_modifier = TypeModifier, 701 | format = Format}, 702 | decode_row_description_message0(Count - 1, Tail, [Field | Acc]); 703 | {error, _} = Error -> Error 704 | end; 705 | {error, _} = Error -> Error; 706 | _ -> {error, unknown_message} 707 | end. 708 | 709 | %%% Helper functions. 710 | 711 | decode_copy_response_message(<>) when Format =:= 0 orelse Format =:= 1 -> 712 | {ok, OverallFormat} = decode_format_code(Format), 713 | if 714 | byte_size(Rest) =:= N * 2 -> 715 | case decode_format_codes(Rest) of 716 | {ok, ColumnFormats} -> 717 | {ok, {OverallFormat, N, ColumnFormats}}; 718 | {error, _} -> {error, column_formats} 719 | end; 720 | true -> 721 | {error, column_formats_size} 722 | end; 723 | decode_copy_response_message(Payload) -> 724 | {error, {unknown_message, copy_response, Payload}}. 725 | 726 | decode_error_and_notice_message_fields(Binary) -> 727 | decode_error_and_notice_message_fields0(Binary, []). 728 | 729 | decode_error_and_notice_message_fields0(<<0>>, Acc) -> 730 | case application:get_env(pgsql, errors_as_maps) of 731 | {ok, true} -> 732 | {ok, maps:from_list(Acc)}; 733 | _ -> 734 | {ok, lists:reverse(Acc)} 735 | end; 736 | 737 | decode_error_and_notice_message_fields0(<>, Acc) -> 738 | case decode_string(Rest0) of 739 | {ok, FieldString, Rest1} -> 740 | FieldTypeSym = decode_error_and_mention_field_type(FieldType), 741 | Field = {FieldTypeSym, FieldString}, 742 | NewAcc = [Field | Acc], 743 | decode_error_and_notice_message_fields0(Rest1, NewAcc); 744 | {error, _} = Error -> Error 745 | end; 746 | decode_error_and_notice_message_fields0(Bin, _Acc) -> {error, {badarg, Bin}}. 747 | 748 | -spec decode_error_and_mention_field_type(byte()) -> pgsql_error:pgsql_error_and_mention_field_type(). 749 | decode_error_and_mention_field_type($S) -> severity; 750 | decode_error_and_mention_field_type($C) -> code; 751 | decode_error_and_mention_field_type($M) -> message; 752 | decode_error_and_mention_field_type($D) -> detail; 753 | decode_error_and_mention_field_type($H) -> hint; 754 | decode_error_and_mention_field_type($P) -> position; 755 | decode_error_and_mention_field_type($p) -> internal_position; 756 | decode_error_and_mention_field_type($q) -> internal_query; 757 | decode_error_and_mention_field_type($W) -> where; 758 | decode_error_and_mention_field_type($s) -> schema; 759 | decode_error_and_mention_field_type($t) -> table; 760 | decode_error_and_mention_field_type($c) -> column; 761 | decode_error_and_mention_field_type($d) -> data_type; 762 | decode_error_and_mention_field_type($n) -> constraint; 763 | decode_error_and_mention_field_type($F) -> file; 764 | decode_error_and_mention_field_type($L) -> line; 765 | decode_error_and_mention_field_type($R) -> routine; 766 | decode_error_and_mention_field_type(Other) -> {unknown, Other}. 767 | 768 | decode_parameter_data_types(Binary) -> 769 | decode_parameter_data_types0(Binary, []). 770 | 771 | decode_parameter_data_types0(<<>>, Acc) -> lists:reverse(Acc); 772 | decode_parameter_data_types0(<>, Acc) -> 773 | decode_parameter_data_types0(Tail, [Oid | Acc]). 774 | 775 | -spec decode_format_code(integer()) -> {ok, pgsql_format()} | {error, any()}. 776 | decode_format_code(0) -> {ok, text}; 777 | decode_format_code(1) -> {ok, binary}; 778 | decode_format_code(_Other) -> {error, unknown_format_code}. 779 | 780 | -spec decode_format_codes(binary()) -> {ok, [pgsql_format()]} | {error, any()}. 781 | decode_format_codes(Binary) -> 782 | decode_format_codes0(Binary, []). 783 | 784 | decode_format_codes0(<>, Acc) -> 785 | case decode_format_code(FormatCode) of 786 | {ok, Format} -> 787 | decode_format_codes0(Tail, [Format | Acc]); 788 | {error, _} = Error -> Error 789 | end; 790 | decode_format_codes0(<<>>, Acc) -> {ok, lists:reverse(Acc)}. 791 | 792 | -spec decode_string(binary()) -> {ok, binary(), binary()} | {error, not_null_terminated}. 793 | decode_string(Binary) -> 794 | case binary:match(Binary, <<0>>) of 795 | nomatch -> {error, not_null_terminated}; 796 | {Position, 1} -> 797 | {String, <<0, Rest/binary>>} = split_binary(Binary, Position), 798 | {ok, String, Rest} 799 | end. 800 | 801 | %%-------------------------------------------------------------------- 802 | %% @doc Decode a row format. 803 | %% 804 | -spec decode_row([#row_description_field{}], [binary()], pgsql_oid_map(), proplists:proplist()) -> tuple(). 805 | decode_row(Descs, Values, OIDMap, DecodeOptions) -> 806 | decode_row0(Descs, Values, OIDMap, DecodeOptions, []). 807 | 808 | decode_row0([Desc | DescsT], [Value | ValuesT], OIDMap, DecodeOptions, Acc) -> 809 | DecodedValue = decode_value(Desc, Value, OIDMap, DecodeOptions), 810 | case proplists:get_bool(return_maps, DecodeOptions) of 811 | true -> 812 | #row_description_field{name = FieldName} = Desc, 813 | decode_row0(DescsT, ValuesT, OIDMap, DecodeOptions, [{FieldName, DecodedValue} | Acc]); 814 | false -> 815 | decode_row0(DescsT, ValuesT, OIDMap, DecodeOptions, [DecodedValue | Acc]) 816 | end; 817 | decode_row0([], [], _OIDMap, DecodeOptions, Acc) -> 818 | case proplists:get_bool(return_maps, DecodeOptions) of 819 | true -> 820 | maps:from_list(Acc); 821 | false -> 822 | list_to_tuple(lists:reverse(Acc)) 823 | end. 824 | 825 | decode_value(_Desc, null, _OIDMap, _DecodeOptions) -> null; 826 | decode_value(#row_description_field{data_type_oid = TypeOID, format = text}, Value, OIDMap, DecodeOptions) -> 827 | decode_value_text(TypeOID, Value, OIDMap, DecodeOptions); 828 | decode_value(#row_description_field{data_type_oid = DataTypeOID, format = binary}, Value, OIDMap, DecodeOptions) -> 829 | decode_value_bin(DataTypeOID, Value, OIDMap, DecodeOptions). 830 | 831 | decode_value_text(TypeOID, Value, _OIDMap, _DecodeOptions) when TypeOID =:= ?INT8OID orelse TypeOID =:= ?INT2OID orelse TypeOID =:= ?INT4OID orelse TypeOID =:= ?OIDOID -> 832 | list_to_integer(binary_to_list(Value)); 833 | decode_value_text(TypeOID, Value, _OIDMap, _DecodeOptions) when TypeOID =:= ?FLOAT4OID orelse TypeOID =:= ?FLOAT8OID orelse TypeOID =:= ?NUMERICOID -> 834 | case Value of 835 | <<"NaN">> -> 'NaN'; 836 | <<"Infinity">> -> 'Infinity'; 837 | <<"-Infinity">> -> '-Infinity'; 838 | _ -> 839 | FloatStr = binary_to_list(Value), 840 | case lists:member($., FloatStr) of 841 | true -> list_to_float(FloatStr); 842 | false when TypeOID =:= ?NUMERICOID -> list_to_integer(FloatStr); 843 | false -> list_to_integer(FloatStr) * 1.0 844 | end 845 | end; 846 | decode_value_text(?BOOLOID, <<"t">>, _OIDMap, _DecodeOptions) -> true; 847 | decode_value_text(?BOOLOID, <<"f">>, _OIDMap, _DecodeOptions) -> false; 848 | decode_value_text(?BYTEAOID, <<"\\", _Encoded/binary>> = Value, _OIDMap, _DecodeOptions) -> 849 | <<"\\x", HexEncoded/binary>> = Value, 850 | Decoded = decode_hex(HexEncoded), 851 | list_to_binary(Decoded); 852 | decode_value_text(?BYTEAOID, Value, _OIDMap, _DecodeOptions) -> Value; 853 | decode_value_text(?DATEOID, Value, _OIDMap, _DecodeOptions) -> 854 | {ok, [Year, Month, Day], []} = io_lib:fread("~u-~u-~u", binary_to_list(Value)), 855 | {Year, Month, Day}; 856 | decode_value_text(?TIMEOID, Value, _OIDMap, DecodeOptions) -> 857 | {ok, [Hour, Min], SecsStr} = io_lib:fread("~u:~u:", binary_to_list(Value)), 858 | {Secs, 0} = decode_secs_and_tz(SecsStr, DecodeOptions), 859 | {Hour, Min, Secs}; 860 | decode_value_text(?TIMETZOID, Value, _OIDMap, DecodeOptions) -> 861 | {ok, [Hour, Min], SecsStr0} = io_lib:fread("~u:~u:", binary_to_list(Value)), 862 | {Secs, TZDelta} = decode_secs_and_tz(SecsStr0, DecodeOptions), 863 | RawTime = {Hour, Min, Secs}, 864 | adjust_time(RawTime, TZDelta); 865 | decode_value_text(TypeOID, <<"infinity">>, _OIDMap, _DecodeOptions) when (TypeOID =:= ?TIMESTAMPOID orelse TypeOID =:= ?TIMESTAMPTZOID) -> infinity; 866 | decode_value_text(TypeOID, <<"-infinity">>, _OIDMap, _DecodeOptions) when (TypeOID =:= ?TIMESTAMPOID orelse TypeOID =:= ?TIMESTAMPTZOID) -> '-infinity'; 867 | decode_value_text(?TIMESTAMPOID, Value, _OIDMap, DecodeOptions) -> 868 | {ok, [Year, Month, Day, Hour, Min], SecsStr} = io_lib:fread("~u-~u-~u ~u:~u:", binary_to_list(Value)), 869 | {Secs, 0} = decode_secs_and_tz(SecsStr, DecodeOptions), 870 | {{Year, Month, Day}, {Hour, Min, Secs}}; 871 | decode_value_text(?TIMESTAMPTZOID, Value, _OIDMap, DecodeOptions) -> 872 | {ok, [Year, Month, Day, Hour, Min], SecsStr0} = io_lib:fread("~u-~u-~u ~u:~u:", binary_to_list(Value)), 873 | {Secs, TZDelta} = decode_secs_and_tz(SecsStr0, DecodeOptions), 874 | case TZDelta of 875 | 0 -> {{Year, Month, Day}, {Hour, Min, Secs}}; 876 | _ -> 877 | {{AdjYear, AdjMonth, AdjDay}, {AdjHour, AdjMin, 0}} = 878 | calendar:gregorian_seconds_to_datetime( 879 | calendar:datetime_to_gregorian_seconds({{Year, Month, Day}, {Hour, Min, 0}}) - (TZDelta * 60)), 880 | {{AdjYear, AdjMonth, AdjDay}, {AdjHour, AdjMin, Secs}} 881 | end; 882 | decode_value_text(?POINTOID, Value, _OIDMap, _DecodeOptions) -> 883 | {P, []} = decode_point_text(binary_to_list(Value)), 884 | {point, P}; 885 | decode_value_text(?LSEGOID, Value, _OIDMap, _DecodeOptions) -> 886 | [$[|P1Str] = binary_to_list(Value), 887 | {P1, [$,|P2Str]} = decode_point_text(P1Str), 888 | {P2, "]"} = decode_point_text(P2Str), 889 | {lseg, P1, P2}; 890 | decode_value_text(?BOXOID, Value, _OIDMap, _DecodeOptions) -> 891 | P1Str = binary_to_list(Value), 892 | {P1, [$,|P2Str]} = decode_point_text(P1Str), 893 | {P2, []} = decode_point_text(P2Str), 894 | {box, P1, P2}; 895 | decode_value_text(?PATHOID, <<$[,_/binary>> = Value, _OIDMap, _DecodeOptions) -> 896 | {Points, []} = decode_points_text($[, $], binary_to_list(Value)), 897 | {path, open, Points}; 898 | decode_value_text(?PATHOID, <<$(,_/binary>> = Value, _OIDMap, _DecodeOptions) -> 899 | {Points, []} = decode_points_text($(, $), binary_to_list(Value)), 900 | {path, closed, Points}; 901 | decode_value_text(?POLYGONOID, Value, _OIDMap, _DecodeOptions) -> 902 | {Points, []} = decode_points_text($(, $), binary_to_list(Value)), 903 | {polygon, Points}; 904 | decode_value_text(?VOIDOID, _Value, _OIDMap, _DecodeOptions) -> null; 905 | decode_value_text(TypeOID, Value, _OIDMap, _DecodeOptions) when TypeOID =:= ?TEXTOID 906 | orelse TypeOID =:= ?UUIDOID 907 | orelse TypeOID =:= ?NAMEOID 908 | orelse TypeOID =:= ?BPCHAROID 909 | orelse TypeOID =:= ?VARCHAROID 910 | -> Value; 911 | decode_value_text(?CIDROID, Value, _OIDMap, _DecodeOptions) -> 912 | [AddrStr, BitSizeStr] = binary:split(Value, <<"/">>), 913 | {ok, Addr} = inet:parse_address(binary_to_list(AddrStr)), 914 | BitSize = binary_to_integer(BitSizeStr), 915 | {cidr, Addr, BitSize}; 916 | decode_value_text(?INETOID, Value, _OIDMap, _DecodeOptions) -> 917 | {ok, Addr} = inet:parse_address(binary_to_list(Value)), 918 | {inet, Addr}; 919 | decode_value_text(TypeOID, Value, OIDMap, DecodeOptions) -> 920 | Type = decode_oid(TypeOID, OIDMap), 921 | if not is_atom(Type) -> {Type, Value}; 922 | true -> 923 | case atom_to_list(Type) of 924 | [$_ | ContentType] -> % Array 925 | OIDContentType = type_to_oid(list_to_atom(ContentType), OIDMap), 926 | {R, _} = decode_array_text(OIDContentType, OIDMap, DecodeOptions, Value, []), 927 | R; 928 | _ -> {Type, Value} 929 | end 930 | end. 931 | 932 | decode_point_text(Str) -> 933 | {X, AfterX} = 934 | case io_lib:fread("(~f,", Str) of 935 | {ok, [X0], AfterX0} -> {X0, AfterX0}; 936 | {error, {fread, float}} -> 937 | {ok, [X0], AfterX0} = io_lib:fread("(~d,", Str), 938 | {X0 * 1.0, AfterX0} 939 | end, 940 | {Y, AfterY} = 941 | case io_lib:fread("~f)", AfterX) of 942 | {ok, [Y0], AfterY0} -> {Y0, AfterY0}; 943 | {error, {fread, float}} -> 944 | {ok, [Y0], AfterY0} = io_lib:fread("~d)", AfterX), 945 | {Y0 * 1.0, AfterY0} 946 | end, 947 | {{X, Y}, AfterY}. 948 | 949 | decode_points_text(Prefix, Suffix, PStr) -> 950 | decode_points_text_aux(Prefix, Suffix, PStr, []). 951 | 952 | decode_points_text_aux(Prefix, Suffix, [Before|PStr], PAcc) when Before =:= $, orelse Before =:= Prefix -> 953 | {P, AfterP} = decode_point_text(PStr), 954 | decode_points_text_aux(Prefix, Suffix, AfterP, [P|PAcc]); 955 | decode_points_text_aux(_, Suffix, [Suffix|After], PAcc) -> 956 | {lists:reverse(PAcc), After}. 957 | 958 | decode_secs_and_tz(SecsStr, DecodeOptions) -> 959 | {Secs, TZDelta} = decode_secs_and_tz0(SecsStr, false, []), 960 | CastSecs = cast_datetime_secs(Secs, DecodeOptions), 961 | {CastSecs, TZDelta}. 962 | 963 | cast_datetime_secs(Secs, DecodeOptions) -> 964 | case proplists:get_value(datetime_float_seconds, DecodeOptions, as_available) of 965 | round -> round(Secs); 966 | always -> Secs * 1.0; 967 | as_available -> Secs 968 | end. 969 | 970 | cast_datetime_usecs(Secs0, USecs, DecodeOptions) -> 971 | Secs1 = case USecs of 972 | 0 -> Secs0; 973 | _ -> Secs0 + USecs / 1000000 974 | end, 975 | cast_datetime_secs(Secs1, DecodeOptions). 976 | 977 | decode_secs_and_tz0([], IsFloat, AccSecs) -> 978 | decode_secs_and_tz2(lists:reverse(AccSecs), IsFloat, 0); 979 | decode_secs_and_tz0([$. | T], _IsFloat, AccSecs) -> 980 | decode_secs_and_tz0(T, true, [$. | AccSecs]); 981 | decode_secs_and_tz0([$+ | T], IsFloat, AccSecs) -> 982 | decode_secs_and_tz1(lists:reverse(AccSecs), IsFloat, 1, T); 983 | decode_secs_and_tz0([$- | T], IsFloat, AccSecs) -> 984 | decode_secs_and_tz1(lists:reverse(AccSecs), IsFloat, -1, T); 985 | decode_secs_and_tz0([Digit | T], IsFloat, AccSecs) -> 986 | decode_secs_and_tz0(T, IsFloat, [Digit | AccSecs]). 987 | 988 | decode_secs_and_tz1(SecsStr, IsFloat, Sign, TZStr) -> 989 | TZDelta = case TZStr of 990 | [TZH1, TZH2] -> list_to_integer([TZH1, TZH2]) * 60; 991 | [TZH1, TZH2, TZM1, TZM2] -> list_to_integer([TZH1, TZH2]) * 60 + list_to_integer([TZM1, TZM2]); 992 | [TZH1, TZH2, $:, TZM1, TZM2] -> list_to_integer([TZH1, TZH2]) * 60 + list_to_integer([TZM1, TZM2]) 993 | end, 994 | decode_secs_and_tz2(SecsStr, IsFloat, Sign * TZDelta). 995 | 996 | decode_secs_and_tz2(SecsStr, true, TZDelta) -> 997 | {list_to_float(SecsStr), TZDelta}; 998 | decode_secs_and_tz2(SecsStr, false, TZDelta) -> 999 | {list_to_integer(SecsStr), TZDelta}. 1000 | 1001 | type_to_oid(Type, OIDMap) -> 1002 | List = gb_trees:to_list(OIDMap), 1003 | {OIDType, _} = lists:keyfind(Type, 2, List), 1004 | OIDType. 1005 | 1006 | 1007 | decode_array_text(_Type, _OIDMap, _DecodeOptions, <<>>, [Acc]) -> 1008 | {Acc, <<>>}; 1009 | decode_array_text(Type, OIDMap, DecodeOptions, <<"{", Next/binary>>, Acc) -> 1010 | {R, Next2} = decode_array_text(Type, OIDMap, DecodeOptions, Next, []), 1011 | decode_array_text(Type, OIDMap, DecodeOptions, Next2, [{array, R} | Acc]); 1012 | decode_array_text(_Type, _OIDMap, _DecodeOptions, <<"}", Next/binary>>, Acc) -> 1013 | {lists:reverse(Acc), Next}; 1014 | decode_array_text(Type, OIDMap, DecodeOptions, <<",", Next/binary>>, Acc) -> 1015 | decode_array_text(Type, OIDMap, DecodeOptions, Next, Acc); 1016 | decode_array_text(Type, OIDMap, DecodeOptions, Content0, Acc) -> 1017 | {Value, Rest} = decode_array_text0(Content0, false, []), 1018 | Element = case Value of 1019 | <<"NULL">> -> null; 1020 | _ -> decode_value_text(Type, Value, OIDMap, DecodeOptions) 1021 | end, 1022 | decode_array_text(Type, OIDMap, DecodeOptions, Rest, [Element|Acc]). 1023 | 1024 | decode_array_text0(<<$", Rest/binary>>, false, []) -> 1025 | decode_array_text0(Rest, true, []); 1026 | decode_array_text0(<<$,, Rest/binary>>, false, Acc) -> 1027 | {list_to_binary(lists:reverse(Acc)), Rest}; 1028 | decode_array_text0(<<$}, _/binary>> = Content, false, Acc) -> 1029 | {list_to_binary(lists:reverse(Acc)), Content}; 1030 | decode_array_text0(<<$", $,, Rest/binary>>, true, Acc) -> 1031 | {list_to_binary(lists:reverse(Acc)), Rest}; 1032 | decode_array_text0(<<$", $}, Rest/binary>>, true, Acc) -> 1033 | {list_to_binary(lists:reverse(Acc)), <<$}, Rest/binary>>}; 1034 | decode_array_text0(<<$\\, C, Rest/binary>>, true, Acc) -> 1035 | decode_array_text0(Rest, true, [C | Acc]); 1036 | decode_array_text0(<>, Quoted, Acc) -> 1037 | decode_array_text0(Rest, Quoted, [C | Acc]). 1038 | 1039 | decode_value_bin(?JSONBOID, <>, _OIDMap, _DecodeOptions) -> {jsonb, Value}; 1040 | decode_value_bin(?JSONOID, Value, _OIDMap, _DecodeOptions) -> {json, Value}; 1041 | decode_value_bin(?BOOLOID, <<0>>, _OIDMap, _DecodeOptions) -> false; 1042 | decode_value_bin(?BOOLOID, <<1>>, _OIDMap, _DecodeOptions) -> true; 1043 | decode_value_bin(?BYTEAOID, Value, _OIDMap, _DecodeOptions) -> Value; 1044 | decode_value_bin(?NAMEOID, Value, _OIDMap, _DecodeOptions) -> Value; 1045 | decode_value_bin(?INT8OID, <>, _OIDMap, _DecodeOptions) -> Value; 1046 | decode_value_bin(?INT2OID, <>, _OIDMap, _DecodeOptions) -> Value; 1047 | decode_value_bin(?INT4OID, <>, _OIDMap, _DecodeOptions) -> Value; 1048 | decode_value_bin(?OIDOID, <>, _OIDMap, _DecodeOptions) -> Value; 1049 | decode_value_bin(?TEXTOID, Value, _OIDMap, _DecodeOptions) -> Value; 1050 | decode_value_bin(?BPCHAROID, Value, _OIDMap, _DecodeOptions) -> Value; 1051 | decode_value_bin(?VARCHAROID, Value, _OIDMap, _DecodeOptions) -> Value; 1052 | decode_value_bin(?FLOAT4OID, <>, _OIDMap, _DecodeOptions) -> Value; 1053 | decode_value_bin(?FLOAT4OID, <<127,192,0,0>>, _OIDMap, _DecodeOptions) -> 'NaN'; 1054 | decode_value_bin(?FLOAT4OID, <<127,128,0,0>>, _OIDMap, _DecodeOptions) -> 'Infinity'; 1055 | decode_value_bin(?FLOAT4OID, <<255,128,0,0>>, _OIDMap, _DecodeOptions) -> '-Infinity'; 1056 | decode_value_bin(?FLOAT8OID, <>, _OIDMap, _DecodeOptions) -> Value; 1057 | decode_value_bin(?FLOAT8OID, <<127,248,0,0,0,0,0,0>>, _OIDMap, _DecodeOptions) -> 'NaN'; 1058 | decode_value_bin(?FLOAT8OID, <<127,240,0,0,0,0,0,0>>, _OIDMap, _DecodeOptions) -> 'Infinity'; 1059 | decode_value_bin(?FLOAT8OID, <<255,240,0,0,0,0,0,0>>, _OIDMap, _DecodeOptions) -> '-Infinity'; 1060 | decode_value_bin(?UUIDOID, Value, _OIDMap, _DecodeOptions) -> 1061 | <> = Value, 1062 | UUIDStr = io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", [UUID_A, UUID_B, UUID_C, UUID_D, UUID_E]), 1063 | list_to_binary(UUIDStr); 1064 | decode_value_bin(?DATEOID, <>, _OIDMap, _DecodeOptions) -> calendar:gregorian_days_to_date(Date + ?POSTGRESQL_GD_EPOCH); 1065 | decode_value_bin(?TIMEOID, TimeBin, _OIDMap, DecodeOptions) -> decode_time(TimeBin, proplists:get_bool(integer_datetimes, DecodeOptions), DecodeOptions); 1066 | decode_value_bin(?TIMETZOID, TimeTZBin, _OIDMap, DecodeOptions) -> decode_time_tz(TimeTZBin, proplists:get_bool(integer_datetimes, DecodeOptions), DecodeOptions); 1067 | decode_value_bin(?TIMESTAMPOID, TimestampBin, _OIDMap, DecodeOptions) -> decode_timestamp(TimestampBin, proplists:get_bool(integer_datetimes, DecodeOptions), DecodeOptions); 1068 | decode_value_bin(?TIMESTAMPTZOID, TimestampBin, _OIDMap, DecodeOptions) -> decode_timestamp(TimestampBin, proplists:get_bool(integer_datetimes, DecodeOptions), DecodeOptions); 1069 | decode_value_bin(?NUMERICOID, NumericBin, _OIDMap, _DecodeOptions) -> decode_numeric_bin(NumericBin); 1070 | decode_value_bin(?POINTOID, <>, _OIDMap, _DecodeOptions) -> {point, {X, Y}}; 1071 | decode_value_bin(?LSEGOID, <>, _OIDMap, _DecodeOptions) -> {lseg, {P1X, P1Y}, {P2X, P2Y}}; 1072 | decode_value_bin(?BOXOID, <>, _OIDMap, _DecodeOptions) -> {box, {P1X, P1Y}, {P2X, P2Y}}; 1073 | decode_value_bin(?PATHOID, <<1:8/unsigned-integer, PointsBin/binary>>, _OIDMap, _DecodeOptions) -> {path, closed, decode_points_bin(PointsBin)}; 1074 | decode_value_bin(?PATHOID, <<0:8/unsigned-integer, PointsBin/binary>>, _OIDMap, _DecodeOptions) -> {path, open, decode_points_bin(PointsBin)}; 1075 | decode_value_bin(?POLYGONOID, Points, _OIDMap, _DecodeOptions) -> {polygon, decode_points_bin(Points)}; 1076 | decode_value_bin(?VOIDOID, <<>>, _OIDMap, _DecodeOptions) -> null; 1077 | decode_value_bin(?CIDROID, <<2, Bits, 1, 4, X, Y, Z, T>>, _OIDMap, _DecodeOptions) -> {cidr, {X, Y, Z, T}, Bits}; % IPv4 (2 == inet) 1078 | decode_value_bin(?CIDROID, <<3, Bits, 1, 16, A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, _OIDMap, _DecodeOptions) -> {cidr, {A, B, C, D, E, F, G, H}, Bits}; % IPv6 (3 == inet6) 1079 | decode_value_bin(?INETOID, <<2, 32, 0, 4, X, Y, Z, T>>, _OIDMap, _DecodeOptions) -> {inet, {X, Y, Z, T}}; % IPv4 (2 == inet) 1080 | decode_value_bin(?INETOID, <<3, 128, 0, 16, A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, _OIDMap, _DecodeOptions) -> {inet, {A, B, C, D, E, F, G, H}}; % IPv6 (3 == inet6) 1081 | decode_value_bin(TypeOID, Value, OIDMap, DecodeOptions) -> 1082 | Type = decode_oid(TypeOID, OIDMap), 1083 | if not is_atom(Type) -> {Type, Value}; 1084 | true -> 1085 | case atom_to_list(Type) of 1086 | [$_ | _] -> % Array 1087 | decode_array_bin(Value, OIDMap, DecodeOptions); 1088 | _ -> {Type, Value} 1089 | end 1090 | end. 1091 | 1092 | decode_points_bin(<>) -> 1093 | decode_points_bin(N, Points, []). 1094 | 1095 | decode_points_bin(0, <<>>, Acc) -> 1096 | lists:reverse(Acc); 1097 | decode_points_bin(N, <>, Acc) when N > 0 -> 1098 | decode_points_bin(N - 1, Tail, [{PX, PY}|Acc]). 1099 | 1100 | decode_array_bin(<>, OIDMap, DecodeOptions) -> 1101 | {RemainingData, DimsInfo} = lists:foldl(fun(_Pos, {Bin, Acc}) -> 1102 | <> = Bin, 1103 | {Next, [{Nbr, LBound} | Acc]} 1104 | end, {Remaining, []}, lists:seq(1, Dimensions)), 1105 | DataList = decode_array_bin_aux(ElementOID, RemainingData, OIDMap, DecodeOptions, []), 1106 | Expanded = expand(DataList, DimsInfo), 1107 | Expanded. 1108 | 1109 | expand([], []) -> 1110 | {array, []}; 1111 | expand([List], []) -> 1112 | List; 1113 | expand(List, [{Nbr,_}|NextDim]) -> 1114 | List2 = expand_aux(List, Nbr, Nbr, [], []), 1115 | expand(List2, NextDim). 1116 | 1117 | expand_aux([], 0, _, Current, Acc) -> 1118 | lists:reverse([{array, lists:reverse(Current)} | Acc]); 1119 | expand_aux(List, 0, Nbr, Current, Acc) -> 1120 | expand_aux(List, Nbr, Nbr, [], [ {array, lists:reverse(Current)} | Acc]); 1121 | expand_aux([E|Next], Level, Nbr, Current, Acc) -> 1122 | expand_aux(Next, Level-1, Nbr, [E | Current], Acc). 1123 | 1124 | 1125 | decode_array_bin_aux(_ElementOID, <<>>, _OIDMap, _DecodeOptions, Acc) -> 1126 | lists:reverse(Acc); 1127 | decode_array_bin_aux(ElementOID, <<-1:32/signed-integer, Rest/binary>>, OIDMap, DecodeOptions, Acc) -> 1128 | decode_array_bin_aux(ElementOID, Rest, OIDMap, DecodeOptions, [null | Acc]); 1129 | decode_array_bin_aux(ElementOID, <>, OIDMap, DecodeOptions, Acc) -> 1130 | {ValueBin, Rest} = split_binary(Next, Size), 1131 | Value = decode_value_bin(ElementOID, ValueBin, OIDMap, DecodeOptions), 1132 | decode_array_bin_aux(ElementOID, Rest, OIDMap, DecodeOptions, [Value | Acc]). 1133 | 1134 | decode_numeric_bin(<<0:16/unsigned, _Weight:16, 16#C000:16/unsigned, 0:16/unsigned>>) -> 'NaN'; 1135 | decode_numeric_bin(<>) when Sign =:= 16#0000 orelse Sign =:= 16#4000 -> 1136 | Len = byte_size(Tail) div 2, 1137 | {ValueInt, DecShift} = decode_numeric_bin0(Tail, Weight, 0), 1138 | ValueDec = decode_numeric_bin_scale(ValueInt, DecShift), 1139 | SignedDec = case Sign of 1140 | 16#0000 -> ValueDec; 1141 | 16#4000 -> -ValueDec 1142 | end, 1143 | % Convert to float if there are digits after the decimal point. 1144 | if 1145 | DScale > 0 andalso is_integer(SignedDec) -> SignedDec * 1.0; 1146 | true -> SignedDec 1147 | end. 1148 | 1149 | -define(NBASE, 10000). 1150 | 1151 | decode_numeric_bin0(<<>>, Weight, Acc) -> {Acc, Weight}; 1152 | decode_numeric_bin0(<>, Weight, Acc) when Digit >= 0 andalso Digit < ?NBASE -> 1153 | NewAcc = (Acc * ?NBASE) + Digit, 1154 | decode_numeric_bin0(Tail, Weight - 1, NewAcc). 1155 | 1156 | decode_numeric_bin_scale(Value, -1) -> Value; 1157 | decode_numeric_bin_scale(Value, DecShift) when DecShift < 0 -> 1158 | NewValue = Value / ?NBASE, 1159 | decode_numeric_bin_scale(NewValue, DecShift + 1); 1160 | decode_numeric_bin_scale(Value, DecShift) when DecShift >= 0 -> 1161 | NewValue = Value * ?NBASE, 1162 | decode_numeric_bin_scale(NewValue, DecShift - 1). 1163 | 1164 | decode_oid(Oid, OIDMap) -> 1165 | case gb_trees:lookup(Oid, OIDMap) of 1166 | {value, OIDName} -> OIDName; 1167 | none -> Oid 1168 | end. 1169 | 1170 | decode_hex(<>) -> 1171 | [erlang:list_to_integer([X, Y], 16) | decode_hex(Tail)]; 1172 | decode_hex(<<>>) -> []. 1173 | 1174 | decode_time(<>, true, DecodeOptions) -> 1175 | Seconds = Time div 1000000, 1176 | USecs = Time rem 1000000, 1177 | decode_time0(Seconds, USecs, DecodeOptions); 1178 | decode_time(<>, false, DecodeOptions) -> 1179 | Seconds = trunc(Time), 1180 | USecs = round((Time - Seconds) * 1000000), % Maximum documented PostgreSQL precision is usec. 1181 | decode_time0(Seconds, USecs, DecodeOptions). 1182 | 1183 | decode_time0(Seconds, USecs, DecodeOptions) -> 1184 | {Hour, Min, Secs0} = calendar:seconds_to_time(Seconds), 1185 | Secs1 = cast_datetime_usecs(Secs0, USecs, DecodeOptions), 1186 | {Hour, Min, Secs1}. 1187 | 1188 | decode_time_tz(<>, IntegerDateTimes, DecodeOptions) -> 1189 | Decoded = decode_time(TimeBin, IntegerDateTimes, DecodeOptions), 1190 | adjust_time(Decoded, - (TZ div 60)). 1191 | 1192 | adjust_time(Time, 0) -> Time; 1193 | adjust_time({Hour, Min, Secs}, TZDelta) when TZDelta > 0 -> 1194 | {(24 + Hour - (TZDelta div 60)) rem 24, (60 + Min - (TZDelta rem 60)) rem 60, Secs}; 1195 | adjust_time({Hour, Min, Secs}, TZDelta) -> 1196 | {(Hour - (TZDelta div 60)) rem 24, (Min - (TZDelta rem 60)) rem 60, Secs}. 1197 | 1198 | decode_timestamp(<<16#7FFFFFFFFFFFFFFF:64/signed-integer>>, true, _DecodeOptions) -> infinity; 1199 | decode_timestamp(<<-16#8000000000000000:64/signed-integer>>, true, _DecodeOptions) -> '-infinity'; 1200 | decode_timestamp(<<127,240,0,0,0,0,0,0>>, false, _DecodeOptions) -> infinity; 1201 | decode_timestamp(<<255,240,0,0,0,0,0,0>>, false, _DecodeOptions) -> '-infinity'; 1202 | decode_timestamp(<>, true, DecodeOptions) -> 1203 | TimestampSecs = Timestamp div 1000000, 1204 | USecs = Timestamp rem 1000000, 1205 | decode_timestamp0(TimestampSecs, USecs, DecodeOptions); 1206 | decode_timestamp(<>, false, DecodeOptions) -> 1207 | TimestampSecs = trunc(Timestamp), 1208 | USecs = round((Timestamp - TimestampSecs) * 1000000), % Maximum documented PostgreSQL precision is usec. 1209 | decode_timestamp0(TimestampSecs, USecs, DecodeOptions). 1210 | 1211 | decode_timestamp0(Secs, USecs, DecodeOptions) -> 1212 | {Date, {Hour, Min, Secs0}} = calendar:gregorian_seconds_to_datetime(Secs + ?POSTGRESQL_GS_EPOCH), 1213 | Secs1 = cast_datetime_usecs(Secs0, USecs, DecodeOptions), 1214 | Time = {Hour, Min, Secs1}, 1215 | {Date, Time}. 1216 | -------------------------------------------------------------------------------- /src/pgsql_sup.erl: -------------------------------------------------------------------------------- 1 | %% @doc Supervisor for the pgsql application. 2 | -module(pgsql_sup). 3 | -vsn("1"). 4 | -behaviour(supervisor). 5 | 6 | %% Access from supervisor. 7 | -export([start_link/0]). 8 | 9 | %% supervisor API. 10 | -export([init/1]). 11 | 12 | -define(SHUTDOWN_DELAY, 5000). 13 | % no more than 5 restarts per second. 14 | -define(MAX_RESTARTS, 5). 15 | -define(MAX_RESTARTS_PERIOD, 1). 16 | 17 | %% @doc Start the supervisor. 18 | %% 19 | -spec start_link() -> {ok, pid()} | {error, any()}. 20 | start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | %% @doc Supervisor init callback. 23 | %% 24 | -spec init(any()) -> {ok, {{supervisor:strategy(), non_neg_integer(), pos_integer()}, [supervisor:child_spec()]}}. 25 | init(_Args) -> 26 | PostgreSQLChildSpec = {pgsql_connection_sup, % id 27 | {pgsql_connection_sup, start_link, []}, % init function 28 | transient, % restart children that crash 29 | ?SHUTDOWN_DELAY, supervisor, 30 | [pgsql_connection_sup] % module 31 | }, 32 | RestartStrategy = {one_for_one, ?MAX_RESTARTS, ?MAX_RESTARTS_PERIOD}, 33 | {ok, {RestartStrategy, [PostgreSQLChildSpec]}}. 34 | --------------------------------------------------------------------------------