Current section
Files
Jump to
Current section
Files
lib/schema/table.ex
defmodule AlchemyTable.Table do
alias AlchemyTable.BigQuery
defmacro __using__(_opt) do
quote do
alias AlchemyTable.Operations.Update
import unquote(__MODULE__)
import AlchemyTable.Table.Utils
Module.register_attribute(__MODULE__, :families, accumulate: true)
Module.register_attribute(__MODULE__, :promoted, accumulate: true)
Module.register_attribute(__MODULE__, :cloned, accumulate: true)
@after_compile unquote(__MODULE__)
end
end
def __after_compile__(env, _) do
should_gen =
Application.get_env(:alchemy_table, :bigquery, [])
|> Keyword.get(:gen_schemas, false)
if should_gen do
env.module
|> apply(:__alchemy_metadata__, [])
|> BigQuery.generate_schema()
end
end
defmacro table(name, opts, do: block) do
instance = Bigtable.Utils.configured_instance_name()
quote do
@key_parts unquote(opts) |> get_key_pattern!() |> build_key_parts()
unquote(block)
defstruct @families
def __alchemy_metadata__ do
%{
name: unquote(name),
instance: unquote(instance),
cloned: @cloned,
promoted: @promoted,
opts: unquote(opts),
schema: __alchemy_schema__()
}
end
def __alchemy_schema__ do
%__MODULE__{}
end
def build_updates(data, timestamp) do
%{cloned: cloned, promoted: promoted, instance: instance, schema: schema} =
__alchemy_metadata__()
cloned = cloned |> List.flatten()
main_key =
build_row_key(@key_parts, data)
|> add_ts(unquote(opts), timestamp)
main_update =
main_key
|> AlchemyTable.Operations.Update.update(schema, data)
cloned_updates =
for table <- List.flatten(cloned), into: [] do
meta = table.__alchemy_metadata__()
%{name: table_name, instance: instance, opts: opts} = meta
update = clone_update(main_key, main_update, data, opts, timestamp)
{instance, table_name, update}
end
promoted_updates =
for {column, module} <- promoted,
get_in(data, column) != nil,
into: [] do
apply(module, :build_updates, [data, timestamp])
end
[{instance, unquote(name), main_update}, cloned_updates, promoted_updates]
|> List.flatten()
end
def update(data, timestamp \\ DateTime.utc_now()) do
build_updates(data, timestamp)
|> Enum.map(&build_mutate_row/1)
end
end
end
defmacro clone(module) do
families =
Macro.expand(module, __CALLER__)
|> apply(:__alchemy_schema__, [])
|> Map.from_struct()
|> Map.to_list()
|> Macro.escape()
quote do
for family <- unquote(families) do
@families family
end
end
end
defmacro promoted(key, value) do
module = Macro.expand(value, __CALLER__)
base_type =
module
|> apply(:__alchemy_schema__, [])
|> Map.from_struct()
|> Macro.escape()
quote do
@promoted {[var!(name), unquote(key)], unquote(module)}
type = unquote(base_type) |> get_in([var!(name), unquote(key)])
var!(columns) = [{unquote(key), type} | var!(columns)]
end
end
@doc """
Defines a column family inside a `Bigtable.Schema.row/2` definition.
The name of the family should be provided to the macro as an atom.
The block of the macro should only contain `Bigtable.Schema.column/2` definitions.
"""
defmacro family(name, do: block) do
quote do
var!(name) = unquote(name)
var!(columns) = []
unquote(block)
@families {unquote(name), Map.new(var!(columns))}
end
end
@doc """
Defines a column inside a `Bigtable.Schema.family/2` definition.
The first argument is an atom that will define the column's name.
The second argument defines the column's type and should be one of:
- `:integer`
- `:float`
- `:boolean`
- `:string`
- `:map`
- `:list`
If the column value is defined as either `:map` or `:list`, the value will be JSON encoded during mutations and decoded during reads.
"""
defmacro column(key, {:__aliases__, _, _} = value) do
type =
Macro.expand(value, __CALLER__)
|> apply(:type, [])
|> Macro.escape()
c = {key, type}
quote do
var!(columns) = [unquote(c) | var!(columns)]
end
end
defmacro column(key, value) do
c = {key, value}
quote do
var!(columns) = [unquote(c) | var!(columns)]
end
end
end