Browse Source

Merge pull request #205 from seriyps/add-table-info-to-column

Add table metadata to #column{} record
Sergey Prokhorov 5 years ago
parent
commit
04938dd3f9
4 changed files with 36 additions and 23 deletions
  1. 8 15
      README.md
  2. 21 3
      include/epgsql.hrl
  3. 4 2
      src/epgsql_wire.erl
  4. 3 3
      test/epgsql_SUITE.erl

+ 8 - 15
README.md

@@ -126,21 +126,15 @@ Asynchronous connect example (applies to **epgsqli** too):
 ### Simple Query
 
 ```erlang
--type query() :: string() | iodata().
--type squery_row() :: {binary()}.
+-include_lib("epgsql/include/epgsql.hrl").
 
--record(column, {
-    name :: binary(),
-    type :: epgsql_type(),
-    size :: -1 | pos_integer(),
-    modifier :: -1 | pos_integer(),
-    format :: integer()
-}).
+-type query() :: string() | iodata().
+-type squery_row() :: tuple() % tuple of binary().
 
 -type ok_reply(RowType) ::
-    {ok, ColumnsDescription :: [#column{}], RowsValues :: [RowType]} |                            % select
+    {ok, ColumnsDescription :: [epgsql:column()], RowsValues :: [RowType]} |                            % select
     {ok, Count :: non_neg_integer()} |                                                            % update/insert/delete
-    {ok, Count :: non_neg_integer(), ColumnsDescription :: [#column{}], RowsValues :: [RowType]}. % update/insert/delete + returning
+    {ok, Count :: non_neg_integer(), ColumnsDescription :: [epgsql:column()], RowsValues :: [RowType]}. % update/insert/delete + returning
 -type error_reply() :: {error, query_error()}.
 -type reply(RowType) :: ok_reply() | error_reply().
 
@@ -159,7 +153,7 @@ epgsql:squery(C, "insert into account (name) values  ('alice'), ('bob')").
 ```erlang
 epgsql:squery(C, "select * from account").
 > {ok,
-    [{column,<<"id">>,int4,4,-1,0},{column,<<"name">>,text,-1,-1,0}],
+    [#column{name = <<"id">>, type = int4, …},#column{name = <<"name">>, type = text, …}],
     [{<<"1">>,<<"alice">>},{<<"2">>,<<"bob">>}]
 }
 ```
@@ -170,13 +164,12 @@ epgsql:squery(C,
     "    values ('joe'), (null)"
     "    returning *").
 > {ok,2,
-    [{column,<<"id">>,int4,4,-1,0}, {column,<<"name">>,text,-1,-1,0}],
+    [#column{name = <<"id">>, type = int4, …}, #column{name = <<"name">>, type = text, …}],
     [{<<"3">>,<<"joe">>},{<<"4">>,null}]
 }
 ```
 
 ```erlang
--include_lib("epgsql/include/epgsql.hrl").
 epgsql:squery(C, "SELECT * FROM _nowhere_").
 > {error,
    #error{severity = error,code = <<"42P01">>,
@@ -253,7 +246,7 @@ an error occurs, all statements result in `{error, #error{}}`.
 ```erlang
 epgsql:equery(C, "select id from account where name = $1", ["alice"]),
 > {ok,
-    [{column,<<"id">>,int4,4,-1,1}],
+    [#column{name = <<"id">>, type = int4, …}],
     [{1}]
 }
 ```

+ 21 - 3
include/epgsql.hrl

@@ -1,10 +1,26 @@
+%% See https://www.postgresql.org/docs/current/protocol-message-formats.html
+%% Description of `RowDescription' packet
 -record(column, {
+    %% field name
     name :: binary(),
+    %% name of the field data type
     type :: epgsql:epgsql_type(),
-    oid :: integer(),
+    %% OID of the field's data type
+    oid :: non_neg_integer(),
+    %% data type size (see pg_type.typlen). negative values denote variable-width types
     size :: -1 | pos_integer(),
+    %% type modifier (see pg_attribute.atttypmod). meaning of the modifier is type-specific
     modifier :: -1 | pos_integer(),
-    format :: integer()
+    %% format code being used for the field during server->client transmission.
+    %% Currently will be zero (text) or one (binary).
+    format :: integer(),
+    %% If the field can be identified as a column of a specific table, the OID of the table; otherwise zero.
+    %% SELECT relname FROM pg_catalog.pg_class WHERE oid=<table_oid>
+    table_oid :: non_neg_integer(),
+    %% If table_oid is not zero, the attribute number of the column; otherwise zero.
+    %% SELECT attname FROM pg_catalog.pg_attribute
+    %% WHERE attrelid=<table_oid> AND attnum=<table_attr_number>
+    table_attr_number :: pos_integer()
 }).
 
 -record(statement, {
@@ -14,10 +30,12 @@
     parameter_info :: [epgsql_oid_db:oid_entry()]
 }).
 
+
+%% See https://www.postgresql.org/docs/current/protocol-error-fields.html
 -record(error, {
     % see client_min_messages config option
     severity :: debug | log | info | notice | warning | error | fatal | panic,
-    code :: binary(),
+    code :: binary(), % See https://www.postgresql.org/docs/current/errcodes-appendix.html
     codename :: atom(),
     message :: binary(),
     extra :: [{severity | detail | hint | position | internal_position | internal_query

+ 4 - 2
src/epgsql_wire.erl

@@ -153,7 +153,7 @@ decode_data(<<Len:?int32, Value:Len/binary, Rest/binary>>, [Decoder | Decs], Cod
 decode_columns(0, _Bin, _Codec) -> [];
 decode_columns(Count, Bin, Codec) ->
     [Name, Rest] = decode_string(Bin),
-    <<_TableOid:?int32, _AttribNum:?int16, TypeOid:?int32,
+    <<TableOid:?int32, AttribNum:?int16, TypeOid:?int32,
       Size:?int16, Modifier:?int32, Format:?int16, Rest2/binary>> = Rest,
     %% TODO: get rid of this 'type' (extra oid_db lookup)
     Type = epgsql_binary:oid_to_name(TypeOid, Codec),
@@ -163,7 +163,9 @@ decode_columns(Count, Bin, Codec) ->
       oid      = TypeOid,
       size     = Size,
       modifier = Modifier,
-      format   = Format},
+      format   = Format,
+      table_oid = TableOid,
+      table_attr_number = AttribNum},
     [Desc | decode_columns(Count - 1, Rest2, Codec)].
 
 %% @doc decode ParameterDescription

+ 3 - 3
test/epgsql_SUITE.erl

@@ -514,9 +514,9 @@ parse_column_format(Config) ->
     Module = ?config(module, Config),
     epgsql_ct:with_connection(Config, fun(C) ->
         {ok, S} = Module:parse(C, "select 1::int4, false::bool, 2.0::float4"),
-        [#column{type = int4},
-         #column{type = bool},
-         #column{type = float4}] = S#statement.columns,
+        [#column{type = int4, table_oid = 0, table_attr_number = 0},
+         #column{type = bool, table_oid = 0, table_attr_number = 0},
+         #column{type = float4, table_oid = 0, table_attr_number = 0}] = S#statement.columns,
         ok = Module:bind(C, S, []),
         {ok, [{1, false, 2.0}]} = Module:execute(C, S, 0),
         ok = Module:close(C, S),