Skip to content
This repository has been archived by the owner on Jan 18, 2020. It is now read-only.

Commit

Permalink
Merge pull request #292 from chop-dbhi/sql-resource
Browse files Browse the repository at this point in the history
Add endpoints for SQL representation of queries
  • Loading branch information
bruth committed Jul 20, 2016
2 parents fb8f056 + 1a0df13 commit 9627aa6
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 22 deletions.
42 changes: 42 additions & 0 deletions serrano/resources/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,46 @@ def get(self, request, **kwargs):
}


def prune_context(cxt):
if 'children' in cxt:
cxt['children'] = map(prune_context, cxt['children'])
else:
cxt = {
'concept': cxt.get('concept'),
'field': cxt.get('field'),
'operator': cxt.get('operator'),
'value': cxt.get('value'),
}

return cxt


class ContextSqlResource(ContextBase):
def is_not_found(self, request, response, **kwargs):
return self.get_object(request, **kwargs) is None

def get(self, request, **kwargs):
params = self.get_params(request)
instance = self.get_object(request, **kwargs)

QueryProcessor = pipeline.query_processors[params['processor']]
processor = QueryProcessor(tree=params['tree'], context=instance)
queryset = processor.get_queryset(request=request).values('pk')

sql, params = queryset.query.get_compiler(queryset.db).as_sql()

return {
'description': prune_context(instance.json),
'representation': {
'sql': sql,
'params': params,
},
}


