2015-12-15 17:22:45 +05:30
|
|
|
module Ringo.Generator
|
|
|
|
( tableDefnSQL
|
2015-12-17 23:40:56 +05:30
|
|
|
, factTableDefnSQL
|
2015-12-19 11:55:08 +05:30
|
|
|
, dimensionTablePopulateSQL
|
2015-12-20 18:25:14 +05:30
|
|
|
, factTablePopulateSQL
|
2015-12-15 17:22:45 +05:30
|
|
|
) where
|
|
|
|
|
|
|
|
import qualified Data.Text as Text
|
|
|
|
|
2015-12-18 02:37:17 +05:30
|
|
|
#if MIN_VERSION_base(4,8,0)
|
|
|
|
#else
|
|
|
|
import Control.Applicative ((<$>))
|
|
|
|
#endif
|
|
|
|
|
2015-12-16 03:03:47 +05:30
|
|
|
import Control.Monad.Reader (Reader, asks)
|
2015-12-19 11:55:08 +05:30
|
|
|
import Data.List (nub, find, subsequences, partition, sortBy)
|
|
|
|
import Data.Maybe (fromJust, mapMaybe, catMaybes)
|
2015-12-16 03:03:47 +05:30
|
|
|
import Data.Monoid ((<>))
|
2015-12-19 11:55:08 +05:30
|
|
|
import Data.Ord (comparing)
|
2015-12-16 03:03:47 +05:30
|
|
|
import Data.Text (Text)
|
2015-12-15 17:22:45 +05:30
|
|
|
|
2015-12-15 18:22:51 +05:30
|
|
|
import Ringo.Extractor.Internal
|
2015-12-15 17:22:45 +05:30
|
|
|
import Ringo.Types
|
2015-12-16 16:57:10 +05:30
|
|
|
import Ringo.Utils
|
2015-12-15 17:22:45 +05:30
|
|
|
|
|
|
|
nullableDefnSQL :: Nullable -> Text
|
|
|
|
nullableDefnSQL Null = "NULL"
|
|
|
|
nullableDefnSQL NotNull = "NOT NULL"
|
|
|
|
|
|
|
|
columnDefnSQL :: Column -> Text
|
|
|
|
columnDefnSQL Column {..} =
|
|
|
|
columnName <> " " <> columnType <> " " <> nullableDefnSQL columnNullable
|
|
|
|
|
2015-12-15 18:22:51 +05:30
|
|
|
colNamesString :: [ColumnName] -> Text
|
2015-12-19 11:55:08 +05:30
|
|
|
colNamesString = Text.intercalate ", "
|
|
|
|
|
|
|
|
fullColName :: TableName -> ColumnName -> ColumnName
|
|
|
|
fullColName tName cName = tName <> "." <> cName
|
|
|
|
|
|
|
|
constraintDefnSQL :: Table -> TableConstraint -> [Text]
|
|
|
|
constraintDefnSQL Table {..} constraint =
|
|
|
|
let alterTableSQL = "ALTER TABLE ONLY " <> tableName <> " ADD "
|
|
|
|
in case constraint of
|
|
|
|
PrimaryKey cName -> [ alterTableSQL <> "PRIMARY KEY (" <> cName <> ")" ]
|
|
|
|
ForeignKey oTableName cNamePairs ->
|
|
|
|
[ alterTableSQL <> "FOREIGN KEY (" <> colNamesString (map fst cNamePairs) <> ") REFERENCES "
|
|
|
|
<> oTableName <> " (" <> colNamesString (map snd cNamePairs) <> ")" ]
|
|
|
|
UniqueKey cNames -> let
|
|
|
|
(notNullCols, nullCols) =
|
|
|
|
both (map columnName)
|
|
|
|
$ partition ((== NotNull) . columnNullable)
|
|
|
|
$ catMaybes [ findColumn cName tableColumns | cName <- cNames ]
|
|
|
|
combinations =
|
|
|
|
map (\cs -> (cs, [ c | c <- nullCols, c `notElem` cs ]))
|
|
|
|
. sortBy (comparing length)
|
|
|
|
$ subsequences nullCols
|
|
|
|
in [ "CREATE UNIQUE INDEX ON " <> tableName
|
|
|
|
<> " (" <> colNamesString (notNullCols ++ nnCols) <> ")"
|
|
|
|
<> if null whereClauses
|
|
|
|
then ""
|
|
|
|
else "\nWHERE "<> Text.intercalate "\nAND " whereClauses
|
|
|
|
| (nnCols, nCols) <- combinations
|
|
|
|
, not $ null (notNullCols ++ nnCols)
|
|
|
|
, let whereClauses =
|
|
|
|
[ c <> " IS NOT NULL" | c <- nnCols ] ++ [ c <> " IS NULL" | c <- nCols ] ]
|
2015-12-15 17:22:45 +05:30
|
|
|
|
|
|
|
tableDefnSQL :: Table -> [Text]
|
2015-12-19 11:55:08 +05:30
|
|
|
tableDefnSQL table@Table {..} =
|
|
|
|
tableSQL : concatMap (constraintDefnSQL table) tableConstraints
|
2015-12-15 17:22:45 +05:30
|
|
|
where
|
|
|
|
tableSQL = "CREATE TABLE " <> tableName <> " (\n"
|
2015-12-19 11:55:08 +05:30
|
|
|
<> (Text.intercalate ",\n" . map columnDefnSQL $ tableColumns)
|
2015-12-15 17:22:45 +05:30
|
|
|
<> "\n)"
|
2015-12-15 18:22:51 +05:30
|
|
|
|
2015-12-17 23:40:56 +05:30
|
|
|
factTableDefnSQL :: Fact -> Table -> Reader Env [Text]
|
|
|
|
factTableDefnSQL fact table = do
|
|
|
|
Settings {..} <- asks envSettings
|
|
|
|
allDims <- extractAllDimensionTables fact
|
|
|
|
|
2015-12-18 13:20:35 +05:30
|
|
|
let factCols = forMaybe (factColumns fact) $ \col -> case col of
|
2015-12-18 01:00:32 +05:30
|
|
|
DimTime cName -> Just $ timeUnitColumnName settingDimTableIdColumnName cName settingTimeUnit
|
2015-12-17 23:40:56 +05:30
|
|
|
NoDimId cName -> Just cName
|
|
|
|
_ -> Nothing
|
|
|
|
|
2015-12-18 13:20:35 +05:30
|
|
|
dimCols = [ factDimFKIdColumnName settingDimPrefix settingDimTableIdColumnName tableName
|
|
|
|
| (_, Table {..}) <- allDims ]
|
|
|
|
|
|
|
|
indexSQLs = [ "CREATE INDEX ON " <> tableName table <> " USING btree (" <> col <> ")"
|
|
|
|
| col <- factCols ++ dimCols ]
|
2015-12-17 23:40:56 +05:30
|
|
|
|
|
|
|
return $ tableDefnSQL table ++ indexSQLs
|
|
|
|
|
2015-12-16 16:57:10 +05:30
|
|
|
dimColumnMapping :: Text -> Fact -> TableName -> [(ColumnName, ColumnName)]
|
|
|
|
dimColumnMapping dimPrefix fact dimTableName =
|
2015-12-19 11:55:08 +05:30
|
|
|
[ (dimColumnName dName cName, cName)
|
|
|
|
| DimVal dName cName <- factColumns fact , dimPrefix <> dName == dimTableName]
|
|
|
|
|
|
|
|
dimensionTablePopulateSQL :: TablePopulationMode -> Fact -> TableName -> Reader Env Text
|
|
|
|
dimensionTablePopulateSQL popMode fact dimTableName = do
|
|
|
|
dimPrefix <- settingDimPrefix <$> asks envSettings
|
|
|
|
let colMapping = dimColumnMapping dimPrefix fact dimTableName
|
|
|
|
baseSelectC = "SELECT DISTINCT\n" <> colNamesString (map snd colMapping) <> "\n"
|
|
|
|
<> "FROM " <> factTableName fact
|
|
|
|
insertC selectC = "INSERT INTO " <> dimTableName
|
|
|
|
<> " (\n" <> colNamesString (map fst colMapping) <> "\n) "
|
|
|
|
<> "SELECT x.* FROM (\n" <> selectC <> ") x"
|
|
|
|
timeCol = head [ cName | DimTime cName <- factColumns fact ]
|
|
|
|
return $ case popMode of
|
|
|
|
FullPopulation -> insertC baseSelectC
|
|
|
|
IncrementalPopulation ->
|
|
|
|
insertC (baseSelectC <> "\nWHERE "
|
|
|
|
<> timeCol <> " > ? AND " <> timeCol <> " <= ?"
|
|
|
|
<> " AND (\n"
|
|
|
|
<> Text.intercalate "\nOR " [ c <> " IS NOT NULL" | (_, c) <- colMapping ]
|
|
|
|
<> "\n)")
|
|
|
|
<> "\nLEFT JOIN " <> dimTableName <> " ON\n"
|
|
|
|
<> Text.intercalate " \nAND "
|
|
|
|
[ fullColName dimTableName c1 <> " IS NOT DISTINCT FROM " <> fullColName "x" c2
|
|
|
|
| (c1, c2) <- colMapping ]
|
|
|
|
<> "\nWHERE " <> Text.intercalate " \nAND "
|
|
|
|
[ fullColName dimTableName c <> " IS NULL" | (c, _) <- colMapping ]
|
2015-12-16 16:57:10 +05:30
|
|
|
|
2015-12-20 18:25:14 +05:30
|
|
|
factTablePopulateSQL :: TablePopulationMode -> Fact -> Reader Env Text
|
|
|
|
factTablePopulateSQL popMode fact = do
|
2015-12-18 01:00:32 +05:30
|
|
|
Settings {..} <- asks envSettings
|
|
|
|
allDims <- extractAllDimensionTables fact
|
|
|
|
tables <- asks envTables
|
2015-12-18 13:20:35 +05:30
|
|
|
let fTableName = factTableName fact
|
|
|
|
table = fromJust . findTable fTableName $ tables
|
2015-12-18 01:00:32 +05:30
|
|
|
dimIdColName = settingDimTableIdColumnName
|
2015-12-16 16:57:10 +05:30
|
|
|
|
2015-12-18 13:20:35 +05:30
|
|
|
timeUnitColumnInsertSQL cName =
|
2015-12-18 01:00:32 +05:30
|
|
|
let colName = timeUnitColumnName dimIdColName cName settingTimeUnit
|
2015-12-18 17:00:46 +05:30
|
|
|
in ( colName
|
|
|
|
, "floor(extract(epoch from " <> fullColName fTableName cName <> ")/"
|
|
|
|
<> Text.pack (show $ timeUnitToSeconds settingTimeUnit) <> ")"
|
|
|
|
, True
|
|
|
|
)
|
2015-12-16 16:57:10 +05:30
|
|
|
|
2015-12-18 13:20:35 +05:30
|
|
|
factColMap = concatFor (factColumns fact) $ \col -> case col of
|
2015-12-16 16:57:10 +05:30
|
|
|
DimTime cName -> [ timeUnitColumnInsertSQL cName ]
|
2015-12-18 17:00:46 +05:30
|
|
|
NoDimId cName -> [ (cName, fullColName fTableName cName, True) ]
|
2015-12-21 22:19:54 +05:30
|
|
|
FactCount scName cName ->
|
|
|
|
[ (cName, "count(" <> maybe "*" (fullColName fTableName) scName <> ")", False) ]
|
|
|
|
FactSum scName cName ->
|
|
|
|
[ (cName, "sum(" <> fullColName fTableName scName <> ")", False) ]
|
2015-12-18 17:00:46 +05:30
|
|
|
FactAverage scName cName ->
|
|
|
|
[ ( cName <> settingAvgCountColumSuffix
|
|
|
|
, "count(" <> fullColName fTableName scName <> ")"
|
|
|
|
, False
|
|
|
|
)
|
|
|
|
, ( cName <> settingAvgSumColumnSuffix
|
|
|
|
, "sum(" <> fullColName fTableName scName <> ")"
|
|
|
|
, False
|
|
|
|
)
|
|
|
|
]
|
2015-12-16 16:57:10 +05:30
|
|
|
_ -> []
|
|
|
|
|
2015-12-18 13:20:35 +05:30
|
|
|
dimColMap = for allDims $ \(dimFact, factTable@Table {..}) ->
|
2015-12-18 01:00:32 +05:30
|
|
|
let colName = factDimFKIdColumnName settingDimPrefix dimIdColName tableName
|
2015-12-16 16:57:10 +05:30
|
|
|
factSourceTableName = factTableName dimFact
|
2015-12-18 13:20:35 +05:30
|
|
|
insertSQL = if factTable `elem` tables
|
|
|
|
then fullColName factSourceTableName colName
|
|
|
|
else let
|
|
|
|
dimLookupWhereClauses =
|
|
|
|
[ fullColName tableName c1 <> " = " <> fullColName factSourceTableName c2
|
|
|
|
| (c1, c2) <- dimColumnMapping settingDimPrefix dimFact tableName ]
|
2015-12-18 01:00:32 +05:30
|
|
|
in "SELECT " <> dimIdColName <> " FROM " <> tableName <> "\nWHERE "
|
2015-12-21 15:30:23 +05:30
|
|
|
<> Text.intercalate "\n AND " dimLookupWhereClauses
|
2015-12-18 17:00:46 +05:30
|
|
|
in (colName, insertSQL, True)
|
2015-12-16 16:57:10 +05:30
|
|
|
|
2015-12-18 17:00:46 +05:30
|
|
|
colMap = [ (cName, if addAs then asName cName sql else sql, addAs)
|
|
|
|
| (cName, sql, addAs) <- factColMap ++ dimColMap ]
|
2015-12-16 16:57:10 +05:30
|
|
|
|
|
|
|
joinClauses =
|
|
|
|
mapMaybe (\tName -> (\p -> "LEFT JOIN " <> tName <> " ON "<> p) <$> joinClausePreds table tName)
|
|
|
|
. nub
|
2015-12-18 17:00:46 +05:30
|
|
|
. map (factTableName . fst)
|
2015-12-16 16:57:10 +05:30
|
|
|
$ allDims
|
|
|
|
|
2015-12-20 18:25:14 +05:30
|
|
|
timeCol = fullColName fTableName $ head [ cName | DimTime cName <- factColumns fact ]
|
|
|
|
|
2015-12-18 01:00:32 +05:30
|
|
|
return $ "INSERT INTO "
|
|
|
|
<> extractedFactTableName settingFactPrefix settingFactInfix (factName fact) settingTimeUnit
|
2015-12-18 17:00:46 +05:30
|
|
|
<> " (\n" <> unlineCols (map fst3 colMap) <> "\n)"
|
|
|
|
<> "\nSELECT \n" <> unlineCols (map snd3 colMap)
|
2015-12-19 11:55:08 +05:30
|
|
|
<> "\nFROM " <> fTableName <> "\n" <> Text.intercalate"\n" joinClauses
|
2015-12-20 18:25:14 +05:30
|
|
|
<> (if popMode == IncrementalPopulation
|
|
|
|
then "\nWHERE " <> timeCol <> " > ? AND " <> timeCol <> " <= ?"
|
|
|
|
else "")
|
2015-12-18 17:00:46 +05:30
|
|
|
<> "\nGROUP BY \n"
|
|
|
|
<> unlineCols (map ((groupByColPrefix <>) . fst3) . filter thd3 $ colMap)
|
2015-12-16 16:57:10 +05:30
|
|
|
where
|
2015-12-18 17:00:46 +05:30
|
|
|
groupByColPrefix = "xxff_"
|
|
|
|
asName cName sql = "(" <> sql <> ")" <> " as " <> groupByColPrefix <> cName
|
2015-12-19 11:55:08 +05:30
|
|
|
unlineCols = Text.intercalate ",\n "
|
2015-12-16 16:57:10 +05:30
|
|
|
|
|
|
|
joinClausePreds table oTableName =
|
|
|
|
fmap (\(ForeignKey _ colPairs) ->
|
2015-12-19 11:55:08 +05:30
|
|
|
Text.intercalate " AND "
|
2015-12-16 16:57:10 +05:30
|
|
|
. map (\(c1, c2) -> fullColName (tableName table) c1 <> " = " <> fullColName oTableName c2)
|
|
|
|
$ colPairs )
|
|
|
|
. find (\cons -> case cons of
|
|
|
|
ForeignKey tName _ -> tName == oTableName
|
|
|
|
_ -> False)
|
|
|
|
. tableConstraints
|
|
|
|
$ table
|