diff --git a/caravel/bin/caravel b/caravel/bin/caravel index 83c6bb6071fc..f03684cc16d6 100755 --- a/caravel/bin/caravel +++ b/caravel/bin/caravel @@ -118,5 +118,11 @@ def refresh_druid(): session.commit() +@manager.command +def worker(): + """Starts a Caravel worker for async query load""" + raise NotImplementedError("# TODO! @b.kyryliuk") + + if __name__ == "__main__": manager.run() diff --git a/caravel/config.py b/caravel/config.py index 1c66411e99db..4cbf9af5088b 100644 --- a/caravel/config.py +++ b/caravel/config.py @@ -173,6 +173,23 @@ INTERVAL = 1 BACKUP_COUNT = 30 +# Default celery config is to use SQLA as a broker, in a production setting +# you'll want to use a proper broker as specified here: +# http://docs.celeryproject.org/en/latest/getting-started/brokers/index.html +""" +# Example: +class CeleryConfig(object): + BROKER_URL = 'amqp://guest:guest@localhost:5672//' + ## Broker settings. + BROKER_URL = 'amqp://guest:guest@localhost:5672//' + CELERY_IMPORTS = ('myapp.tasks', ) + CELERY_RESULT_BACKEND = 'db+sqlite:///results.db' + CELERY_ANNOTATIONS = {'tasks.add': {'rate_limit': '10/s'}} +""" +CELERY_CONFIG = None + +# Maximum number of rows returned in the SQL editor +SQL_MAX_ROW = 1000 try: from caravel_config import * # noqa @@ -181,3 +198,4 @@ if not CACHE_DEFAULT_TIMEOUT: CACHE_DEFAULT_TIMEOUT = CACHE_CONFIG.get('CACHE_DEFAULT_TIMEOUT') + diff --git a/caravel/migrations/versions/33459b145c15_allow_temp_table.py b/caravel/migrations/versions/33459b145c15_allow_temp_table.py new file mode 100644 index 000000000000..7fab1a13a63e --- /dev/null +++ b/caravel/migrations/versions/33459b145c15_allow_temp_table.py @@ -0,0 +1,23 @@ +"""allow_temp_table + +Revision ID: 33459b145c15 +Revises: d8bc074f7aad +Create Date: 2016-06-13 15:54:08.117103 + +""" + +# revision identifiers, used by Alembic. +revision = '33459b145c15' +down_revision = 'd8bc074f7aad' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column( + 'dbs', sa.Column('allow_temp_table', sa.Boolean(), nullable=True)) + + +def downgrade(): + op.drop_column('dbs', 'allow_temp_table') diff --git a/caravel/models.py b/caravel/models.py index 651918dd3886..f9f28895a557 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -368,6 +368,7 @@ class Database(Model, AuditMixinNullable): sqlalchemy_uri = Column(String(1024)) password = Column(EncryptedType(String(1024), config.get('SECRET_KEY'))) cache_timeout = Column(Integer) + allow_temp_table = Column(Boolean, default=False) extra = Column(Text, default=textwrap.dedent("""\ { "metadata_params": {}, diff --git a/caravel/views.py b/caravel/views.py index d2e8e5b9db52..bda4e9efea17 100644 --- a/caravel/views.py +++ b/caravel/views.py @@ -4,17 +4,18 @@ from __future__ import print_function from __future__ import unicode_literals +from datetime import datetime import json import logging import re import sys import time import traceback -from datetime import datetime import pandas as pd import sqlalchemy as sqla +import celery from flask import ( g, request, redirect, flash, Response, render_template, Markup) from flask_appbuilder import ModelView, CompactCRUDMixin, BaseView, expose @@ -36,6 +37,47 @@ config = app.config log_this = models.Log.log_this +celery_app = celery.Celery(celery_config=config.get('CELERY_CONFIG')) + +@celery_app.task +def get_sql_results(database_id, sql, async=False): + """Gets sql results from a Caravel database connection""" + # TODO @b.kyryliuk handle async + # handle models.Queries (userid, sql, timestamps, status) index on userid, state, start_ddtm + session = db.session() + mydb = session.query(models.Database).filter_by(id=database_id).first() + + if ( + not self.appbuilder.sm.has_access( + 'all_datasource_access', 'all_datasource_access')): + raise utils.CaravelSecurityException(_( + "This view requires the `all_datasource_access` permission")) + content = "" + if mydb: + eng = mydb.get_sqla_engine() + if config.SQL_MAX_ROW: + sql = sql.strip().strip(';') + qry = ( + select('*') + .select_from(TextAsFrom(text(sql), ['*']).alias('inner_qry')) + .limit(config.SQL_MAX_ROW) + ) + sql = str(qry.compile(eng, compile_kwargs={"literal_binds": True})) + try: + df = pd.read_sql_query(sql=sql, con=eng) + content = df.to_html( + index=False, + na_rep='', + classes=( + "dataframe table table-striped table-bordered " + "table-condensed sql_results").split(' ')) + except Exception as e: + content = ( + '