single_resource = never_cache(ContextResource())
stats_resource = never_cache(ContextStatsResource())
sql_resource = never_cache(ContextSqlResource())
active_resource = never_cache(ContextsResource())
revisions_resource = never_cache(RevisionsResource(
object_model=DataContext, object_model_template=templates.Context,
Expand All @@ -257,6 +295,10 @@ def get(self, request, **kwargs):
url(r'^(?P<pk>\d+)/stats/$', stats_resource, name='stats'),
url(r'^session/stats/$', stats_resource, {'session': True}, name='stats'),

# SQL for a single context
url(r'^(?P<pk>\d+)/sql/$', sql_resource, name='sql'),
url(r'^session/sql/$', sql_resource, {'session': True}, name='sql'),

# Revision related endpoints
url(r'^revisions/$', revisions_resource, name='revisions'),
url(r'^(?P<pk>\d+)/revisions/$', revisions_for_object_resource,
Expand Down
16 changes: 15 additions & 1 deletion serrano/resources/query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
from serrano.resources.history import ObjectRevisionResource, \
ObjectRevisionsResource, RevisionsResource
from serrano.resources.query.base import PublicQueriesResource, \
QueryResource, QueriesResource
QueryResource, QueriesResource, QuerySqlResource
from serrano.resources.query.forks import QueryForksResource
from serrano.resources.query.results import QueryResultsResource
from serrano.resources.query.stats import QueryStatsResource

single_resource = never_cache(QueryResource())
active_resource = never_cache(QueriesResource())
sql_resource = never_cache(QuerySqlResource())
public_resource = never_cache(PublicQueriesResource())
forks_resource = never_cache(QueryForksResource())
stats_resource = never_cache(QueryStatsResource())
Expand Down Expand Up @@ -107,6 +108,19 @@
name='stats'
),

# SQL
url(
r'^(?P<pk>\d+)/sql/$',
sql_resource,
name='sql'
),
url(
r'^session/sql/$',
sql_resource,
{'session': True},
name='sql'
),

# Forks
url(
r'^(?P<pk>\d+)/forks/$',
Expand Down
68 changes: 66 additions & 2 deletions serrano/resources/query/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from restlib2.params import Parametizer, StrParam

from avocado.events import usage
from avocado.models import DataQuery
from avocado.models import DataQuery, DataView, DataContext
from avocado.query import pipeline
from serrano.forms import QueryForm
from serrano.links import reverse_tmpl
Expand Down Expand Up @@ -85,20 +85,42 @@ def get_queryset(self, request, **kwargs):

return self.model.objects.filter(**kwargs)

def get_request_filters(self, request):
filters = {}

if getattr(request, 'user', None) and request.user.is_authenticated():
filters['user'] = request.user
elif request.session.session_key:
filters['session_key'] = request.session.session_key

return filters

def get_object(self, request, pk=None, session=None, **kwargs):
if not pk and not session:
raise ValueError('A pk or session must used for the lookup')

if not hasattr(request, 'instance'):
queryset = self.get_queryset(request, **kwargs)
instance = None

try:
if pk:
instance = queryset.get(pk=pk)
else:
instance = queryset.get(session=True)
except self.model.DoesNotExist:
instance = None
if session:
filters = self.get_request_filters(request)

try:
context = DataContext.objects.filter(**filters)\
.get(session=True)
view = DataView.objects.filter(**filters)\
.get(session=True)
instance = DataQuery(context_json=context.json,
view_json=view.json)
except (DataContext.DoesNotExist, DataView.DoesNotExist):
pass

request.instance = instance

Expand Down Expand Up @@ -219,3 +241,45 @@ def delete(self, request, **kwargs):
instance.delete()
usage.log('delete', instance=instance, request=request)
request.session.modified = True


def prune_context(cxt):
if 'children' in cxt:
cxt['children'] = map(prune_context, cxt['children'])
else:
cxt = {
'concept': cxt.get('concept'),
'field': cxt.get('field'),
'operator': cxt.get('operator'),
'value': cxt.get('value'),
}

return cxt


class QuerySqlResource(QueryBase):
def is_not_found(self, request, response, **kwargs):
return self.get_object(request, **kwargs) is None

def get(self, request, **kwargs):
params = self.get_params(request)
instance = self.get_object(request, **kwargs)

QueryProcessor = pipeline.query_processors[params['processor']]
processor = QueryProcessor(tree=params['tree'],
context=instance.context,
view=instance.view)
queryset = processor.get_queryset(request=request)

sql, params = queryset.query.get_compiler(queryset.db).as_sql()

return {
'description': {
'context': prune_context(instance.context_json),
'view': instance.view_json,
},
'representation': {
'sql': sql,
'params': params,
},
}
75 changes: 56 additions & 19 deletions serrano/resources/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from django.conf.urls import patterns, url
from django.views.decorators.cache import never_cache
from restlib2.http import codes
from restlib2.params import Parametizer, StrParam
from preserialize.serialize import serialize
from modeltree.tree import trees, MODELTREE_DEFAULT_ALIAS
from avocado.models import DataView
from avocado.events import usage
from avocado.query import pipeline
from serrano.forms import ViewForm
from .base import ThrottledResource
from .history import RevisionsResource, ObjectRevisionsResource, \
Expand All @@ -16,13 +19,20 @@
log = logging.getLogger(__name__)


class ViewParametizer(Parametizer):
tree = StrParam(MODELTREE_DEFAULT_ALIAS, choices=trees)
processor = StrParam('default', choices=pipeline.query_processors)


class ViewBase(ThrottledResource):
cache_max_age = 0
private_cache = True

model = DataView
template = templates.View

parametizer = ViewParametizer

def get_link_templates(self, request):
uri = request.build_absolute_uri

Expand Down Expand Up @@ -52,6 +62,25 @@ def get_queryset(self, request, **kwargs):

return self.model.objects.filter(**kwargs)

def get_object(self, request, pk=None, session=None, **kwargs):
if not pk and not session:
raise ValueError('A pk or session must used for the lookup')

if not hasattr(request, 'instance'):
queryset = self.get_queryset(request, **kwargs)

try:
if pk:
instance = queryset.get(pk=pk)
else:
instance = queryset.get(session=True)
except self.model.DoesNotExist:
instance = None

request.instance = instance

return request.instance

def get_default(self, request):
default = self.model.objects.get_default_template()

Expand Down Expand Up @@ -105,25 +134,6 @@ def post(self, request):

class ViewResource(ViewBase):
"Resource for accessing a single view"
def get_object(self, request, pk=None, session=None, **kwargs):
if not pk and not session:
raise ValueError('A pk or session must used for the lookup')

if not hasattr(request, 'instance'):
queryset = self.get_queryset(request, **kwargs)

try:
if pk:
instance = queryset.get(pk=pk)
else:
instance = queryset.get(session=True)
except self.model.DoesNotExist:
instance = None

request.instance = instance

return request.instance

def is_not_found(self, request, response, **kwargs):
return self.get_object(request, **kwargs) is None

Expand Down Expand Up @@ -169,8 +179,31 @@ def delete(self, request, **kwargs):
request.session.modified = True


class ViewSqlResource(ViewBase):
def is_not_found(self, request, response, **kwargs):
return self.get_object(request, **kwargs) is None

def get(self, request, **kwargs):
params = self.get_params(request)
instance = self.get_object(request, **kwargs)

QueryProcessor = pipeline.query_processors[params['processor']]
processor = QueryProcessor(tree=params['tree'], view=instance)
queryset = processor.get_queryset(request=request)

sql, params = queryset.query.get_compiler(queryset.db).as_sql()

return {
'description': instance.json,
'representation': {
'sql': sql,
'params': params,
},
}

single_resource = never_cache(ViewResource())
active_resource = never_cache(ViewsResource())
sql_resource = never_cache(ViewSqlResource)
revisions_resource = never_cache(RevisionsResource(
object_model=DataView, object_model_template=templates.View,
object_model_base_uri='serrano:views'))
Expand All @@ -190,6 +223,10 @@ def delete(self, request, **kwargs):
url(r'^(?P<pk>\d+)/$', single_resource, name='single'),
url(r'^session/$', single_resource, {'session': True}, name='session'),

# Endpoints for specific views
url(r'^(?P<pk>\d+)/sql/$', sql_resource, name='sql'),
url(r'^session/sql/$', sql_resource, {'session': True}, name='sql'),

# Revision related endpoints
url(r'^revisions/$', revisions_resource, name='revisions'),
url(r'^(?P<pk>\d+)/revisions/$', revisions_for_object_resource,
Expand Down

0 comments on commit 9627aa6

Please sign in to comment.