139 lines
4.3 KiB
Elixir
139 lines
4.3 KiB
Elixir
defmodule Postgrex.Extension do
|
|
@moduledoc """
|
|
An extension knows how to encode and decode PostgreSQL types to and
|
|
from Elixir values.
|
|
|
|
Custom extensions can be enabled via `Postgrex.Types.define/3`.
|
|
`Postgrex.Types.define/3` must be called on its own file, outside of
|
|
any module and function, as it only needs to be defined once during
|
|
compilation.
|
|
|
|
For example to support label trees using the text encoding format:
|
|
|
|
defmodule MyApp.LTree do
|
|
@behaviour Postgrex.Extension
|
|
|
|
# It can be memory efficient to copy the decoded binary because a
|
|
# reference counted binary that points to a larger binary will be passed
|
|
# to the decode/4 callback. Copying the binary can allow the larger
|
|
# binary to be garbage collected sooner if the copy is going to be kept
|
|
# for a longer period of time. See `:binary.copy/1` for more
|
|
# information.
|
|
def init(opts) do
|
|
Keyword.get(opts, :decode_copy, :copy)
|
|
end
|
|
|
|
# Use this extension when `type` from %Postgrex.TypeInfo{} is "ltree"
|
|
def matching(_state), do: [type: "ltree"]
|
|
|
|
# Use the text format, "ltree" does not have a binary format.
|
|
def format(_state), do: :text
|
|
|
|
# Use quoted expression to encode a string that is the same as
|
|
# postgresql's ltree text format. The quoted expression should contain
|
|
# clauses that match those of a `case` or `fn`. Encoding matches on the
|
|
# value and returns encoded `iodata()`. The first 4 bytes in the
|
|
# `iodata()` must be the byte size of the rest of the encoded data, as a
|
|
# signed 32bit big endian integer.
|
|
def encode(_state) do
|
|
quote do
|
|
bin when is_binary(bin) ->
|
|
[<<byte_size(bin)::signed-size(32)>> | bin]
|
|
end
|
|
end
|
|
|
|
# Use quoted expression to decode the data to a string. Decoding matches
|
|
# on an encoded binary with the same signed 32bit big endian integer
|
|
# length header.
|
|
def decode(:reference) do
|
|
quote do
|
|
<<len::signed-size(32), bin::binary-size(len)>> ->
|
|
bin
|
|
end
|
|
end
|
|
def decode(:copy) do
|
|
quote do
|
|
<<len::signed-size(32), bin::binary-size(len)>> ->
|
|
:binary.copy(bin)
|
|
end
|
|
end
|
|
end
|
|
|
|
This example could be used in a custom types module:
|
|
|
|
Postgrex.Types.define(MyApp.Types, [MyApp.LTree])
|
|
|
|
Or pass in opts for the extension that will be passed to the `init/1` callback:
|
|
|
|
Postgrex.Types.define(MyApp.Types, [{MyApp.LTree, [decode_copy: :copy]}])
|
|
|
|
"""
|
|
|
|
@type t :: module
|
|
@type state :: term
|
|
|
|
@doc """
|
|
Should perform any initialization of the extension. The function receives the
|
|
user options. The state returned from this function will be passed to other
|
|
callbacks.
|
|
"""
|
|
@callback init(Keyword.t()) :: state
|
|
|
|
@doc """
|
|
Prelude defines properties and values that are attached to the body of
|
|
the types module.
|
|
"""
|
|
@callback prelude(state) :: Macro.t()
|
|
|
|
@doc """
|
|
Specifies the types the extension matches, see `Postgrex.TypeInfo` for
|
|
specification of the fields.
|
|
"""
|
|
@callback matching(state) :: [
|
|
type: String.t(),
|
|
send: String.t(),
|
|
receive: String.t(),
|
|
input: String.t(),
|
|
output: String.t()
|
|
]
|
|
|
|
@doc """
|
|
Returns the format the type should be encoded as. See
|
|
http://www.postgresql.org/docs/9.4/static/protocol-overview.html#PROTOCOL-FORMAT-CODES.
|
|
"""
|
|
@callback format(state) :: :binary | :text
|
|
|
|
@doc """
|
|
Returns a quoted list of clauses that encode an Elixir value to iodata.
|
|
|
|
It must use a signed 32 bit big endian integer byte length header.
|
|
|
|
def encode(_) do
|
|
quote do
|
|
integer ->
|
|
<<8::signed-32, integer::signed-64>>
|
|
end
|
|
end
|
|
|
|
"""
|
|
@callback encode(state) :: Macro.t()
|
|
|
|
@doc """
|
|
Returns a quoted list of clauses that decode a binary to an Elixir value.
|
|
|
|
The pattern must use binary syntax and decode a fixed length using the signed
|
|
32 bit big endian integer byte length header.
|
|
|
|
def decode(_) do
|
|
quote do
|
|
# length header is in bytes
|
|
<<len::signed-32, integer::signed-size(len)-unit(8)>> ->
|
|
integer
|
|
end
|
|
end
|
|
"""
|
|
@callback decode(state) :: Macro.t()
|
|
|
|
@optional_callbacks [prelude: 1]
|
|
end
|