PG::TextEncoder::

Record class

This class encodes one record of columns for transmission as query parameter in text format. See PostgreSQL Composite Types for a description of the format and how it can be used.

PostgreSQL allows composite types to be used in many of the same ways that simple types can be used. For example, a column of a table can be declared to be of a composite type.

The encoder expects the record columns as array of values. The single values are encoded as defined in the assigned type_map. If no type_map was assigned, all values are converted to strings by PG::TextEncoder::String.

It is possible to manually assign a type encoder for each column per PG::TypeMapByColumn, or to make use of PG::BasicTypeMapBasedOnResult to assign them based on the table OIDs.

Encode a record from an Array<String> to a String in PostgreSQL Composite Type format (uses default type map TypeMapAllStrings):

PG::TextEncoder::Record.new.encode([1, 2])  # => "(\"1\",\"2\")"

Encode a record from Array<Float> to String :

# Build a type map for two Floats
tm = PG::TypeMapByColumn.new([PG::TextEncoder::Float.new]*2)
# Use this type map to encode the record:
PG::TextEncoder::Record.new(type_map: tm).encode([1,2])
# => "(\"1.0\",\"2.0\")"

Records can also be encoded and decoded directly to and from the database. This avoids intermediate string allocations and is very fast. Take the following type and table definitions:

conn.exec("CREATE TYPE complex AS (r float, i float) ")
conn.exec("CREATE TABLE my_table (v1 complex, v2 complex) ")

A record can be encoded by adding a type map to Connection#exec_params and siblings:

# Build a type map for the two floats "r" and "i" as in our "complex" type
tm = PG::TypeMapByColumn.new([PG::TextEncoder::Float.new]*2)
# Build a record encoder to encode this type as a record:
enco = PG::TextEncoder::Record.new(type_map: tm)
# Insert table data and use the encoder to cast the complex value "v1" from ruby array:
conn.exec_params("INSERT INTO my_table VALUES ($1) RETURNING v1", [[1,2]], 0, PG::TypeMapByColumn.new([enco])).to_a
# => [{"v1"=>"(1,2)"}]

Alternatively the typemap can be build based on database OIDs rather than manually assigning encoders.

# Fetch a NULL record of our type to retrieve the OIDs of the two fields "r" and "i"
oids = conn.exec( "SELECT (NULL::complex).*" )
# Build a type map (PG::TypeMapByColumn) for encoding the "complex" type
etm = PG::BasicTypeMapBasedOnResult.new(conn).build_column_map( oids )

It’s also possible to use the BasicTypeMapForQueries to send records to the database server. In contrast to ORM libraries, PG doesn’t have information regarding the type of data the server is expecting. So BasicTypeMapForQueries works based on the class of the values to be sent and it has to be instructed that a ruby array shall be casted to a record.

# Retrieve OIDs of all basic types from the database
etm = PG::BasicTypeMapForQueries.new(conn)
etm.encode_array_as = :record
# Apply the basic type registry to all values sent to the server
conn.type_map_for_queries = etm
# Send a complex number as an array of two integers
conn.exec_params("INSERT INTO my_table VALUES ($1) RETURNING v1", [[1,2]]).to_a
# => [{"v1"=>"(1,2)"}]

Records can also be nested or further wrapped into other encoders like PG::TextEncoder::CopyRow.

See also PG::TextDecoder::Record for the decoding direction.