Module: Mondrian::REST::QueryHelper

Defined in:
lib/mondrian_rest/query_helper.rb

Instance Method Summary collapse

Instance Method Details

#build_query(cube, options = {}) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/mondrian_rest/query_helper.rb', line 113

def build_query(cube, options={})

  measure_members = cube.dimension('Measures').hierarchy.levels.first.members
  options = {
    'cut' => [],
    'drilldown' => [],
    'measures' => [measure_members.first.name],
    'nonempty' => false,
    'distinct' => false
  }.merge(options)

  # validate measures exist
  cm_names = measure_members.map(&:name)

  options['measures'].each { |m|
    error!("Measure #{m} does not exist in cube #{cube.name}", 404) unless cm_names.include?(m)
  }

  # measures go in axis(0) of the resultset
  query = olap.from(cube.name)
          .axis(0,
                *options['measures'].map { |m|
                  measure_members.find { |cm| cm.name == m }.full_name
                })
  if options['nonempty']
    query = query.nonempty
  end
  axis_idx = 1

  query_axes = options['drilldown'].map { |dd| parse_drilldown(cube, dd) }

  slicer_axis = options['cut'].reduce({}) { |h, cut_expr|
    pc = parse_cut(cube, cut_expr)
    h[pc[:level]] = pc
    h
  }

  dd = query_axes.map do |qa|
    # there's a slice (cut) on this axis
    if slicer_axis[qa.raw_level]
      cut = slicer_axis.delete(qa.raw_level)
      case cut[:type]
      when :member
        "{#{cut[:cut]}}"
      else
        cut[:cut]
      end
    else
      qa.raw_level.unique_name + '.Members'
    end
  end

  # query axes (drilldown)
  dd.each do |ds|
    query = query.axis(axis_idx,
                       ds)

    if options['distinct']
      query = query.distinct
    end

    if options['nonempty']
      query = query.nonempty
    end

    axis_idx += 1
  end

  # slicer axes (cut)
  if slicer_axis.size >= 1
    query = query.where(slicer_axis.values.map { |v| v[:cut] })
  end
  query
end

#get_dimension(cube, dname) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/mondrian_rest/query_helper.rb', line 11

def get_dimension(cube, dname)
  cube_dimensions = cube.dimensions
                    .find_all { |d| d.dimension_type != :measures }
  dim = cube_dimensions.find { |d| d.name == dname }
  error!("Dimension #{dname} does not exist", 400) if dim.nil?
  dim
end

#get_member(cube, member_exp) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/mondrian_rest/query_helper.rb', line 19

def get_member(cube, member_exp)
  begin
    rm = cube.raw_cube
       .lookupMember(org.olap4j.mdx.IdentifierNode.parseIdentifier(member_exp).getSegmentList)
  rescue Java::JavaLang::IllegalArgumentException
    error!("Illegal member expression: #{member_exp}", 400)
  end
  member = nil
  unless rm.nil?
    member = Mondrian::OLAP::Member.new(rm)
  end
  member
end

#parse_cut(cube, cut_expr) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mondrian_rest/query_helper.rb', line 33

def parse_cut(cube, cut_expr)
  p = mdx_parser.parseExpression(cut_expr)

  case p
  when org.olap4j.mdx.CallNode
    case p.getOperatorName
    when "{}"
      # check that the set contains only Members of a single dimension level
      ls = p.getArgList.map { |id_node|
        get_member(cube, unparse_node(id_node)).raw_level
      }.uniq
      unless ls.size == 1
        error!("Illegal cut: " + cut_expr, 400)
      end
      { level: ls.first, cut: unparse_node(p), type: :set }
    when "()"
      # check that the range contains a valid range

      unless p.getArgList.first.is_a?(org.olap4j.mdx.CallNode) \
        and p.getArgList.first.getOperatorName == ':'
        error!("Illegal cut: " + cut_expr, 400)
      end

      ls = p.getArgList.first.getArgList.map { |id_node|
        get_member(cube, unparse_node(id_node)).raw_level
      }.uniq

      unless ls.size == 1
        error!("Illegal cut: " + cut_expr, 400)
      end

      { level: ls.first, cut: unparse_node(p), type: :range }
    else
      error!("Illegal cut: " + cut_expr, 400)
    end
  when org.olap4j.mdx.IdentifierNode
    # if `cut_expr` looks like a member, check that it's level is
    # equal to `level`
    m = get_member(cube, cut_expr)
    { level: m.raw_level, cut: cut_expr, type: :member }
  else
    error!("Illegal cut: " + cut_expr, 400)
  end
end

#parse_drilldown(cube, drilldown) ⇒ Object

Parses a drilldown specification XXX TODO write doc



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/mondrian_rest/query_helper.rb', line 81

def parse_drilldown(cube, drilldown)
  begin
    s = org.olap4j.mdx.IdentifierNode.parseIdentifier(drilldown).getSegmentList
  rescue Java::JavaLang::IllegalArgumentException
    error!("Illegal drilldown specification: #{drilldown}", 400)
  end

  if s.size > 3 || s.map(&:quoting).any? { |q| q.name == 'KEY' }
    error!("Illegal drilldown specification: #{drilldown}", 400)
    return
  end

  dimension = get_dimension(cube, s.first.name)
  hierarchy = dimension.hierarchies.first
  level = hierarchy.levels[hierarchy.has_all? ? 1 : 0]

  if s.size > 1
    if s.size == 3 # 3 parts, means that a hierarchy was provided
      hierarchy = dimension.hierarchies.find { |h_| h_.name == "#{dimension.name}.#{s[1].name}" }
      if hierarchy.nil?
        error!("Hierarchy `#{s[1].name}` does not exist in dimension #{dimension.name}", 404)
      end
    end
    level = hierarchy.levels.find { |l_| l_.name == s[s.size - 1].name }
    if level.nil?
      error!("Level `#{s[1].name}` does not exist in #{dimension.name}", 404)
    end
  end

  level
end

#unparse_node(node) ⇒ Object



4
5
6
7
8
9
# File 'lib/mondrian_rest/query_helper.rb', line 4

def unparse_node(node)
  sw = java.io.StringWriter.new
  ptw = org.olap4j.mdx.ParseTreeWriter.new(sw)
  node.unparse(ptw)
  sw.toString
end