
net.revenj.patterns.OlapCubeQuery.scala Maven / Gradle / Ivy
package net.revenj.patterns
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
/** Olap cube is online analytical processing concept used for extracting business intelligence.
* At it's core it's just a grouping of data by some dimensions and aggregation
* of values through facts. Facts can be sum, count, distinct and various others concepts.
* Cube can be made from various data sources: aggregates, snowflakes, SQL, LINQ, etc...
*
* DSL example:
* {{{
* module Finance {
* aggregate Payment {
* Timestamp createdAt { versioning; }
* String account;
* Money total;
* calculated Int year from 'it => it.Year';
* }
*
* cube Analysis {
* dimension account;
* dimension year;
* count createdAt;
* sum total;
* }
* }
* }}}
*/
trait OlapCubeQuery[T <: DataSource] {
val dimensions: Set[String]
val facts: Set[String]
def analyze(
dimensions: scala.collection.Seq[String],
facts: scala.collection.Seq[String],
order: scala.collection.Seq[(String, Boolean)] = Seq.empty,
filter: Option[Specification[T]] = None,
limit: Option[Int] = None,
offset: Option[Int] = None): Future[scala.collection.IndexedSeq[Map[String, Any]]]
def analyze(dimensionsAndFacts: scala.collection.Seq[String], filter: Specification[T]): Future[scala.collection.IndexedSeq[Map[String, Any]]] = {
analyze(
dimensionsAndFacts.filter(dimensions.contains),
dimensionsAndFacts.filter(facts.contains),
Seq.empty,
Option(filter),
None,
None
)
}
}
object OlapCubeQuery {
implicit def builder[T <: DataSource](query: OlapCubeQuery[T]): OlapCubeQueryBuilder[T] = new OlapCubeQueryBuilder(query)
class OlapCubeQueryBuilder[T <: DataSource](query: OlapCubeQuery[T]) {
private val dimensions = ArrayBuffer.newBuilder[String]
private val facts = ArrayBuffer.newBuilder[String]
private var resultLimit: Option[Int] = None
private var resultOffset: Option[Int] = None
private val order = ArrayBuffer.newBuilder[(String, Boolean)]
def use(dimensionOrFact: String): this.type = {
require(dimensionOrFact ne null, "null value provided for dimension or fact")
require(dimensionOrFact.length != 0, "empty value provided for dimension or fact")
if (query.dimensions.contains(dimensionOrFact)) {
dimensions += dimensionOrFact
} else if (query.facts.contains(dimensionOrFact)) {
facts += dimensionOrFact
} else {
throw new IllegalArgumentException("Unknown dimension or fact: " + dimensionOrFact + ". Use dimensions or facts method for available dimensions and facts")
}
this
}
def ascending(result: String): OlapCubeQueryBuilder[T] = orderBy(result, ascending = true)
def descending(result: String): OlapCubeQueryBuilder[T] = orderBy(result, ascending = false)
private def orderBy(result: String, ascending: Boolean): this.type = {
require(query.dimensions.contains(result) || query.facts.contains(result), "Unknown result: " + result + ". Result can be only field from used dimensions and facts.")
order += result -> ascending
this
}
def take(count: Int): this.type = limit(count)
def limit(count: Int): this.type = {
require(count > 0, "Invalid limit value. Limit must be positive")
resultLimit = Some(count)
this
}
def drop(count: Int): this.type = offset(count)
def offset(count: Int): this.type = {
require(count > 0, "Invalid offset value. Offset must be positive")
resultOffset = Some(count)
this
}
def analyze(specification: Option[Specification[T]] = None): Future[scala.collection.IndexedSeq[Map[String, Any]]] = {
query.analyze(dimensions.result(), facts.result(), order.result(), specification, resultLimit, resultOffset)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy