PowerOfThree (PowerOfThree v0.1.1)

View Source

The 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
end

After 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 typesEcto typeElixir type
number:idinteger
string:binary_idbinary
number, boolean:integerinteger
number, boolean enough?:floatfloat
boolean:booleanboolean
stringUTF-8 encoded stringstring
string:binary:binary
string:bitstring:bitstring
{:array, inner_type}listTODO geo?
Not Supported now:mapmap
Not Supported now{:map, inner_type}map
number:decimalDecimal
time:dateDate
time:timeTime
time:time_usecTime
time:naive_datetimeNaiveDateTime
time:naive_datetime_usecNaiveDateTime
time:utc_datetimeDateTime
time:utc_datetime_usecDateTime
number:durationDuration

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

Measure takes 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

cube(cube_name, opts, list)

(macro)

dimension(ecto_schema_field_or_list_of_fields, opts \\ [])

(macro)

measure(atom_count_ecto_field_or_list, opts \\ [])

(macro)

Measure takes atom :count, a single Ecto.Schema field or a list of Ecto.Schema fields.

Measure for several Ecto.Schema fields A list of Ecto.Schema fields reference and :type are mandatory 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" )

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" )

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" )

time_dimensions(cube_date_time_fields \\ [])

(macro)

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