module Database.Sql.Vertica.Parser where
import Database.Sql.Type
import Database.Sql.Info
import Database.Sql.Helpers
import Database.Sql.Vertica.Type
import Database.Sql.Vertica.Scanner
import Database.Sql.Vertica.Parser.Internal
import Database.Sql.Position
import qualified Database.Sql.Vertica.Parser.Token as Tok
import Database.Sql.Vertica.Parser.IngestionOptions
import Database.Sql.Vertica.Parser.Shared
import Data.Char (isDigit)
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.List as L
import Data.Maybe (catMaybes, fromMaybe)
import Data.Monoid (Endo (..))
import Data.Semigroup (Option (..))
import qualified Text.Parsec as P
import Text.Parsec ( chainl1, choice, many, many1
, option, optional, optionMaybe
, sepBy, sepBy1, try, (<|>), (<?>))
import Control.Arrow (first)
import Control.Monad (void, (>=>), when)
import Data.Semigroup (Semigroup (..), sconcat)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE (last, fromList)
import Data.Foldable (fold)
statementParser :: Parser (VerticaStatement RawNames Range)
statementParser = do
maybeStmt <- optionMaybe $ choice
[ try $ VerticaStandardSqlStatement <$> statementP
, do
_ <- try $ P.lookAhead createProjectionPrefixP
VerticaCreateProjectionStatement <$> createProjectionP
, try $ VerticaMultipleRenameStatement <$> multipleRenameP
, try $ VerticaSetSchemaStatement <$> setSchemaP
, try $ VerticaUnhandledStatement <$> renameProjectionP
, do
_ <- try $ P.lookAhead alterResourcePoolPrefixP
VerticaUnhandledStatement <$> alterResourcePoolP
, do
_ <- try $ P.lookAhead createResourcePoolPrefixP
VerticaUnhandledStatement <$> createResourcePoolP
, do
_ <- try $ P.lookAhead dropResourcePoolPrefixP
VerticaUnhandledStatement <$> dropResourcePoolP
, do
_ <- try $ P.lookAhead createFunctionPrefixP
VerticaUnhandledStatement <$> createFunctionP
, VerticaUnhandledStatement <$> alterTableAddConstraintP
, VerticaUnhandledStatement <$> exportToStdoutP
, do
_ <- try $ P.lookAhead setSessionPrefixP
VerticaUnhandledStatement <$> setSessionP
, VerticaUnhandledStatement <$> setTimeZoneP
, VerticaUnhandledStatement <$> connectP
, VerticaUnhandledStatement <$> disconnectP
, VerticaUnhandledStatement <$> createAccessPolicyP
, VerticaUnhandledStatement <$> copyFromP
, VerticaUnhandledStatement <$> showP
, VerticaMergeStatement <$> mergeP
]
case maybeStmt of
Just stmt -> terminator >> return stmt
Nothing -> VerticaStandardSqlStatement <$> emptyStatementP
where
terminator = (Tok.semicolonP <|> eof)
emptyStatementP = EmptyStmt <$> Tok.semicolonP
parse :: Text -> Either P.ParseError (VerticaStatement RawNames Range)
parse = P.runParser statementParser 0 "-" . tokenize
parseAll :: Text -> Either P.ParseError (VerticaStatement RawNames Range)
parseAll = P.runParser (statementParser <* P.eof) 0 "-" . tokenize
parseMany :: Text -> Either P.ParseError [VerticaStatement RawNames Range]
parseMany = P.runParser (P.many1 statementParser) 0 "-" . tokenize
parseManyAll :: Text -> Either P.ParseError [VerticaStatement RawNames Range]
parseManyAll text = P.runParser (P.many1 statementParser <* P.eof) 0 "-" . tokenize $ text
parseManyEithers :: Text -> Either P.ParseError [Either (Unparsed Range) (VerticaStatement RawNames Range)]
parseManyEithers text = P.runParser parser 0 "-" . tokenize $ text
where
parser = do
statements <- P.many1 $ P.setState 0 >> choice
[ try $ Right <$> statementParser
, try $ Left <$> do
ss <- many Tok.notSemicolonP
e <- Tok.semicolonP
pure $ case ss of
[] -> Unparsed e
s:_ -> Unparsed (s <> e)
]
locs <- many Tok.notSemicolonP
P.eof
pure $ case locs of
[] -> statements
s:es -> statements ++ [Left $ Unparsed $ sconcat (s:|es)]
optionBool :: Parser a -> Parser Bool
optionBool p = option False $ p >> pure True
statementP :: Parser (Statement Vertica RawNames Range)
statementP = choice
[ InsertStmt <$> insertP
, DeleteStmt <$> deleteP
, QueryStmt <$> queryP
, explainP
, TruncateStmt <$> truncateP
, AlterTableStmt <$> alterTableP
, do
_ <- try $ P.lookAhead createSchemaPrefixP
CreateSchemaStmt <$> createSchemaP
, do
_ <- try $ P.lookAhead createExternalTablePrefixP
CreateTableStmt <$> createExternalTableP
, do
_ <- try $ P.lookAhead createViewPrefixP
CreateViewStmt <$> createViewP
, CreateTableStmt <$> createTableP
, do
_ <- try $ P.lookAhead dropViewPrefixP
DropViewStmt <$> dropViewP
, DropTableStmt <$> dropTableP
, GrantStmt <$> grantP
, RevokeStmt <$> revokeP
, BeginStmt <$> beginP
, CommitStmt <$> commitP
, RollbackStmt <$> rollbackP
]
oqColumnNameP :: Parser (OQColumnName Range)
oqColumnNameP = (\ (c, r') -> QColumnName r' Nothing c) <$> Tok.columnNameP
insertP :: Parser (Insert RawNames Range)
insertP = do
r <- Tok.insertP
insertBehavior <- InsertAppend <$> Tok.intoP
insertTable <- tableNameP
insertColumns <- optionMaybe $ try $ do
_ <- Tok.openP
c:cs <- oqColumnNameP `sepBy1` Tok.commaP
_ <- Tok.closeP
pure (c :| cs)
insertValues <- choice
[ do
s <- Tok.defaultP
e <- Tok.valuesP
pure $ InsertDefaultValues (s <> e)
, do
s <- Tok.valuesP
_ <- Tok.openP
x:xs <- defaultExprP `sepBy1` Tok.commaP
e <- Tok.closeP
let row = x :| xs
rows = row :| []
pure $ InsertExprValues (s <> e) rows
, InsertSelectValues <$> queryP
]
let insertInfo = r <> getInfo insertValues
pure Insert{..}
defaultExprP :: Parser (DefaultExpr RawNames Range)
defaultExprP = choice
[ DefaultValue <$> Tok.defaultP
, ExprValue <$> exprP
]
deleteP :: Parser (Delete RawNames Range)
deleteP = do
r <- Tok.deleteP
_ <- Tok.fromP
table <- tableNameP
maybeExpr <- optionMaybe $ do
_ <- Tok.whereP
exprP
let r' = case maybeExpr of
Nothing -> getInfo table
Just expr -> getInfo expr
info = r <> r'
pure $ Delete info table maybeExpr
truncateP :: Parser (Truncate RawNames Range)
truncateP = do
s <- Tok.truncateP
_ <- Tok.tableP
table <- tableNameP
pure $ Truncate (s <> getInfo table) table
querySelectP :: Parser (Query RawNames Range)
querySelectP = do
select <- selectP
return $ QuerySelect (selectInfo select) select
queryP :: Parser (Query RawNames Range)
queryP = manyParensP $ do
with <- option id withP
query <- ((querySelectP <|> P.between Tok.openP Tok.closeP queryP) `chainl1` (exceptP <|> unionP))
`chainl1` intersectP
order <- option id orderP
limit <- option id limitP
offset <- option id offsetP
return $ with $ limit $ offset $ order $ query
where
exceptP = do
r <- Tok.exceptP
return $ QueryExcept r Unused
unionP = do
r <- Tok.unionP
distinct <- option (Distinct True) distinctP
return $ QueryUnion r distinct Unused
intersectP = do
r <- Tok.intersectP
return $ QueryIntersect r Unused
withP = do
r <- Tok.withP
withs <- cteP `sepBy1` Tok.commaP
return $ \ query ->
let r' = sconcat $ r :| getInfo query : map cteInfo withs
in QueryWith r' withs query
cteP = do
(name, r) <- Tok.tableNameP
alias <- makeTableAlias r name
columns <- option []
$ P.between Tok.openP Tok.closeP $ columnAliasP `sepBy1` Tok.commaP
_ <- Tok.asP
(query, r') <- do
_ <- Tok.openP
q <- queryP
r' <- Tok.closeP
return (q, r')
return $ CTE (r <> r') alias columns query
orderP = do
(r, orders) <- orderTopLevelP
return $ \ query -> QueryOrder (getInfo query <> r) orders query
limitP = do
r <- Tok.limitP
choice
[ Tok.numberP >>= \ (v, r') ->
let limit = Limit (r <> r') v
in return $ \ query -> QueryLimit (getInfo query <> r') limit query
, Tok.nullP >> return id
]
offsetP = do
r <- Tok.offsetP
Tok.numberP >>= \ (v, r') ->
let offset = Offset (r <> r') v
in return $ \ query -> QueryOffset (getInfo query <> r') offset query
distinctP :: Parser Distinct
distinctP = choice $
[ Tok.allP >> return (Distinct False)
, Tok.distinctP >> return (Distinct True)
]
explainP :: Parser (Statement Vertica RawNames Range)
explainP = do
s <- Tok.explainP
stmt <- choice
[ InsertStmt <$> insertP
, DeleteStmt <$> deleteP
, QueryStmt <$> queryP
]
pure $ ExplainStmt (s <> getInfo stmt) stmt
columnAliasP :: Parser (ColumnAlias Range)
columnAliasP = do
(name, r) <- Tok.columnNameP
makeColumnAlias r name
alterTableP :: Parser (AlterTable RawNames Range)
alterTableP = do
s <- Tok.alterP
_ <- Tok.tableP
from <- tableNameP
_ <- Tok.renameP
_ <- Tok.toP
to <- (\ uqtn -> uqtn { tableNameSchema = Nothing }) <$> unqualifiedTableNameP
pure $ AlterTableRenameTable (s <> getInfo to) from to
createSchemaPrefixP :: Parser Range
createSchemaPrefixP = do
s <- Tok.createP
e <- Tok.schemaP
return $ s <> e
ifNotExistsP :: Parser (Maybe Range)
ifNotExistsP = optionMaybe $ do
s <- Tok.ifP
_ <- Tok.notP
e <- Tok.existsP
pure $ s <> e
ifExistsP :: Parser Range
ifExistsP = do
s <- Tok.ifP
e <- Tok.existsP
pure $ s <> e
createSchemaP :: Parser (CreateSchema RawNames Range)
createSchemaP = do
s <- createSchemaPrefixP
createSchemaIfNotExists <- ifNotExistsP
(name, r) <- Tok.schemaNameP
let createSchemaName = mkNormalSchema name r
e <- option r (Tok.authorizationP >> snd <$> Tok.userNameP)
let createSchemaInfo = s <> e
return $ CreateSchema{..}
createTableColumnsP :: Parser (TableDefinition Vertica RawNames Range)
createTableColumnsP = do
s <- Tok.openP
c:cs <- columnOrConstraintP `sepBy1` Tok.commaP
e <- Tok.closeP
pure $ TableColumns (s <> e) (c:|cs)
where
columnOrConstraintP :: Parser (ColumnOrConstraint Vertica RawNames Range)
columnOrConstraintP = choice
[ try $ ColumnOrConstraintColumn <$> columnDefinitionP
, ColumnOrConstraintConstraint <$> constraintDefinitionP
]
columnDefinitionP = do
(name, s) <- Tok.columnNameP
columnDefinitionType <- dataTypeP
updates <- many $ choice [ notNullUpdateP, nullUpdateP, defaultUpdateP ]
let columnDefinitionInfo = s <> getInfo columnDefinitionType
columnDefinitionExtra = Nothing
columnDefinitionNull = Nothing
columnDefinitionDefault = Nothing
columnDefinitionName = QColumnName s None name
foldr (>=>) pure updates ColumnDefinition{..}
notNullUpdateP :: Parser (ColumnDefinition d r Range -> Parser (ColumnDefinition d r Range))
notNullUpdateP = do
r <- (<>) <$> Tok.notP <*> Tok.nullP
pure $ \ d -> case columnDefinitionNull d of
Nothing -> pure $ d { columnDefinitionNull = Just $ NotNull r }
Just (Nullable _) -> fail "conflicting NULL/NOT NULL specifications on column"
Just (NotNull _) -> pure d
nullUpdateP :: Parser (ColumnDefinition d r Range -> Parser (ColumnDefinition d r Range))
nullUpdateP = do
r <- Tok.nullP
pure $ \ d -> case columnDefinitionNull d of
Nothing -> pure $ d { columnDefinitionNull = Just $ Nullable r }
Just (NotNull _) -> fail "conflicting NULL/NOT NULL specifications on column"
Just (Nullable _) -> pure d
defaultUpdateP :: Parser (ColumnDefinition d RawNames Range -> Parser (ColumnDefinition d RawNames Range))
defaultUpdateP = do
_ <- Tok.defaultP
expr <- exprP
pure $ \ d -> case columnDefinitionDefault d of
Nothing -> pure $ d { columnDefinitionDefault = Just expr }
Just _ -> fail "multiple defaults for column"
constraintDefinitionP :: Parser (ConstraintDefinition Range)
constraintDefinitionP = ConstraintDefinition <$> tableConstraintP
createExternalTablePrefixP :: Parser (Range, Externality Range)
createExternalTablePrefixP = do
s <- Tok.createP
r <- Tok.externalP
_ <- Tok.tableP
return (s, External r)
createExternalTableP :: Parser (CreateTable Vertica RawNames Range)
createExternalTableP = do
(s, createTableExternality) <- createExternalTablePrefixP
let createTablePersistence = Persistent
createTableIfNotExists <- ifNotExistsP
createTableName <- tableNameP
createTableDefinition <- createTableColumnsP
_ <- optional $ do
_ <- optional $ Tok.includeP <|> Tok.excludeP
_ <- Tok.schemaP
Tok.privilegesP
_ <- Tok.asP
e <- Tok.copyP
e' <- consumeOrderedOptions e $
[ ingestionColumnListP (getInfo <$> exprP)
, ingestionColumnOptionP
, fromP
, fileStorageFormatP
]
e'' <- consumeUnorderedOptions e' $
[ Tok.withP
, abortOnErrorP
, delimiterAsP
, enclosedByP
, Tok.enforceLengthP
, errorToleranceP
, escapeFormatP
, exceptionsOnNodeP
, fileFilterP
, nullAsP
, fileParserP
, recordTerminatorP
, rejectedDataOnNodeP
, rejectMaxP
, skipRecordsP
, skipBytesP
, fileSourceP
, trailingNullColsP
, trimByteP
]
let createTableInfo = s <> e''
createTableExtra = Nothing
pure CreateTable{..}
where
stringP :: Parser Range
stringP = snd <$> Tok.stringP
fromP :: Parser Range
fromP = do
s <- Tok.fromP
let fileP = do
r <- stringP
consumeOrderedOptions r [nodeLocationP, compressionP]
rs <- fileP `sepBy1` Tok.commaP
return $ s <> last rs
nodeLocationP = choice $
[ Tok.onP >> snd <$> Tok.nodeNameP
, Tok.onP >> Tok.anyP >> Tok.nodeP
]
createViewPrefixP :: Parser (Range, Maybe Range, Persistence Range)
createViewPrefixP = do
s <- Tok.createP
ifNotExists <- optionMaybe $ do
s' <- Tok.orP
e' <- Tok.replaceP
pure $ s' <> e'
persistence <- option Persistent $ Temporary <$> do
s' <- Tok.localP
e' <- Tok.temporaryP
pure $ s' <> e'
e <- Tok.viewP
pure (s <> e, ifNotExists, persistence)
schemaPrivilegesP :: Parser Range
schemaPrivilegesP = do
s <- choice [ Tok.includeP, Tok.excludeP ]
optional Tok.schemaP
e <- Tok.privilegesP
return $ s <> e
createViewP :: Parser (CreateView RawNames Range)
createViewP = do
(s, createViewIfNotExists, createViewPersistence) <- createViewPrefixP
createViewName <- tableNameP >>= \case
QTableName info Nothing view ->
case createViewPersistence of
Persistent -> pure $ QTableName info Nothing view
Temporary _ -> pure $ QTableName info (pure $ QSchemaName info Nothing "<session>" SessionSchema) view
qualifiedTableName ->
case createViewPersistence of
Persistent -> pure $ qualifiedTableName
Temporary _ -> fail $ "cannot specify schema on a local temporary view"
createViewColumns <- optionMaybe $ do
_ <- Tok.openP
c:cs <- unqualifiedColumnNameP `sepBy1` Tok.commaP
_ <- Tok.closeP
return (c:|cs)
case createViewPersistence of
Persistent -> optional schemaPrivilegesP
Temporary _ -> pure ()
_ <- Tok.asP
createViewQuery <- querySelectP
let createViewInfo = s <> getInfo createViewQuery
pure CreateView{..}
where
unqualifiedColumnNameP = do
(name, r) <- Tok.columnNameP
pure $ QColumnName r None name
createTableP :: Parser (CreateTable Vertica RawNames Range)
createTableP = do
s <- Tok.createP
(createTablePersistence, isLocal) <- option (Persistent, False) $ do
isLocal <- option False $ choice
[ Tok.localP >> pure True
, Tok.globalP >> pure False
]
createTablePersistence <- Temporary <$> Tok.temporaryP
pure (createTablePersistence, isLocal)
let createTableExternality = Internal
_ <- Tok.tableP
createTableIfNotExists <- ifNotExistsP
createTableName <- tableNameP >>= \case
QTableName info Nothing table ->
if isLocal
then pure $ QTableName info (pure $ QSchemaName info Nothing "<session>" SessionSchema) table
else pure $ QTableName info (pure $ QSchemaName info Nothing "public" NormalSchema) table
qualifiedTableName ->
if isLocal
then fail "cannot specify schema on a local temporary table"
else pure $ qualifiedTableName
let onCommitP = case createTablePersistence of
Persistent -> pure ()
Temporary _ -> do
_ <- Tok.onP
_ <- Tok.commitP
_ <- Tok.deleteP <|> Tok.preserveP
void Tok.rowsP
createTableDefinition <- choice
[ createTableColumnsP <* optional onCommitP <* optional schemaPrivilegesP
, try $ optional onCommitP *> optional schemaPrivilegesP *> createTableAsP
, optional schemaPrivilegesP *> createTableLikeP
]
createTableExtra <- tableInfoP
case createTablePersistence of
Persistent -> pure ()
Temporary _ -> optional $ do
_ <- Tok.noP
void Tok.projectionP
let e = maybe (getInfo createTableDefinition) getInfo createTableExtra
createTableInfo = s <> e
pure CreateTable{..}
where
columnListP :: Parser (NonEmpty (UQColumnName Range))
columnListP = do
_ <- Tok.openP
c:cs <- (`sepBy1` Tok.commaP) $ do
(name, r) <- Tok.columnNameP
pure $ QColumnName r None name
_ <- Tok.closeP
pure (c:|cs)
createTableLikeP = do
s <- Tok.likeP
table <- tableNameP
e <- option (getInfo table) $ do
_ <- Tok.includingP <|> Tok.excludingP
Tok.projectionsP
pure $ TableLike (s <> e) table
createTableAsP = do
s <- Tok.asP
columns <- optionMaybe $ try columnListP
query <- optionalParensP $ queryP
pure $ TableAs (s <> getInfo query) columns query
tableInfoP :: Parser (Maybe (TableInfo RawNames Range))
tableInfoP = do
mOrdering <- optionMaybe orderTopLevelP
let tableInfoOrdering = snd <$> mOrdering
let tableInfoEncoding :: Maybe (TableEncoding RawNames Range)
tableInfoEncoding = Nothing
tableInfoSegmentation <- optionMaybe $ choice
[ do
s <- Tok.unsegmentedP
choice
[ do
_ <- Tok.nodeP
node <- nodeNameP
let e = getInfo node
pure $ UnsegmentedOneNode (s <> e) node
, do
_ <- Tok.allP
e <- Tok.nodesP
pure $ UnsegmentedAllNodes (s <> e)
]
, do
s <- Tok.segmentedP
_ <- Tok.byP
expr <- exprP
list <- nodeListP
pure $ SegmentedBy (s <> getInfo list) expr list
]
tableInfoKSafety <- optionMaybe $ do
s <- Tok.ksafeP
choice
[ do
(n, e) <- integerP
pure $ KSafety (s <> e) (Just n)
, pure $ KSafety s Nothing
]
tableInfoPartitioning <- optionMaybe $ do
s <- Tok.partitionP
_ <- Tok.byP
expr <- exprP
pure $ Partitioning (s <> getInfo expr) expr
let infos = [ fst <$> mOrdering
, getInfo <$> tableInfoEncoding
, getInfo <$> tableInfoSegmentation
, getInfo <$> tableInfoKSafety
, getInfo <$> tableInfoPartitioning
]
case getOption $ mconcat $ map Option infos of
Nothing -> pure Nothing
Just tableInfoInfo -> pure $ Just TableInfo{..}
dropViewPrefixP :: Parser Range
dropViewPrefixP = do
s <- Tok.dropP
e <- Tok.viewP
pure $ s <> e
dropViewP :: Parser (DropView RawNames Range)
dropViewP = do
s <- dropViewPrefixP
dropViewIfExists <- optionMaybe ifExistsP
dropViewName <- tableNameP
let dropViewInfo = s <> getInfo dropViewName
pure DropView{..}
dropTableP :: Parser (DropTable RawNames Range)
dropTableP = do
s <- Tok.dropP
_ <- Tok.tableP
dropTableIfExists <- optionMaybe ifExistsP
(dropTableName:rest) <- tableNameP `sepBy1` Tok.commaP
cascade <- optionMaybe Tok.cascadeP
let dropTableNames = dropTableName :| rest
dropTableInfo = s <> (fromMaybe (getInfo $ NE.last dropTableNames) cascade)
pure DropTable{..}
grantP :: Parser (Grant Range)
grantP = do
s <- Tok.grantP
e <- many1 Tok.notSemicolonP
return $ Grant (s <> (last e))
revokeP :: Parser (Revoke Range)
revokeP = do
s <- Tok.revokeP
e <- many1 Tok.notSemicolonP
return $ Revoke (s <> (last e))
beginP :: Parser Range
beginP = do
s <- choice [ do
s <- Tok.beginP
e <- option s (Tok.workP <|> Tok.transactionP)
return $ s <> e
, do
s <- Tok.startP
e <- Tok.transactionP
return $ s <> e
]
e <- consumeOrderedOptions s [isolationLevelP, transactionModeP]
return $ s <> e
where
isolationLevelP :: Parser Range
isolationLevelP = do
s <- Tok.isolationP
_ <- Tok.levelP
e <- choice [ Tok.serializableP
, Tok.repeatableP >> Tok.readP
, Tok.readP >> (Tok.committedP <|> Tok.uncommittedP)
]
return $ s <> e
transactionModeP :: Parser Range
transactionModeP = do
s <- Tok.readP
e <- Tok.onlyP <|> Tok.writeP
return $ s <> e
commitP :: Parser Range
commitP = do
s <- Tok.commitP <|> Tok.endP
e <- option s (Tok.workP <|> Tok.transactionP)
return $ s <> e
rollbackP :: Parser Range
rollbackP = do
s <- Tok.rollbackP <|> Tok.abortP
e <- option s (Tok.workP <|> Tok.transactionP)
return $ s <> e
nodeListP :: Parser (NodeList Range)
nodeListP = choice
[ do
s <- Tok.allP
e <- Tok.nodesP
offset <- optionMaybe nodeListOffsetP
let e' = maybe e getInfo offset
pure $ AllNodes (s <> e') offset
, do
s <- Tok.nodesP
n:ns <- nodeNameP `sepBy1` Tok.commaP
let e = getInfo $ last (n:ns)
pure $ Nodes (s <> e) (n:|ns)
]
nodeListOffsetP :: Parser (NodeListOffset Range)
nodeListOffsetP = do
s <- Tok.offsetP
(n, e) <- integerP
pure $ NodeListOffset (s <> e) n
nodeNameP :: Parser (Node Range)
nodeNameP = do
(node, e) <- Tok.nodeNameP
pure $ Node e node
integerP :: Parser (Int, Range)
integerP = do
(n, e) <- Tok.numberP
case reads $ TL.unpack n of
[(n', "")] -> pure (n', e)
_ -> fail $ unwords ["unable to parse", show n, "as integer"]
selectP :: Parser (Select RawNames Range)
selectP = do
r <- Tok.selectP
selectDistinct <- option notDistinct distinctP
selectCols <- do
selections <- selectionP `sepBy1` Tok.commaP
let r' = foldl1 (<>) $ map getInfo selections
return $ SelectColumns r' selections
selectFrom <- optionMaybe fromP
selectWhere <- optionMaybe whereP
selectTimeseries <- optionMaybe timeseriesP
selectGroup <- optionMaybe groupP
selectHaving <- optionMaybe havingP
selectNamedWindow <- optionMaybe namedWindowP
let (Just selectInfo) = sconcat $ Just r :|
[ Just $ getInfo selectCols
, getInfo <$> selectFrom
, getInfo <$> selectWhere
, getInfo <$> selectTimeseries
, getInfo <$> selectGroup
, getInfo <$> selectHaving
, getInfo <$> selectNamedWindow
]
return Select{..}
where
fromP = do
r <- Tok.fromP
tablishes <- tablishP `sepBy1` Tok.commaP
let r' = foldl (<>) r $ fmap getInfo tablishes
return $ SelectFrom r' tablishes
whereP = do
r <- Tok.whereP
condition <- exprP
return $ SelectWhere (r <> getInfo condition) condition
timeseriesP = do
s <- Tok.timeseriesP
selectTimeseriesSliceName <- columnAliasP
_ <- Tok.asP
selectTimeseriesInterval <- do
(c, r) <- Tok.stringP
pure $ StringConstant r c
_ <- Tok.overP
_ <- Tok.openP
selectTimeseriesPartition <- optionMaybe partitionP
selectTimeseriesOrder <- do
_ <- Tok.orderP
_ <- Tok.byP
exprP
e <- Tok.closeP
let selectTimeseriesInfo = s <> e
pure $ SelectTimeseries {..}
toGroupingElement :: PositionOrExpr RawNames Range -> GroupingElement RawNames Range
toGroupingElement posOrExpr = GroupingElementExpr (getInfo posOrExpr) posOrExpr
groupP = do
r <- Tok.groupP
_ <- Tok.byP
exprs <- exprP `sepBy1` Tok.commaP
let selectGroupGroupingElements = map (toGroupingElement . handlePositionalReferences) exprs
selectGroupInfo = foldl (<>) r $ fmap getInfo selectGroupGroupingElements
return SelectGroup{..}
havingP = do
r <- Tok.havingP
conditions <- exprP `sepBy1` Tok.commaP
let r' = foldl (<>) r $ fmap getInfo conditions
return $ SelectHaving r' conditions
namedWindowP =
do
r <- Tok.windowP
windows <- (flip sepBy1) Tok.commaP $ do
name <- windowNameP
_ <- Tok.asP
_ <- Tok.openP
window <- choice
[ do
partition@(Just p) <- Just <$> partitionP
order <- option [] orderInWindowClauseP
let orderInfos = map getInfo order
info = L.foldl' (<>) (getInfo p) orderInfos
return $ Left $ WindowExpr info partition order Nothing
, do
inherit <- windowNameP
order <- option [] orderInWindowClauseP
let orderInfo = map getInfo order
info = L.foldl' (<>) (getInfo inherit) orderInfo
return $ Right $ PartialWindowExpr info inherit Nothing order Nothing
]
e <- Tok.closeP
let info = getInfo name <> e
return $ case window of
Left w -> NamedWindowExpr info name w
Right pw -> NamedPartialWindowExpr info name pw
let info = L.foldl' (<>) r $ fmap getInfo windows
return $ SelectNamedWindow info windows
handlePositionalReferences :: Expr RawNames Range -> PositionOrExpr RawNames Range
handlePositionalReferences e = case e of
ConstantExpr _ (NumericConstant _ n) | TL.all isDigit n -> PositionOrExprPosition (getInfo e) (read $ TL.unpack n) Unused
_ -> PositionOrExprExpr e
selectStarP :: Parser (Selection RawNames Range)
selectStarP = choice
[ do
r <- Tok.starP
return $ SelectStar r Nothing Unused
, try $ do
(t, r) <- Tok.tableNameP
_ <- Tok.dotP
r' <- Tok.starP
return $ SelectStar (r <> r') (Just $ QTableName r Nothing t) Unused
, try $ do
(s, t, r, r') <- qualifiedTableNameP
_ <- Tok.dotP
r'' <- Tok.starP
return $ SelectStar (r <> r'')
(Just $ QTableName r' (Just $ mkNormalSchema s r) t) Unused
]
selectionP :: Parser (Selection RawNames Range)
selectionP = try selectStarP <|> do
expr <- exprP
alias <- aliasP expr
return $ SelectExpr (getInfo alias <> getInfo expr) [alias] expr
makeColumnAlias :: Range -> Text -> Parser (ColumnAlias Range)
makeColumnAlias r alias = ColumnAlias r alias . ColumnAliasId <$> getNextCounter
makeTableAlias :: Range -> Text -> Parser (TableAlias Range)
makeTableAlias r alias = TableAlias r alias . TableAliasId <$> getNextCounter
makeDummyAlias :: Range -> Parser (ColumnAlias Range)
makeDummyAlias r = makeColumnAlias r "?column?"
makeExprAlias :: Expr RawNames Range -> Parser (ColumnAlias Range)
makeExprAlias (BinOpExpr info _ _ _) = makeDummyAlias info
makeExprAlias (UnOpExpr info _ _) = makeDummyAlias info
makeExprAlias (LikeExpr info _ _ _ _) = makeDummyAlias info
makeExprAlias (CaseExpr info _ _) = makeDummyAlias info
makeExprAlias (ColumnExpr info (QColumnName _ _ name)) = makeColumnAlias info name
makeExprAlias (ConstantExpr info _) = makeDummyAlias info
makeExprAlias (InListExpr info _ _) = makeDummyAlias info
makeExprAlias (InSubqueryExpr info _ _) = makeDummyAlias info
makeExprAlias (BetweenExpr info _ _ _) = makeDummyAlias info
makeExprAlias (OverlapsExpr info _ _) = makeDummyAlias info
makeExprAlias (AtTimeZoneExpr info _ _) = makeColumnAlias info "timezone"
makeExprAlias (FunctionExpr info (QFunctionName _ _ name) _ _ _ _ _) = makeColumnAlias info name
makeExprAlias (SubqueryExpr info _) = makeDummyAlias info
makeExprAlias (ArrayExpr info _) = makeDummyAlias info
makeExprAlias (ExistsExpr info _) = makeDummyAlias info
makeExprAlias (FieldAccessExpr _ _ _) = fail "Unsupported struct access in Vertica: unused datatype in this dialect"
makeExprAlias (ArrayAccessExpr _ _ _) = fail "Unsupported array access in Vertica: unused datatype in this dialect"
makeExprAlias (TypeCastExpr _ _ expr _) = makeExprAlias expr
makeExprAlias (VariableSubstitutionExpr _) = fail "Unsupported variable substitution in Vertica: unused datatype in this dialect"
aliasP :: Expr RawNames Range -> Parser (ColumnAlias Range)
aliasP expr = choice
[ try $ do
optional Tok.asP
(name, r) <- choice
[ Tok.columnNameP
, first TL.decodeUtf8 <$> Tok.stringP
]
makeColumnAlias r name
, do
_ <- Tok.asP
_ <- P.between Tok.openP Tok.closeP $ Tok.columnNameP `sepBy1` Tok.commaP
makeExprAlias expr
, makeExprAlias expr
]
exprP :: Parser (Expr RawNames Range)
exprP = orExprP
parenExprP :: Parser (Expr RawNames Range)
parenExprP = P.between Tok.openP Tok.closeP $ choice
[ try subqueryExprP
, exprP
]
subqueryExprP :: Parser (Expr RawNames Range)
subqueryExprP = do
query <- queryP
return $ SubqueryExpr (getInfo query) query
caseExprP :: Parser (Expr RawNames Range)
caseExprP = do
r <- Tok.caseP
whens <- choice
[ P.many1 $ do
_ <- Tok.whenP
condition <- exprP
_ <- Tok.thenP
result <- exprP
return (condition, result)
, do
expr <- exprP
P.many1 $ do
whenr <- Tok.whenP
nullseq <- optionMaybe Tok.nullsequalP
condition <- case nullseq of
Nothing -> BinOpExpr whenr "=" expr <$> exprP
Just nullseqr -> BinOpExpr (whenr <> nullseqr) "<=>" expr <$> exprP
_ <- Tok.thenP
result <- exprP
return (condition, result)
]
melse <- optionMaybe $ do
_ <- Tok.elseP
exprP
r' <- Tok.endP
return $ CaseExpr (r <> r') whens melse
fieldTypeP :: Parser (Expr RawNames Range)
fieldTypeP = do
(ftype, r) <- Tok.fieldTypeP
return $ ConstantExpr r $ StringConstant r $ TL.encodeUtf8 ftype
functionExprP :: Parser (Expr RawNames Range)
functionExprP = choice
[ castFuncP
, dateDiffFuncP
, extractFuncP
, try regularFuncP
, bareFuncP
]
where
castFuncP = do
r <- Tok.castP
_ <- Tok.openP
e <- exprP
_ <- Tok.asP
t <- choice
[ try $ do
i <- Tok.intervalP
(unit, u) <- Tok.datePartP
pure $ PrimitiveDataType (i <> u) ("INTERVAL " <> TL.toUpper unit) []
, dataTypeP
]
r' <- Tok.closeP
return $ TypeCastExpr (r <> r') CastFailureError e t
dateDiffFuncP = do
r <- Tok.dateDiffP
_ <- Tok.openP
datepart <- choice
[ do
_ <- Tok.openP
expr <- exprP
_ <- Tok.closeP
pure expr
, do
(string, r') <- Tok.stringP
pure $ ConstantExpr r' $ StringConstant r' string
, do
(string, r') <- Tok.datePartP
pure $ ConstantExpr r' $ StringConstant r' $ TL.encodeUtf8 string
]
_ <- Tok.commaP
startExp <- exprP
_ <- Tok.commaP
endExp <- exprP
r' <- Tok.closeP
return $ FunctionExpr (r <> r') (QFunctionName r Nothing "datediff") notDistinct [datepart, startExp, endExp] [] Nothing Nothing
extractFuncP = do
r <- Tok.extractP
_ <- Tok.openP
ftype <- fieldTypeP
_ <- Tok.fromP
expr <- exprP
r' <- Tok.closeP
return $ FunctionExpr (r <> r') (QFunctionName r Nothing "extract") notDistinct [ftype, expr] [] Nothing Nothing
regularFuncP = do
name <- choice
[ try $ do
(s, r) <- Tok.schemaNameP
_ <- Tok.dotP
(f, r') <- Tok.functionNameP
return $ QFunctionName (r <> r') (Just $ mkNormalSchema s r) f
, do
(f, r) <- Tok.functionNameP
return $ QFunctionName r Nothing f
]
(distinct, arguments, parameters, r') <- do
_ <- Tok.openP
(distinct, arguments) <- choice
[ case name of
QFunctionName _ Nothing "count" -> do
r' <- Tok.starP
return ( notDistinct
, [ConstantExpr r' $ NumericConstant r' "1"]
)
QFunctionName _ Nothing "substring" -> do
arg1 <- exprP
word <- (const True <$> Tok.fromP)
<|> (const False <$> Tok.commaP)
arg2 <- exprP
arg3 <- optionMaybe $ do
_ <- if word then Tok.forP else Tok.commaP
exprP
return ( notDistinct
, arg1 : arg2 : maybe [] pure arg3
)
_ -> fail "no special case for function"
, do
isDistinct <- distinctP
(isDistinct,) . (:[]) <$> exprP
, (notDistinct,) <$> exprP `sepBy` Tok.commaP
]
parameters <- option [] $ do
_ <- Tok.usingP
_ <- Tok.parametersP
flip sepBy1 Tok.commaP $ do
(param, paramr) <- Tok.paramNameP
_ <- Tok.equalP
expr <- exprP
pure (ParamName paramr param, expr)
optional $ Tok.ignoreP >> Tok.nullsP
r' <- Tok.closeP
return (distinct, arguments, parameters, r')
over <- optionMaybe $ try $ overP
let r'' = maybe r' getInfo over <> getInfo name
return $ FunctionExpr r'' name distinct arguments parameters Nothing over
bareFuncP = do
(v, r) <- choice
[ Tok.currentDatabaseP
, Tok.currentSchemaP
, Tok.userP
, Tok.currentUserP
, Tok.sessionUserP
, Tok.currentDateP
, Tok.currentTimeP
, Tok.currentTimestampP
, Tok.localTimeP
, Tok.localTimestampP
, Tok.sysDateP
]
pure $ FunctionExpr r (QFunctionName r Nothing v) notDistinct [] [] Nothing Nothing
orderTopLevelP :: Parser (Range, [Order RawNames Range])
orderTopLevelP = orderExprP False True
orderInWindowClauseP :: Parser [Order RawNames Range]
orderInWindowClauseP = snd <$> orderExprP True False
orderExprP :: Bool -> Bool -> Parser (Range, [Order RawNames Range])
orderExprP nullsClausePermitted positionalReferencesPermitted = do
r <- Tok.orderP
_ <- Tok.byP
orders <- helperP `sepBy1` Tok.commaP
let r' = getInfo $ last orders
return (r <> r', orders)
where
helperP :: Parser (Order RawNames Range)
helperP = do
expr <- exprP
let posOrExpr = if positionalReferencesPermitted
then handlePositionalReferences expr
else PositionOrExprExpr expr
dir <- directionP
nulls <- case (nullsClausePermitted, dir) of
(False, _) -> return $ NullsAuto Nothing
(True, OrderAsc _) -> option (NullsLast Nothing) nullsP
(True, OrderDesc _) -> option (NullsFirst Nothing) nullsP
let info = getInfo expr ?<> getInfo dir <> getInfo nulls
return $ Order info posOrExpr dir nulls
directionP :: Parser (OrderDirection (Maybe Range))
directionP = option (OrderAsc Nothing) $ choice
[ OrderAsc . Just <$> Tok.ascP
, OrderDesc . Just <$> Tok.descP
]
nullsP :: Parser (NullPosition (Maybe Range))
nullsP = do
r <- Tok.nullsP
choice
[ Tok.firstP >>= \ r' -> return $ NullsFirst $ Just $ r <> r'
, Tok.lastP >>= \ r' -> return $ NullsLast $ Just $ r <> r'
, Tok.autoP >>= \ r' -> return $ NullsAuto $ Just $ r <> r'
]
frameP :: Parser (Frame Range)
frameP = do
ftype <- choice
[ RowFrame <$> Tok.rowsP
, RangeFrame <$> Tok.rangeP
]
choice
[ do
_ <- Tok.betweenP
start <- frameBoundP
_ <- Tok.andP
end <- frameBoundP
let r = getInfo ftype <> getInfo end
return $ Frame r ftype start (Just end)
, do
start <- frameBoundP
let r = getInfo ftype <> getInfo start
return $ Frame r ftype start Nothing
]
frameBoundP :: Parser (FrameBound Range)
frameBoundP = choice
[ fmap Unbounded $ (<>)
<$> Tok.unboundedP
<*> choice [ Tok.precedingP, Tok.followingP ]
, fmap CurrentRow $ (<>) <$> Tok.currentP <*> Tok.rowP
, constantP >>= \ expr -> choice
[ Tok.precedingP >>= \ r ->
return $ Preceding (getInfo expr <> r) expr
, Tok.followingP >>= \ r ->
return $ Following (getInfo expr <> r) expr
]
]
overP :: Parser (OverSubExpr RawNames Range)
overP = do
start <- Tok.overP
subExpr <- choice
[ Left <$> windowP
, Right <$> windowNameP
]
return $ case subExpr of
Left w -> mergeWindowInfo start w
Right wn -> OverWindowName (start <> getInfo wn) wn
where
windowP :: Parser (OverSubExpr RawNames Range)
windowP = do
start' <- Tok.openP
expr <- choice
[ Left <$> windowExprP start'
, Right <$> partialWindowExprP start'
]
return $ case expr of
Left w -> OverWindowExpr (start' <> getInfo w) w
Right pw -> OverPartialWindowExpr (start' <> getInfo pw) pw
mergeWindowInfo :: Range -> OverSubExpr RawNames Range -> OverSubExpr RawNames Range
mergeWindowInfo r = \case
OverWindowExpr r' WindowExpr{..} ->
OverWindowExpr (r <> r') $ WindowExpr { windowExprInfo = windowExprInfo <> r , ..}
OverWindowName r' n -> OverWindowName (r <> r') n
OverPartialWindowExpr r' PartialWindowExpr{..} ->
OverPartialWindowExpr (r <> r') $ PartialWindowExpr { partWindowExprInfo = partWindowExprInfo <> r , ..}
windowExprP :: Range -> Parser (WindowExpr RawNames Range)
windowExprP start =
do
partition <- optionMaybe partitionP
order <- option [] orderInWindowClauseP
frame <- optionMaybe frameP
end <- Tok.closeP
let info = start <> end
return (WindowExpr info partition order frame)
partialWindowExprP :: Range -> Parser (PartialWindowExpr RawNames Range)
partialWindowExprP start =
do
inherit <- windowNameP
order <- option [] orderInWindowClauseP
frame <- optionMaybe frameP
end <- Tok.closeP
let info = start <> end
return (PartialWindowExpr info inherit Nothing order frame)
windowNameP :: Parser (WindowName Range)
windowNameP =
do
(name, r) <- Tok.windowNameP
return $ WindowName r name
partitionP :: Parser (Partition RawNames Range)
partitionP = do
r <- Tok.partitionP
choice
[ Tok.byP >> (exprP `sepBy1` Tok.commaP) >>= \ exprs ->
return $ PartitionBy
(sconcat $ r :| map getInfo exprs) exprs
, Tok.bestP >>= \ r' -> return $ PartitionBest (r <> r')
, Tok.nodesP >>= \ r' -> return $ PartitionNodes (r <> r')
]
existsExprP :: Parser (Expr RawNames Range)
existsExprP = do
r <- Tok.existsP
_ <- Tok.openP
query <- queryP
r' <- Tok.closeP
return $ ExistsExpr (r <> r') query
arrayExprP :: Parser (Expr RawNames Range)
arrayExprP = do
s <- Tok.arrayP
_ <- Tok.openBracketP
cs <- exprP `sepBy` Tok.commaP
e <- Tok.closeBracketP
pure $ ArrayExpr (s <> e) cs
castExprP :: Parser (Expr RawNames Range)
castExprP = foldl (flip ($)) <$> castedP <*> many castP
where
castedP :: Parser (Expr RawNames Range)
castedP = choice
[ try parenExprP
, try existsExprP
, try arrayExprP
, try functionExprP
, caseExprP
, try $ do
constant <- constantP
return $ ConstantExpr (getInfo constant) constant
, do
name <- columnNameP
return $ ColumnExpr (getInfo name) name
]
castP :: Parser (Expr RawNames Range -> Expr RawNames Range)
castP = do
_ <- Tok.castOpP
typeName <- dataTypeP
let r expr = getInfo expr <> getInfo typeName
return (\ expr -> TypeCastExpr (r expr) CastFailureError expr typeName)
atTimeZoneExprP :: Parser (Expr RawNames Range)
atTimeZoneExprP = foldl (flip ($)) <$> castExprP <*> many atTimeZoneP
where
atTimeZoneP :: Parser (Expr RawNames Range -> Expr RawNames Range)
atTimeZoneP = do
_ <- Tok.atP
_ <- Tok.timezoneP
tz <- castExprP
return $ \ expr ->
AtTimeZoneExpr (getInfo expr <> getInfo tz) expr tz
unOpP :: Text -> Parser (Expr RawNames Range -> Expr RawNames Range)
unOpP op = do
r <- Tok.symbolP op
return $ \ expr -> UnOpExpr (r <> getInfo expr) (Operator op) expr
negateExprP :: Parser (Expr RawNames Range)
negateExprP = do
neg <- option id $ choice $ map unOpP [ "+", "-", "@" ]
expr <- atTimeZoneExprP
return $ neg expr
binOpP :: Text -> Parser (Expr RawNames Range -> Expr RawNames Range -> Expr RawNames Range)
binOpP op = do
r <- Tok.symbolP op
let r' lhs rhs = sconcat $ r :| map getInfo [lhs, rhs]
return $ \ lhs rhs -> BinOpExpr (r' lhs rhs) (Operator op) lhs rhs
exponentExprP :: Parser (Expr RawNames Range)
exponentExprP = negateExprP `chainl1` binOpP "^"
productExprP :: Parser (Expr RawNames Range)
productExprP = exponentExprP `chainl1` opP
where
opP = choice $ map binOpP [ "*", "//", "/", "%" ]
sumExprP :: Parser (Expr RawNames Range)
sumExprP = productExprP `chainl1` opP
where
opP = choice $ map binOpP [ "+", "-" ]
notP :: Parser (Expr RawNames Range -> Expr RawNames Range)
notP = (\ r -> UnOpExpr r "NOT") <$> Tok.notP
isExprP :: Parser (Expr RawNames Range)
isExprP = do
expr <- sumExprP
is <- fmap (foldl (.) id) $ many $ choice
[ do
_ <- Tok.isP
not_ <- option id notP
(not_ .) <$> choice
[ Tok.trueP >>= \ r -> return (UnOpExpr r "ISTRUE")
, Tok.falseP >>= \ r -> return (UnOpExpr r "ISFALSE")
, Tok.nullP >>= \ r -> return (UnOpExpr r "ISNULL")
, Tok.unknownP >>= \ r -> return (UnOpExpr r "ISUNKNOWN")
]
, Tok.isnullP >>= \ r -> return (UnOpExpr r "ISNULL")
, Tok.notnullP >>= \ r -> return (UnOpExpr r "NOT" . UnOpExpr r "ISNULL")
]
return $ is expr
appendExprP :: Parser (Expr RawNames Range)
appendExprP = isExprP `chainl1` binOpP "||"
inExprP :: Parser (Expr RawNames Range)
inExprP = do
expr <- appendExprP
not_ <- option id notP
in_ <- foldl (.) id <$> many inP
return $ not_ $ in_ expr
where
inP = do
_ <- Tok.inP
_ <- Tok.openP
list <- choice
[ Left <$> queryP
, Right <$> exprP `sepBy1` Tok.commaP
]
r <- Tok.closeP
return $ case list of
Left query ->
\ expr -> InSubqueryExpr (getInfo expr <> r) query expr
Right constants ->
\ expr -> InListExpr (getInfo expr <> r) constants expr
betweenExprP :: Parser (Expr RawNames Range)
betweenExprP = do
expr <- inExprP
between <- foldl (.) id <$> many betweenP
return $ between expr
where
betweenP = do
_ <- Tok.betweenP
start <- sumExprP
_ <- Tok.andP
end <- sumExprP
let r expr = getInfo expr <> getInfo end
return $ \ expr -> BetweenExpr (r expr) start end expr
overlapsExprP :: Parser (Expr RawNames Range)
overlapsExprP = try overlapsP <|> betweenExprP
where
overlapsP = do
let pair :: Parser a -> Parser ((a, a), Range)
pair p = do
r <- Tok.openP
s <- p
_ <- Tok.commaP
e <- p
r' <- Tok.closeP
return ((s, e), r <> r')
(lhs, r) <- pair exprP
_ <- Tok.overlapsP
(rhs, r') <- pair exprP
return $ OverlapsExpr (r <> r') lhs rhs
likeExprP :: Parser (Expr RawNames Range)
likeExprP = do
expr <- overlapsExprP
like <- option id comparisonP
return $ like expr
where
comparisonP :: Parser (Expr RawNames Range -> Expr RawNames Range)
comparisonP = choice
[ do
comparison <- symbolComparisonP
pattern <- Pattern <$> overlapsExprP
return $ comparison pattern
, do
comparison <- textComparisonP
pattern <- Pattern <$> overlapsExprP
escape <- optionMaybe $ do
_ <- Tok.escapeP
Escape <$> exprP
return $ comparison escape pattern
]
symbolComparisonP :: Parser (Pattern RawNames Range -> Expr RawNames Range -> Expr RawNames Range)
symbolComparisonP = choice $
let r expr pattern = getInfo expr <> getInfo pattern
in [ do
_ <- Tok.likeOpP
return $ \ pattern expr -> LikeExpr (r pattern expr) "LIKE" Nothing pattern expr
, do
_ <- Tok.iLikeOpP
return $ \ pattern expr -> LikeExpr (r pattern expr) "ILIKE" Nothing pattern expr
, do
_ <- Tok.notLikeOpP
return $ \ pattern expr ->
UnOpExpr (r pattern expr) "NOT" $ LikeExpr (r pattern expr) "LIKE" Nothing pattern expr
, do
_ <- Tok.notILikeOpP
return $ \ pattern expr ->
UnOpExpr (r pattern expr) "NOT" $ LikeExpr (r pattern expr) "ILIKE" Nothing pattern expr
, do
_ <- Tok.regexMatchesP
return $ \ pattern expr ->
BinOpExpr (r pattern expr) "REGEX MATCHES" expr $ patternExpr pattern
, do
_ <- Tok.regexIgnoreCaseMatchesP
return $ \ pattern expr ->
BinOpExpr (r pattern expr) "REGEX IGNORE-CASE MATCHES" expr $ patternExpr pattern
, do
_ <- Tok.notRegexMatchesP
return $ \ pattern expr ->
UnOpExpr (r pattern expr) "NOT" $
BinOpExpr (r pattern expr) "REGEX MATCHES" expr $ patternExpr pattern
, do
_ <- Tok.notRegexIgnoreCaseMatchesP
return $ \ pattern expr ->
UnOpExpr (r pattern expr) "NOT" $
BinOpExpr (r pattern expr) "REGEX IGNORE-CASE MATCHES" expr $ patternExpr pattern
]
textComparisonP :: Parser (Maybe (Escape RawNames Range) -> Pattern RawNames Range -> Expr RawNames Range -> Expr RawNames Range)
textComparisonP = do
not_ <- option id notP
like <- choice
[ Tok.likeP >>= \ r -> return $ LikeExpr r "LIKE"
, Tok.iLikeP >>= \ r -> return $ LikeExpr r "ILIKE"
, Tok.likeBP >>= \ r -> return $ LikeExpr r "LIKE"
, Tok.iLikeBP >>= \ r -> return $ LikeExpr r "ILIKE"
]
return $ \ escape pattern expr -> not_ $ like escape pattern expr
mkBinOp :: (Text, a) -> Expr r a -> Expr r a -> Expr r a
mkBinOp (op, r) = BinOpExpr r (Operator op)
inequalityExprP :: Parser (Expr RawNames Range)
inequalityExprP = likeExprP `chainl1` (mkBinOp <$> Tok.inequalityOpP)
equalityExprP :: Parser (Expr RawNames Range)
equalityExprP = inequalityExprP `chainl1` (mkBinOp <$> Tok.equalityOpP)
notExprP :: Parser (Expr RawNames Range)
notExprP = do
nots <- appEndo . fold . reverse . map Endo <$> many notP
expr <- equalityExprP
return $ nots expr
andExprP :: Parser (Expr RawNames Range)
andExprP = notExprP `chainl1`
(Tok.andP >>= \ r -> return $ BinOpExpr r "AND")
orExprP :: Parser (Expr RawNames Range)
orExprP = andExprP `chainl1` (Tok.orP >>= \ r -> return (BinOpExpr r "OR"))
singleTableP :: Parser (Tablish RawNames Range)
singleTableP = try subqueryP <|> try tableP <|> parenthesizedJoinP
where
subqueryP = do
r <- Tok.openP
query <- queryP
_ <- Tok.closeP
optional Tok.asP
(name, r') <- Tok.tableNameP
alias <- makeTableAlias r' name
return $ TablishSubQuery (r <> r')
(TablishAliasesT alias)
query
tableP = do
name <- tableNameP
maybe_alias <- optionMaybe $ do
optional Tok.asP
(alias, r) <- Tok.tableNameP
makeTableAlias r alias
let r = case maybe_alias of
Nothing -> getInfo name
Just alias -> getInfo alias <> getInfo name
aliases = maybe TablishAliasesNone TablishAliasesT maybe_alias
return $ TablishTable r aliases name
parenthesizedJoinP = do
tablish <- P.between Tok.openP Tok.closeP $ do
table <- singleTableP
joins <- fmap (appEndo . fold . reverse) $ many1 $ Endo <$> joinP
return $ joins table
optional $ do
optional Tok.asP
void Tok.tableNameP
pure tablish
optionalParensP :: Parser a -> Parser a
optionalParensP p = try p <|> P.between Tok.openP Tok.closeP p
manyParensP :: Parser a -> Parser a
manyParensP p = try p <|> P.between Tok.openP Tok.closeP (manyParensP p)
tablishP :: Parser (Tablish RawNames Range)
tablishP = do
table <- singleTableP
joins <- fmap (appEndo . fold . reverse) $ many $ Endo <$> joinP
return $ joins table
joinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
joinP = regularJoinP <|> naturalJoinP <|> crossJoinP
regularJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
regularJoinP = do
maybeJoinType <- optionMaybe $ innerJoinTypeP <|> outerJoinTypeP
joinType <- Tok.joinP >>= \ r -> return $ case maybeJoinType of
Nothing -> JoinInner r
Just joinType -> (<> r) <$> joinType
rhs <- singleTableP
condition <- choice
[ do
_ <- Tok.onP <?> "condition in join clause"
JoinOn <$> exprP
, do
s <- Tok.usingP <?> "using in join clause"
_ <- Tok.openP
names <- flip sepBy1 Tok.commaP $ do
(name, r) <- Tok.columnNameP
pure $ QColumnName r None name
e <- Tok.closeP
return $ JoinUsing (s <> e) names
]
let r lhs = getInfo lhs <> getInfo rhs <> getInfo condition
return $ \ lhs ->
TablishJoin (r lhs) joinType condition lhs rhs
outerJoinTypeP :: Parser (JoinType Range)
outerJoinTypeP = do
joinType <- choice
[ Tok.leftP >>= \ r -> return $ JoinLeft r
, Tok.rightP >>= \ r -> return $ JoinRight r
, Tok.fullP >>= \ r -> return $ JoinFull r
]
optional Tok.outerP
return joinType
innerJoinTypeP :: Parser (JoinType Range)
innerJoinTypeP = Tok.innerP >>= \ r -> return $ JoinInner r
naturalJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
naturalJoinP = do
r <- Tok.naturalP
maybeJoinType <- optionMaybe $ innerJoinTypeP <|> outerJoinTypeP
joinType <- Tok.joinP >>= \ r' -> return $ case maybeJoinType of
Nothing -> JoinInner r
Just joinType -> (const $ r <> r') <$> joinType
rhs <- singleTableP
let r' lhs = getInfo lhs <> getInfo rhs
return $ \ lhs -> TablishJoin (r' lhs) joinType (JoinNatural r Unused) lhs rhs
crossJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
crossJoinP = do
r <- Tok.crossP
r'<- Tok.joinP
rhs <- singleTableP
let r'' lhs = getInfo lhs <> getInfo rhs
joinInfo = r <> r'
true' = JoinOn $ ConstantExpr joinInfo $ BooleanConstant joinInfo True
return $ \ lhs ->
TablishJoin (r'' lhs) (JoinInner joinInfo) true' lhs rhs
createProjectionPrefixP :: Parser Range
createProjectionPrefixP = do
s <- Tok.createP
e <- Tok.projectionP
pure $ s <> e
createProjectionP :: Parser (CreateProjection RawNames Range)
createProjectionP = do
s <- createProjectionPrefixP
createProjectionIfNotExists <- ifNotExistsP
createProjectionName <- projectionNameP
createProjectionColumns <- optionMaybe $ try columnListP
_ <- Tok.asP
createProjectionQuery <- queryP
createProjectionSegmentation <- optionMaybe $ choice
[ do
s' <- Tok.unsegmentedP
choice
[ do
_ <- Tok.nodeP
node <- nodeNameP
let e' = getInfo node
pure $ UnsegmentedOneNode (s' <> e') node
, do
_ <- Tok.allP
e' <- Tok.nodesP
pure $ UnsegmentedAllNodes (s' <> e')
]
, do
s' <- Tok.segmentedP
_ <- Tok.byP
expr <- exprP
list <- nodeListP
pure $ SegmentedBy (s' <> getInfo list) expr list
]
createProjectionKSafety <- optionMaybe $ do
s' <- Tok.ksafeP
choice
[ do
(n, e') <- integerP
pure $ KSafety (s' <> e') (Just n)
, pure $ KSafety s' Nothing
]
let createProjectionInfo =
sconcat $ s :| catMaybes [ Just $ getInfo createProjectionQuery
, getInfo <$> createProjectionSegmentation
, getInfo <$> createProjectionKSafety
]
pure CreateProjection{..}
where
columnListP :: Parser (NonEmpty (ProjectionColumn Range))
columnListP = do
_ <- Tok.openP
c:cs <- flip sepBy1 Tok.commaP $ do
(projectionColumnName, s) <- Tok.columnNameP
projectionColumnAccessRank <- optionMaybe $ do
s' <- Tok.accessRankP
(n, e') <- integerP
pure $ AccessRank (s' <> e') n
projectionColumnEncoding <- optionMaybe $ do
_ <- Tok.encodingP
Tok.encodingTypeP
let projectionColumnInfo =
sconcat $ s :| catMaybes [ getInfo <$> projectionColumnAccessRank
, getInfo <$> projectionColumnEncoding ]
pure ProjectionColumn{..}
_ <- Tok.closeP
pure (c:|cs)
multipleRenameP :: Parser (MultipleRename RawNames Range)
multipleRenameP = do
s <- Tok.alterP
_ <- Tok.tableP
sources <- tableNameP `sepBy1` Tok.commaP
_ <- Tok.renameP
_ <- Tok.toP
targets <- map (\ uqtn -> uqtn { tableNameSchema = Nothing }) <$> unqualifiedTableNameP `sepBy1` Tok.commaP
when (length sources /= length targets) $ fail "multi-renames require the same number of sources and targets"
let e = getInfo $ last targets
pairs = zip sources targets
toAlterTableRename = \ (from, to) ->
AlterTableRenameTable (getInfo from <> getInfo to) from to
renames = map toAlterTableRename pairs
pure $ MultipleRename (s <> e) renames
setSchemaP :: Parser (SetSchema RawNames Range)
setSchemaP = do
s <- Tok.alterP
_ <- Tok.tableP
table <- tableNameP
_ <- Tok.setP
_ <- Tok.schemaP
(schema, r) <- Tok.schemaNameP
e <- option r $ choice [Tok.restrictP, Tok.cascadeP]
pure $ SetSchema (s <> e) table $ mkNormalSchema schema r
renameProjectionP :: Parser Range
renameProjectionP = do
s <- Tok.alterP
_ <- Tok.projectionP
_ <- projectionNameP
_ <- Tok.renameP
_ <- Tok.toP
to <- projectionNameP
pure $ s <> getInfo to
alterResourcePoolPrefixP :: Parser Range
alterResourcePoolPrefixP = do
s <- Tok.alterP
_ <- Tok.resourceP
e <- Tok.poolP
pure $ s <> e
alterResourcePoolP :: Parser Range
alterResourcePoolP = do
s <- alterResourcePoolPrefixP
ts <- P.many Tok.notSemicolonP
pure $ case reverse ts of
[] -> s
e:_ -> s <> e
createResourcePoolPrefixP :: Parser Range
createResourcePoolPrefixP = do
s <- Tok.createP
_ <- Tok.resourceP
e <- Tok.poolP
pure $ s <> e
createResourcePoolP :: Parser Range
createResourcePoolP = do
s <- createResourcePoolPrefixP
ts <- P.many Tok.notSemicolonP
pure $ case reverse ts of
[] -> s
e:_ -> s <> e
dropResourcePoolPrefixP :: Parser Range
dropResourcePoolPrefixP = do
s <- Tok.dropP
_ <- Tok.resourceP
e <- Tok.poolP
pure $ s <> e
dropResourcePoolP :: Parser Range
dropResourcePoolP = do
s <- dropResourcePoolPrefixP
e <- Tok.notSemicolonP
pure $ s <> e
createFunctionPrefixP :: Parser Range
createFunctionPrefixP = do
s <- Tok.createP
_ <- optional $ Tok.orP >> Tok.replaceP
e <- choice
[ do
_ <- optional $ Tok.transformP <|> Tok.analyticP <|> Tok.aggregateP
Tok.functionP
, Tok.filterP
, Tok.parserP
, Tok.sourceP
]
pure $ s <> e
createFunctionP :: Parser Range
createFunctionP = do
s <- createFunctionPrefixP
ts <- P.many Tok.notSemicolonP
pure $ case reverse ts of
[] -> s
e:_ -> s <> e
alterTableAddConstraintP :: Parser Range
alterTableAddConstraintP = do
s <- Tok.alterP
_ <- Tok.tableP
_ <- tableNameP
_ <- Tok.addP
e <- tableConstraintP
pure $ s <> e
tableConstraintP :: Parser Range
tableConstraintP = do
s <- optionMaybe $ do
s <- Tok.constraintP
_ <- Tok.constraintNameP
return s
e <- choice
[ do
_ <- Tok.primaryP
_ <- Tok.keyP
e <- columnListP
option e (Tok.enabledP <|> Tok.disabledP)
, do
_ <- Tok.uniqueP
e <- columnListP
option e (Tok.enabledP <|> Tok.disabledP)
, do
_ <- Tok.foreignP
_ <- Tok.keyP
_ <- columnListP
_ <- Tok.referencesP
e <- getInfo <$> tableNameP
option e columnListP
, do
_ <- Tok.checkP
e <- getInfo <$> exprP
option e (Tok.enabledP <|> Tok.disabledP)
]
return (maybe e id s <> e)
where
columnListP :: Parser Range
columnListP = do
s <- Tok.openP
_ <- Tok.columnNameP `sepBy1` Tok.commaP
e <- Tok.closeP
return (s <> e)
exportToStdoutP :: Parser Range
exportToStdoutP = do
s <- Tok.exportP
_ <- Tok.toP
_ <- Tok.stdoutP
_ <- Tok.fromP
_ <- tableNameP
_ <- Tok.openP
_ <- Tok.columnNameP `sepBy1` Tok.commaP
e <- Tok.closeP
pure $ s <> e
setSessionPrefixP :: Parser Range
setSessionPrefixP = do
s <- Tok.setP
e <- Tok.sessionP
return $ s <> e
setSessionP :: Parser Range
setSessionP = do
s <- setSessionPrefixP
ts <- P.many Tok.notSemicolonP
pure $ case reverse ts of
[] -> s
e:_ -> s <> e
setTimeZoneP :: Parser Range
setTimeZoneP = do
s <- Tok.setP
_ <- Tok.timezoneP
_ <- Tok.toP
e <- choice [ Tok.defaultP
, snd <$> Tok.stringP
, Tok.intervalP >> snd <$> Tok.stringP
]
return $ s <> e
connectP :: Parser Range
connectP = do
s <- Tok.connectP
_ <- Tok.toP
_ <- Tok.verticaP
_ <- Tok.databaseNameP
_ <- Tok.userP
_ <- Tok.userNameP
_ <- Tok.passwordP
e <- snd <$> Tok.stringP <|> snd <$> starsP
e' <- option e $ do
_ <- Tok.onP
_ <- Tok.stringP
_ <- Tok.commaP
snd <$> Tok.numberP
pure $ s <> e'
where
starsP = do
rs <- P.many1 Tok.starP
let text = TL.take (fromIntegral $ length rs) $ TL.repeat '*'
r = head rs <> last rs
pure (text, r)
disconnectP :: Parser Range
disconnectP = do
s <- Tok.disconnectP
(_, e) <- Tok.databaseNameP
pure $ s <> e
createAccessPolicyP :: Parser Range
createAccessPolicyP = do
s <- Tok.createP
_ <- Tok.accessP
_ <- Tok.policyP
_ <- Tok.onP
_ <- tableNameP
_ <- Tok.forP
_ <- Tok.columnP
_ <- Tok.columnNameP
_ <- exprP
e <- choice [ Tok.enableP, Tok.disableP ]
pure $ s <> e
copyFromP :: Parser Range
copyFromP = do
s <- Tok.copyP
e <- getInfo <$> tableNameP
e' <- consumeOrderedOptions e $
[ ingestionColumnListP (getInfo <$> exprP)
, ingestionColumnOptionP
, fromP
, fileStorageFormatP
]
e'' <- consumeUnorderedOptions e' $
[ do
_ <- optional Tok.withP
choice [ fileSourceP
, fileFilterP
, fileParserP
]
, delimiterAsP
, trailingNullColsP
, nullAsP
, escapeFormatP
, enclosedByP
, recordTerminatorP
, try $ skipRecordsP
, try $ skipBytesP
, trimByteP
, rejectMaxP
, rejectedDataOnNodeP
, exceptionsOnNodeP
, Tok.enforceLengthP
, errorToleranceP
, abortOnErrorP
, optional Tok.storageP >> loadMethodP
, streamNameP
, noCommitP
]
return $ s <> e''
where
onNodeP :: Range -> Parser Range
onNodeP r = do
s <- option r $ choice
[ try $ Tok.onP >> snd <$> Tok.nodeNameP
, Tok.onP >> Tok.anyP >> Tok.nodeP
]
e <- option s compressionP
return $ s <> e
fromP :: Parser Range
fromP = do
outerS <- Tok.fromP
outerE <- choice $
[ do
s <- Tok.stdinP
e <- option s compressionP
return $ s <> e
, do
(_, s) <- Tok.stringP
e <- last <$> ((onNodeP s) `sepBy1` Tok.commaP)
return $ s <> e
, do
s <- Tok.localP
e' <- choice [ do
e <- Tok.stdinP
option e compressionP
, let pathToDataP = do
e <- snd <$> Tok.stringP
option e compressionP
in last <$> (pathToDataP `sepBy1` Tok.commaP)
]
return $ s <> e'
, do
s <- Tok.verticaP
_ <- Tok.databaseNameP
_ <- Tok.dotP
e <- getInfo <$> tableNameP
e' <- option e $ do
_ <- Tok.openP
_ <- Tok.columnNameP `sepBy1` Tok.commaP
Tok.closeP
return $ s <> e'
]
return $ outerS <> outerE
showP :: Parser Range
showP = do
s <- Tok.showP
es <- many1 Tok.notSemicolonP
return $ s <> last es
mergeP :: Parser (Merge RawNames Range)
mergeP = do
r1 <- Tok.mergeP
_ <- Tok.intoP
mergeTargetTable <- tableNameP
mergeTargetAlias <- optionMaybe tableAliasP
_ <- Tok.usingP
mergeSourceTable <- tableNameP
mergeSourceAlias <- optionMaybe tableAliasP
_ <- Tok.onP
mergeCondition <- exprP
mergeUpdateDirective <- optionMaybe $ do
_ <- try $ P.lookAhead $ Tok.whenP >> Tok.matchedP
_ <- Tok.whenP
_ <- Tok.matchedP
_ <- Tok.thenP
_ <- Tok.updateP
_ <- Tok.setP
NE.fromList <$> colValP `sepBy1` Tok.commaP
(mergeInsertDirectiveColumns, mergeInsertDirectiveValues, r2) <- option (Nothing, Nothing, Just r1) $ do
_ <- Tok.whenP
_ <- Tok.notP
_ <- Tok.matchedP
_ <- Tok.thenP
_ <- Tok.insertP
cols <- optionMaybe $ NE.fromList <$> P.between Tok.openP Tok.closeP (oqColumnNameP `sepBy1` Tok.commaP)
_ <- Tok.valuesP
_ <- Tok.openP
vals <- NE.fromList <$> defaultExprP `sepBy1` Tok.commaP
e <- Tok.closeP
return (cols, Just vals, Just e)
when ((mergeUpdateDirective, mergeInsertDirectiveValues) == (Nothing, Nothing)) $
fail "MERGE requires at least one of UPDATE and INSERT"
let mLastUpdate = fmap (getInfo . snd . NE.last) mergeUpdateDirective
mLastInsert = r2
r3 = sconcat $ NE.fromList $ catMaybes [mLastUpdate, mLastInsert]
mergeInfo = r1 <> r3
return Merge{..}
where
tableAliasP :: Parser (TableAlias Range)
tableAliasP = do
(name, r) <- Tok.tableNameP
makeTableAlias r name
colValP :: Parser (ColumnRef RawNames Range, DefaultExpr RawNames Range)
colValP = do
col <- oqColumnNameP
_ <- Tok.equalP
val <- defaultExprP
return (col { columnNameTable = Nothing }, val)