PowerOfThree (PowerOfThree v0.1.2)
View SourceThe PowerOfThree defines three macros to be used with Ecto.Schema to creates cube config files.
The PowerOfThree must be used after using Ecto.Schema.
The Ecto.Schema defines table column names to be used in measure and dimensions defenitions.
The definition of the Cude is possible through main APIs:
cube/3.
cube/3 has to define sql_table: that is refering Ecto schema source.
After using Ecto.Schema and PowerOfThree define cube with cube/2 macro.
Example
defmodule Example.Customer do
use Ecto.Schema
use PowerOfThree
schema "customer" do
field(:first_name, :string)
field(:last_name, :string)
field(:email, :string)
field(:birthday_day, :integer)
field(:birthday_month, :integer)
field(:brand_code, :string)
field(:market_code, :string)
end
cube :of_customers, # name of the cube: mandatory
sql_table: "customer", # Ecto.Schema `source`: mandatory
# Only `sql_table:` is supported. Must reference EctoSchema `:source`
# the `sql:` is not supported and never will be.
description: "of Customers"
# path through options in accordance with Cube DSL
dimension(
[:brand_code, :market_code, :email],
# several fields of `customer` Ecto.Schema: mandatory
# the list is validated against list of fields of EctoSchema
name: :email_per_brand_per_market,
# dimensions `name:`, optional.
primary_key: true # This `customer:` table supports only one unique combination of
# `:brand_code`, `:market_code`, `:email`
)
dimension(
:first_name, # a field of `customer` Ecto.Schema: mandatory
# validated against list of fields of EctoSchema
name: :given_name, # dimension `name:` optional
description: "Given Name"
# path through options in accordance with Dimension DSL
)
measure(:count) # measure of type `count:` is a special one: no column reference in `sql:` is needed
# `name:` defaults to `count:`
measure(:email, # measures counts distinct of `email:` column
name: :aquari # given a `name:` and `description:` and `filter:` in options
type: :count_distinct,
description: "Only count one zodiak sign",
filters: [%{sql: "(birthday_month = 1 AND birthday_day >= 20) OR (birthday_month = 2 AND birthday_day <= 18)"}]
# better be correct SQL refrencing correct columns - not validated now
# `filter:` uses SQL clause to not count other categories of customers
)
end
endAfter creating a few dimensions and measures run mix compile. The following yaml is created for the above:
---
cubes:
- name: of_customers
description: of Customers
sql_table: customer
measures:
- name: count
type: count
- meta:
ecto_field: email
ecto_type: string
name: aquari
type: count_distinct
description: Only count one zodiak sign
filters:
- sql: (birthday_month = 1 AND birthday_day >= 20) OR (birthday_month = 2 AND birthday_day <= 18)
sql: email
dimensions:
- meta:
ecto_fields:
- brand_code
- market_code
- email
name: email_per_brand_per_market
type: string
primary_key: true
sql: brand_code||market_code||email
- meta:
ecto_field: first_name
ecto_field_type: string
name: given_name
type: string
description: Given Name
sql: first_name
The dimensions and measures derive some defaults from Ecto.Schema.field properties.
For example the dimension: type: is derived from ecto if not given explicitly according to this rules:
| Cube dimension types | Ecto type | Elixir type |
|---|---|---|
| number | :id | integer |
| string | :binary_id | binary |
| number, boolean | :integer | integer |
| number, boolean enough? | :float | float |
| boolean | :boolean | boolean |
| string | UTF-8 encoded string | string |
| string | :binary | :binary |
| string | :bitstring | :bitstring |
{:array, inner_type} | list | TODO geo? |
| Not Supported now | :map | map |
| Not Supported now | {:map, inner_type} | map |
| number | :decimal | Decimal |
| time | :date | Date |
| time | :time | Time |
| time | :time_usec | Time |
| time | :naive_datetime | NaiveDateTime |
| time | :naive_datetime_usec | NaiveDateTime |
| time | :utc_datetime | DateTime |
| time | :utc_datetime_usec | DateTime |
| number | :duration | Duration |
The goal of PowerOfThree is to cover 80% of cases where the source of Ecto Schema is a table and fields have real column names:
where field name =:= database column name
The the support of all cube features is not the goal here. The automation of obtaining the usable cube configs with minimal verbocity is: avoid typing more typos then needed.
The cube DSL allows the sql: - any SQL query. If everyone can write SQL it does not mean everyone should.
Writing good SQL is an art a few knew. In the memory of Patrick's Mother the PowerOfThree will not support sql:.
While defining custom sql: may looks like an option, how would one validate the creative use of aliases in SQL?
Meanwhile Ecto.Schema fields are available for references to define dimensions type:.
Summary
Functions
Dimension first argument takes a single Ecto.Schema field or a list of Ecto.Schema fields.
Measure first argument takes an atom :count, a single Ecto.Schema field or a list of Ecto.Schema fields.
Uses :inserted_at as default time dimension
defmacro cube(CALLER,cube_name, what_ecto_schema, block)
Functions
Dimension first argument takes a single Ecto.Schema field or a list of Ecto.Schema fields.
Lets create a Dimension for several Ecto.Schema fields. A list of Ecto.Schema fields is mandatory.
Ecto.Schema fields concatenated into SQL: brand_code||market_code||email
The primary_key: true tells the cube how to distinguish unique records.
Examples
dimension(
[:brand_code, :market_code, :email],
name: :email_per_brand_per_market,
primary_key: true
)Lets create a Dimension for a single Ecto.Schema field
Examples
dimension(:brand_code, name: :brand, description: "Beer")
Measure first argument takes an atom :count, a single Ecto.Schema field or a list of Ecto.Schema fields.
Lets create a Measure for several Ecto.Schema fields. A list of Ecto.Schema fields reference and :type are mandatory.
The sql: is mandatory, must be valid SQL clause using the fields from list and returning a number.
Examples
measure([:tax_amount,:discount_total_amount],
sql: "tax_amount + discount_total_amount",
type: :sum,
description: "two measures we want add together"
)Lets create a Measure for a single Ecto.Schema field
The Ecto.Schema field reference and :type are mandatory
The other cube measure DLS properties are passed through
Examples
measure(:email,
name: :emails_distinct,
type: :count_distinct,
description: "count distinct of emails"
)Lets create a Measure of type :count
No :type is needed
The other cube measure DLS properties are passed through
Examples
measure(:count,
description: "no need for fields for :count type measure"
)
Uses :inserted_at as default time dimension
defmacro cube(CALLER,cube_name, what_ecto_schema, block)
defp cube(caller,cube_name,what_ecto_schema, block) do