Packages
ecto_tablestore
0.5.4
0.15.1
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
Alibaba Tablestore adapter for Ecto
Current section
Files
Jump to
Current section
Files
lib/ecto_tablestore/schema.ex
defmodule EctoTablestore.Schema do
@moduledoc ~S"""
Defines a schema for Tablestore.
Since the atomic increment may need to return the increased value, `EctoTablestore.Schema` module underlying uses `Ecto.Schema`,
and automatically append all `:integer` field type with `read_after_writes: true` option by default.
An Ecto schema is used to map any data source into an Elixir struct. The definition of the schema is
possible through the API: `tablestore_schema/2`.
`tablestore_schema/2` is typically used to map data from a persisted source, usually a Tablestore table,
into Elixir structs and vice-versa. For this reason, the first argument of `tablestore_schema/2` is the
source(table) name. Structs defined with `tablestore_schema/2` also contain a `__meta__` field with metadata holding
the status of struct, for example, if it has bee built, loaded or deleted.
Since Tablestore is a NoSQL database service, `embedded_schema/1` is not supported so far.
## About timestamps
Since Tablestore's column does not support `DateTime` type, use UTC timestamp (:integer type)
as `timestamps()` macro for the generated `inserted_at` and `updated_at` fields by default.
## About primary key
* The primary key supports `:id` (integer()) and `:binary_id` (binary()).
* By default the `:primary_key` option is `false`.
* The first defined primary key by the written order in the `tablestore_schema` is the partition key.
* Up to 4 primary key(s), it is limited by TableStore product server side.
* Up to 1 primary key with `autogenerate: true` option, it is limited by TableStore product server side.
* The primary key set with `autogenerate: true` will use the TableStore product server's AUTO_INCREMENT feature.
* If the partition key set as `autogenerate: true` is not allowed to take advantage of the AUTO_INCREMENT feature which it
is limited by server, but there is a built-in implement to use the `Sequence` to achieve the same atomic increment operation
in `ecto_tablestore` library.
## Example
```elixir
defmodule User do
use EctoTablestore.Schema
tablestore_schema "users" do
field :outer_id, :binary_id, primary_key: true
field :internal_id, :id, primary_key: true, autogenerate: true
field :name, :string
field :desc
end
end
```
By default, if not explicitly set field type will process it as `:string` type.
"""
defmacro __using__(_) do
quote do
use Ecto.Schema
import EctoTablestore.Schema, only: [tablestore_schema: 2]
@primary_key false
@timestamps_opts [
type: :integer,
autogenerate: {EctoTablestore.Schema, :__timestamps__, []}
]
end
end
def __timestamps__() do
DateTime.utc_now() |> DateTime.to_unix()
end
defmacro tablestore_schema(source, do: block) do
{block, hashids} = check_block(block)
quote do
Ecto.Schema.schema(unquote(source), do: unquote(block))
unquote(generate_hashids_config(hashids))
end
end
defp generate_hashids_config(hashids) do
for {key, hashids_item} <- hashids do
quote location: :keep do
def hashids(unquote(key)) do
unquote(hashids_item)
end
end
end
end
defp check_block({:__block__, info, fields}) do
{fields, hashids} = supplement_fields(fields, [], [])
{
{:__block__, info, fields},
Macro.escape(hashids)
}
end
defp check_block(block) do
block
end
defp supplement_fields([], prepared, hashids) do
{Enum.reverse(prepared), hashids}
end
defp supplement_fields(
[{defined_macro, field_line, [field_name, :integer]} | rest_fields],
prepared,
hashids
) do
update = {
defined_macro,
field_line,
[
field_name,
{:__aliases__, field_line, [:EctoTablestore, :Integer]},
[read_after_writes: true]
]
}
supplement_fields(rest_fields, [update | prepared], hashids)
end
defp supplement_fields(
[{defined_macro, field_line, [field_name, :integer, opts]} = field | rest_fields],
prepared,
hashids
) do
field =
if Keyword.get(opts, :primary_key, false) do
field
else
{
defined_macro,
field_line,
[
field_name,
{:__aliases__, field_line, [:EctoTablestore, :Integer]},
Keyword.put(opts, :read_after_writes, true)
]
}
end
supplement_fields(rest_fields, [field | prepared], hashids)
end
defp supplement_fields(
[
{defined_macro, field_line,
[field_name, {:__aliases__, line, [:EctoTablestore, :Integer]}]}
| rest_fields
],
prepared,
hashids
) do
update = {
defined_macro,
field_line,
[field_name, {:__aliases__, line, [:EctoTablestore, :Integer]}, [read_after_writes: true]]
}
supplement_fields(rest_fields, [update | prepared], hashids)
end
defp supplement_fields(
[
{defined_macro, field_line,
[field_name, {:__aliases__, line, [:EctoTablestore, :Integer]}, opts]} = field
| rest_fields
],
prepared,
hashids
) do
field =
if Keyword.get(opts, :primary_key, false) do
field
else
{
defined_macro,
field_line,
[
field_name,
{:__aliases__, line, [:EctoTablestore, :Integer]},
Keyword.put(opts, :read_after_writes, true)
]
}
end
supplement_fields(rest_fields, [field | prepared], hashids)
end
defp supplement_fields(
[
{defined_macro, field_line,
[field_name, :hashids, opts]} = field
| rest_fields
],
prepared,
prepared_hashids
) do
if Keyword.get(opts, :primary_key, false) and opts[:hashids] != nil do
{field, new_hashids} = supplement_hashids_field(defined_macro, field_line, field_name, opts)
supplement_fields(rest_fields, [field | prepared], [new_hashids | prepared_hashids])
else
supplement_fields(rest_fields, [field | prepared], prepared_hashids)
end
end
defp supplement_fields(
[
{defined_macro, field_line,
[field_name, {:__aliases__, _line, type}, opts]} = field
| rest_fields
],
prepared,
prepared_hashids
)
when type == [:EctoTablestore, :Hashids]
when type == [:Hashids] do
if Keyword.get(opts, :primary_key, false) and opts[:hashids] != nil do
{field, new_hashids} = supplement_hashids_field(defined_macro, field_line, field_name, opts)
supplement_fields(rest_fields, [field | prepared], [new_hashids | prepared_hashids])
else
supplement_fields(rest_fields, [field | prepared], prepared_hashids)
end
end
defp supplement_fields([{defined_macro, field_line, field_info} | rest_fields], prepared, hashids) do
supplement_fields(rest_fields, [{defined_macro, field_line, field_info} | prepared], hashids)
end
defp supplement_hashids_field(defined_macro, field_line, field_name, opts) do
hashids =
opts
|> Keyword.get(:hashids)
|> Keyword.take([:salt, :min_len, :alphabet])
|> Hashids.new()
field =
{
defined_macro,
field_line,
[
field_name,
EctoTablestore.Hashids,
opts
]
}
new_hashids = {field_name, hashids}
{field, new_hashids}
end
end