Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting arbitrary expressions #43

Merged
merged 1 commit into from
Oct 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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