Skip to content

Commit

Permalink
Merge pull request #43 from mistercrunch/expr
Browse files Browse the repository at this point in the history
Supporting arbitrary expressions
  • Loading branch information
mistercrunch committed Oct 7, 2015
2 parents 36c7406 + 6db1b97 commit cf53cfc
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 deletions.
26 changes: 26 additions & 0 deletions panoramix/migrations/versions/1e2841a4128_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""empty message
Revision ID: 1e2841a4128
Revises: 5a7bad26f2a7
Create Date: 2015-10-05 22:11:00.537054
"""

# revision identifiers, used by Alembic.
revision = '1e2841a4128'
down_revision = '5a7bad26f2a7'

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('table_columns', sa.Column('expression', sa.Text(), nullable=True))
### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('table_columns', 'expression')
### end Alembic commands ###
41 changes: 33 additions & 8 deletions panoramix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from sqlalchemy import Table
from sqlalchemy import create_engine, MetaData, desc, select, and_
from sqlalchemy.orm import relationship
from sqlalchemy.sql import table, literal_column, text
from sqlalchemy.sql import table, literal_column, text, column
from sqlalchemy.sql.elements import ColumnClause
from flask import request

from copy import deepcopy, copy
Expand Down Expand Up @@ -338,6 +339,7 @@ def query(
inner_from_dttm=None, inner_to_dttm=None,
extras=None):

cols = {col.column_name: col for col in self.columns}
qry_start_dttm = datetime.now()
if not self.main_dttm_col:
raise Exception(
Expand All @@ -360,9 +362,25 @@ def query(

if groupby:
select_exprs = [literal_column(s) for s in groupby]
groupby_exprs = [literal_column(s) for s in groupby]
inner_groupby_exprs = [
literal_column(s).label('__' + s) for s in groupby]
select_exprs = []
groupby_exprs = []
inner_select_exprs = []
inner_groupby_exprs = []
for s in groupby:
col = cols[s]
expr = col.expression
if expr:
outer = ColumnClause(expr, is_literal=True).label(s)
inner = ColumnClause(expr, is_literal=True).label('__' + s)
else:
outer = literal_column(s).label(s)
inner = literal_column(s).label('__' + s)

groupby_exprs.append(outer)
select_exprs.append(outer)
inner_groupby_exprs.append(inner)
inner_select_exprs.append(inner)

if granularity != "all":
select_exprs += [timestamp]
groupby_exprs += [timestamp]
Expand All @@ -384,9 +402,14 @@ def query(

where_clause_and = []
for col, op, eq in filter:
col_obj = cols[col]
if op in ('in', 'not in'):
values = eq.split(",")
cond = literal_column(col).in_(values)
if col_obj.expression:
cond = ColumnClause(
col_obj.expression, is_literal=True).in_(values)
else:
cond = literal_column(col).in_(values)
if op == 'not in':
cond = ~cond
where_clause_and.append(cond)
Expand All @@ -397,23 +420,24 @@ def query(
qry = qry.limit(row_limit)

if timeseries_limit and groupby:
subq = select(inner_groupby_exprs)
subq = select(inner_select_exprs)
subq = subq.select_from(table(self.table_name))
subq = subq.where(and_(*(where_clause_and + inner_time_filter)))
subq = subq.group_by(*inner_groupby_exprs)
subq = subq.order_by(desc(main_metric_expr))
subq = subq.limit(timeseries_limit)
on_clause = []
for gb in groupby:
for i, gb in enumerate(groupby):
on_clause.append(
literal_column(gb) == literal_column("__" + gb))
groupby_exprs[i] == literal_column("__" + gb))

from_clause = from_clause.join(subq.alias(), and_(*on_clause))

qry = qry.select_from(from_clause)

engine = self.database.get_sqla_engine()
sql = str(qry.compile(engine, compile_kwargs={"literal_binds": True}))
print sql
df = read_sql_query(
sql=sql,
con=engine
Expand Down Expand Up @@ -548,6 +572,7 @@ class TableColumn(Model, AuditMixinNullable):
max = Column(Boolean, default=False)
min = Column(Boolean, default=False)
filterable = Column(Boolean, default=False)
expression = Column(Text, default='')
description = Column(Text, default='')

def __repr__(self):
Expand Down
2 changes: 2 additions & 0 deletions panoramix/static/widgets/viz_nvd3.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ function viz_nvd3(token_name, json_callback) {
} else if (viz_type === 'dist_bar') {
var chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.reduceXTicks(false)
.rotateLabels(45)
.groupSpacing(0.1); //Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
Expand Down
3 changes: 2 additions & 1 deletion panoramix/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class TableColumnInlineView(CompactCRUDMixin, PanoramixModelView):
can_delete = False
edit_columns = [
'column_name', 'description', 'groupby', 'filterable', 'table',
'count_distinct', 'sum', 'min', 'max']
'count_distinct', 'sum', 'min', 'max', 'expression']
add_columns = edit_columns
list_columns = [
'column_name', 'type', 'groupby', 'filterable', 'count_distinct',
'sum', 'min', 'max']
Expand Down

0 comments on commit cf53cfc

Please sign in to comment.