mirror of
https://github.com/djohnlewis/stackdump
synced 2025-01-23 07:01:41 +00:00
135 lines
5.3 KiB
Python
135 lines
5.3 KiB
Python
from sqlbuilder import *
|
|
from main import SQLObject, sqlmeta
|
|
import types, threading
|
|
|
|
####
|
|
|
|
class ViewSQLObjectField(SQLObjectField):
|
|
def __init__(self, alias, *arg):
|
|
SQLObjectField.__init__(self, *arg)
|
|
self.alias = alias
|
|
def __sqlrepr__(self, db):
|
|
return self.alias + "." + self.fieldName
|
|
def tablesUsedImmediate(self):
|
|
return [self.tableName]
|
|
|
|
class ViewSQLObjectTable(SQLObjectTable):
|
|
FieldClass = ViewSQLObjectField
|
|
|
|
def __getattr__(self, attr):
|
|
if attr == 'sqlmeta':
|
|
raise AttributeError
|
|
return SQLObjectTable.__getattr__(self, attr)
|
|
|
|
def _getattrFromID(self, attr):
|
|
return self.FieldClass(self.soClass.sqlmeta.alias, self.tableName, 'id', attr, self.soClass, None)
|
|
|
|
def _getattrFromColumn(self, column, attr):
|
|
return self.FieldClass(self.soClass.sqlmeta.alias, self.tableName, column.name, attr, self.soClass, column)
|
|
|
|
|
|
class ViewSQLObject(SQLObject):
|
|
"""
|
|
A SQLObject class that derives all it's values from other SQLObject classes.
|
|
Columns on subclasses should use SQLBuilder constructs for dbName,
|
|
and sqlmeta should specify:
|
|
|
|
* idName as a SQLBuilder construction
|
|
* clause as SQLBuilder clause for specifying join conditions or other restrictions
|
|
* table as an optional alternate name for the class alias
|
|
|
|
See test_views.py for simple examples.
|
|
"""
|
|
|
|
def __classinit__(cls, new_attrs):
|
|
SQLObject.__classinit__(cls, new_attrs)
|
|
# like is_base
|
|
if cls.__name__ != 'ViewSQLObject':
|
|
dbName = hasattr(cls,'_connection') and (cls._connection and cls._connection.dbName) or None
|
|
|
|
if getattr(cls.sqlmeta, 'table', None):
|
|
cls.sqlmeta.alias = cls.sqlmeta.table
|
|
else:
|
|
cls.sqlmeta.alias = cls.sqlmeta.style.pythonClassToDBTable(cls.__name__)
|
|
alias = cls.sqlmeta.alias
|
|
columns = [ColumnAS(cls.sqlmeta.idName, 'id')]
|
|
# {sqlrepr-key: [restriction, *aggregate-column]}
|
|
aggregates = {'':[None]}
|
|
inverseColumns = dict([(y,x) for x,y in cls.sqlmeta.columns.iteritems()])
|
|
for col in cls.sqlmeta.columnList:
|
|
n = inverseColumns[col]
|
|
ascol = ColumnAS(col.dbName, n)
|
|
if isAggregate(col.dbName):
|
|
restriction = getattr(col, 'aggregateClause',None)
|
|
if restriction:
|
|
restrictkey = sqlrepr(restriction, dbName)
|
|
aggregates[restrictkey] = aggregates.get(restrictkey, [restriction]) + [ascol]
|
|
else:
|
|
aggregates[''].append(ascol)
|
|
else:
|
|
columns.append(ascol)
|
|
|
|
metajoin = getattr(cls.sqlmeta, 'join', NoDefault)
|
|
clause = getattr(cls.sqlmeta, 'clause', NoDefault)
|
|
select = Select(columns,
|
|
distinct=True,
|
|
# @@ LDO check if this really mattered for performance
|
|
# @@ Postgres (and MySQL?) extension!
|
|
#distinctOn=cls.sqlmeta.idName,
|
|
join=metajoin,
|
|
clause=clause)
|
|
|
|
aggregates = aggregates.values()
|
|
#print cls.__name__, sqlrepr(aggregates, dbName)
|
|
|
|
if aggregates != [[None]]:
|
|
join = []
|
|
last_alias = "%s_base" % alias
|
|
last_id = "id"
|
|
last = Alias(select, last_alias)
|
|
columns = [ColumnAS(SQLConstant("%s.%s"%(last_alias,x.expr2)), x.expr2) for x in columns]
|
|
|
|
for i, agg in enumerate(aggregates):
|
|
restriction = agg[0]
|
|
if restriction is None:
|
|
restriction = clause
|
|
else:
|
|
restriction = AND(clause, restriction)
|
|
agg = agg[1:]
|
|
agg_alias = "%s_%s" % (alias, i)
|
|
agg_id = '%s_id'%agg_alias
|
|
if not last.q.alias.endswith('base'):
|
|
last = None
|
|
new_alias = Alias(Select([ColumnAS(cls.sqlmeta.idName, agg_id)]+agg,
|
|
groupBy=cls.sqlmeta.idName,
|
|
join=metajoin,
|
|
clause=restriction),
|
|
agg_alias)
|
|
agg_join = LEFTJOINOn(last,
|
|
new_alias,
|
|
"%s.%s = %s.%s" % (last_alias, last_id, agg_alias, agg_id))
|
|
|
|
join.append(agg_join)
|
|
for col in agg:
|
|
columns.append(ColumnAS(SQLConstant("%s.%s"%(agg_alias, col.expr2)),col.expr2))
|
|
|
|
last = new_alias
|
|
last_alias = agg_alias
|
|
last_id = agg_id
|
|
select = Select(columns,
|
|
join=join)
|
|
|
|
cls.sqlmeta.table = Alias(select, alias)
|
|
cls.q = ViewSQLObjectTable(cls)
|
|
for n,col in cls.sqlmeta.columns.iteritems():
|
|
col.dbName = n
|
|
|
|
def isAggregate(expr):
|
|
if isinstance(expr, SQLCall):
|
|
return True
|
|
if isinstance(expr, SQLOp):
|
|
return isAggregate(expr.expr1) or isAggregate(expr.expr2)
|
|
return False
|
|
|
|
######
|