202 lines
8.0 KiB
Python
202 lines
8.0 KiB
Python
"""
|
|
Tiny BI
|
|
Copyright (C)2007 Fabien Pinckaers
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation version 2.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
"""
|
|
|
|
from pyparsing import *
|
|
|
|
import cube, axis, measure, level, query, slicer, cross
|
|
import mdx_operator
|
|
|
|
|
|
#
|
|
# Predicate: the construction of the object's query does not depend
|
|
# on the schema architecture. I think we can go on for this, this will
|
|
# simplify the extension/module architecture.
|
|
#
|
|
# Rethink about that in one month.
|
|
#
|
|
|
|
class mdx_parser(object):
|
|
def mdx_level(self):
|
|
"""
|
|
Return a parser that parse the level part of the MDX query and
|
|
return a level object
|
|
Examples:
|
|
[prod].[all prod].children
|
|
[prod].[all prod].join(...)
|
|
"""
|
|
leftSqBr = Literal("[").suppress()
|
|
rightSqBr = Literal("]").suppress()
|
|
dotToken = Literal(".").suppress()
|
|
measuresToken = Literal("measures").suppress()
|
|
scalar = Word(alphanums+alphas8bit.encode("utf-8") +"_"+" "+"-" )
|
|
level_filter = leftSqBr + scalar + rightSqBr
|
|
level_function = Keyword("children", caseless=True)
|
|
level_filter.setParseAction(lambda s,a,toks: level.level_filter(toks[0]))
|
|
level_function.setParseAction(lambda s,a,toks: level.level_function(toks[0]))
|
|
level_item = level_filter | level_function
|
|
level_parse = leftSqBr + scalar + rightSqBr + Optional(dotToken + delimitedList(level_item, ".", combine=False))
|
|
level_parse.setParseAction(lambda s,a,toks: level.level(toks[0], toks[1:]))
|
|
measure_parse = leftSqBr + measuresToken + rightSqBr + dotToken + leftSqBr + scalar + rightSqBr
|
|
measure_parse.setParseAction(lambda s,a,toks: measure.measure(toks[0]))
|
|
lev = measure_parse | level_parse
|
|
return lev
|
|
|
|
|
|
def mdx_axis(self):
|
|
"""
|
|
Return a parser that parse the axis part of the MDX query and
|
|
return an axis object
|
|
Examples:
|
|
{[prod].[all prod].children, [a]}
|
|
OR
|
|
crossjoin({[Order Date].[all]},{[prod].[all prod].children, [a]})
|
|
This is to be made recursive for the cross join
|
|
crossjoin(crossjoin({[City].[all],[City].children},{[User].[all]}),{[User].[Administrator]})
|
|
|
|
"""
|
|
leftCurlBr = Literal("{").suppress()
|
|
rightCurlBr = Literal("}").suppress()
|
|
comma = Literal(",").suppress()
|
|
|
|
level_parse = self.mdx_level()
|
|
axis_parser = delimitedList(level_parse, ",", combine=False)
|
|
axis_parser.setParseAction(lambda s,a,toks:axis.axis(mdx_operator.mdx_set(toks)))
|
|
|
|
mdx = leftCurlBr + axis_parser + rightCurlBr
|
|
|
|
return mdx
|
|
|
|
def cross_axis(self):
|
|
leftCurlBr = Literal("{").suppress()
|
|
rightCurlBr = Literal("}").suppress()
|
|
cross_parser = self.mdx_level()
|
|
cross_parser.setParseAction(lambda s,a,toks:cross.cross(toks))
|
|
|
|
cross_parse = leftCurlBr + cross_parser + rightCurlBr
|
|
|
|
return cross_parse
|
|
|
|
def mdx_cross_axis(self):
|
|
leftRoundBr = Literal("(").suppress()
|
|
rightRoundBr = Literal(")").suppress()
|
|
comma = Literal(",").suppress()
|
|
crossjoinToken = Keyword("crossjoin", caseless=True).suppress()
|
|
|
|
crossx = Forward()
|
|
cross_mdx = crossx | self.mdx_axis()
|
|
crossx << (crossjoinToken + leftRoundBr + cross_mdx + comma + self.cross_axis() + rightRoundBr)
|
|
# crossx.setParseAction(lambda s,a,toks:axis.axis(toks)
|
|
simple_mdx = self.mdx_axis()
|
|
|
|
mdx = simple_mdx | crossx
|
|
return mdx
|
|
|
|
def mdx_axis_list(self):
|
|
#
|
|
# TODO: accept notation line AXIS(0) = rows, AXIS(1)=columns, ...
|
|
#
|
|
row_names = ["rows","columns","pages"]
|
|
onToken = Keyword("on", caseless=True).suppress()
|
|
page_name = oneOf(' '.join(row_names))
|
|
axis_parser = self.mdx_cross_axis() + Optional(onToken + page_name)
|
|
|
|
def _assign_name(s,a,toks):
|
|
if len(toks)>=3:
|
|
toks[0].name_set(toks[-1])
|
|
for x in range(1,len(toks)-1):
|
|
toks[x].name_set('cross')
|
|
elif len(toks)==2:
|
|
toks[0].name_set(toks[1])
|
|
elif len(toks)==1:
|
|
toks[0].name_set(row_names.pop(0))
|
|
else:
|
|
raise 'invalid size'
|
|
return toks[:-1]
|
|
axis_parser.setParseAction(_assign_name)
|
|
axis_lst = delimitedList(axis_parser, ",")
|
|
axis_lst.setParseAction(lambda s,a,toks: [toks])
|
|
return axis_lst
|
|
|
|
def mdx_slice(self):
|
|
""" Return a MDX parser of the where clause of a MDX query """
|
|
leftBr = Literal("(").suppress()
|
|
rightBr = Literal(")").suppress()
|
|
levels = delimitedList(self.mdx_level(), ',', combine=False)
|
|
levels.setParseAction(lambda s,a,toks: toks)
|
|
slicer_lst = delimitedList(leftBr + levels + rightBr, ",", combine=False)
|
|
slicer_lst.setParseAction(lambda s,a,toks:slicer.slicer(list(toks)))
|
|
return slicer_lst
|
|
|
|
def mdx_cube(self):
|
|
""" Return a MDX parser of the from clause of a MDX query """
|
|
mdx = Word(alphas+'_')
|
|
mdx.setParseAction(lambda s,a,toks: cube.cube(toks[0]))
|
|
return mdx
|
|
|
|
def mdx_query(self):
|
|
""" Return a MDX parser of the from clause of a MDX query """
|
|
selectToken = Keyword("select", caseless=True).suppress()
|
|
fromToken = Keyword("from", caseless=True).suppress()
|
|
whereToken = Keyword("where", caseless=True).suppress()
|
|
semicolon = Literal(";").suppress()
|
|
mdx = selectToken + self.mdx_axis_list() + fromToken + self.mdx_cube() + Optional(whereToken + self.mdx_slice()) + Optional(semicolon)
|
|
mdx.setParseAction(lambda s,a,toks: query.query(*toks))
|
|
return mdx
|
|
|
|
def parse(self, query):
|
|
""" Parse a string and get a MDX object """
|
|
return self.mdx_query().parseString(query)[0]
|
|
|
|
if __name__ == "__main__":
|
|
mdx = mdx_parser()
|
|
level_parse = mdx.mdx_level()
|
|
for test in ['[sales].children','[prod].[all prod]']:
|
|
print 'Testing level', test
|
|
print level_parse.parseString(test)
|
|
print
|
|
axis_parser = mdx.mdx_axis()
|
|
for test in ['{[a]}','{[prod].[all prod],[time].[Q3].[Sep]}']:
|
|
print 'Testing axis', test
|
|
print axis_parser.parseString(test)
|
|
|
|
axis_parser = mdx.mdx_axis_list()
|
|
for test in ['{[a]} on rows, {[prod].[all prod],[time].[Q3].[Sep]} on columns',
|
|
'{[region].[all region].children} on rows, {[prod].[all prod].children} on columns'
|
|
]:
|
|
print 'Testing axis', test
|
|
print axis_parser.parseString(test)
|
|
|
|
cube_parser = mdx.mdx_cube()
|
|
for test in ['sales']:
|
|
print 'Testing axis', test
|
|
print cube_parser.parseString(test)
|
|
|
|
for query_parser in [
|
|
"Select {[region].[all region].children} on rows, {[product]} on columns from cubulus where ([time].[all time].[time_2005])",
|
|
"Select {[region].[all region].children} on rows, {[prod].[all prod].children} on columns from cubulus where ([time].[all time].[time_2005])",
|
|
#"Select {[time].[all time].children} on rows, crossjoin([region].[all region].children, [region].[all region].children) on columns from cubulus"
|
|
]:
|
|
print 'Testing ', query_parser
|
|
print mdx.parse(query_parser)
|
|
|
|
print mdx.parse('''select
|
|
{[date].[2007].[Q2].children} on rows,
|
|
{[country_id].children,[country_id].[1].children} on columns
|
|
from res_partner where ([measure].[credit_limit])''')
|
|
# vim: ts=4 sts=4 sw=4 si et
|