diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Uber Technologies, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/queryparser-presto.cabal b/queryparser-presto.cabal
new file mode 100644
--- /dev/null
+++ b/queryparser-presto.cabal
@@ -0,0 +1,103 @@
+name:                queryparser-presto
+
+-- The package version.  See the Haskell package versioning policy (PVP)
+-- for standards guiding when and how versions should be incremented.
+-- http://www.haskell.org/haskellwiki/Package_versioning_policy
+-- PVP summary:      +-+------- breaking API changes
+--                   | | +----- non-breaking API additions
+--                   | | | +--- code changes with no API change
+version:             0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis:            Parsing for Presto SQL queries
+
+-- A longer description of the package.
+description:
+            A library for parsing Presto SQL queries into analyzable ASTs.
+            .
+            This library is to be used with the queryparser library, which
+            provides the common type definitions and analyses across the
+            different SQL dialects.
+
+-- The license under which the package is released.
+license:             MIT
+
+-- The file containing the license text.
+license-file:        LICENSE
+
+-- The package author(s).
+author:              Heli Wang, David Thomas, Matt Halverson
+
+-- An email address to which users can send suggestions, bug reports, and
+-- patches.
+maintainer:          heli@uber.com
+
+-- A copyright notice.
+-- copyright:
+
+category:            Database
+
+build-type:          Simple
+
+-- Extra files to be distributed with the package, such as examples or a
+-- README.
+-- extra-source-files:
+
+-- Constraint on the version of Cabal needed to build this package.
+cabal-version:       >=1.10
+
+flag development {
+    description: Enable development level of strictness
+    default: False
+    manual: True
+}
+
+library
+  -- Modules exported by the library.
+  exposed-modules:     Database.Sql.Presto.Parser
+                     , Database.Sql.Presto.Parser.Token
+                     , Database.Sql.Presto.Scanner
+                     , Database.Sql.Presto.Token
+                     , Database.Sql.Presto.Type
+                     , Database.Sql.Presto.Parser.Internal
+
+  -- Modules included in this library but not exported.
+  -- other-modules:     
+
+  default-extensions:  OverloadedStrings
+                     , LambdaCase
+                     , RecordWildCards
+                     , TupleSections
+                     , ConstraintKinds
+                     , FlexibleInstances
+
+  -- Other library packages from which modules are imported.
+  build-depends:       base >=4.8 && <4.9
+                     , text >=1.2 && <1.3
+                     , bytestring
+                     , queryparser
+                     , containers
+                     , semigroups >= 0.16
+                     , mtl >= 2.2 && < 2.3
+                     , parsec >= 3.1 && < 3.2
+                     , pretty >= 1.1 && < 1.2
+                     , aeson >= 0.8
+                     , yaml >= 0.8 && < 0.9
+                     , unordered-containers
+                     , hashable
+                     , QuickCheck
+                     , regex-tdfa
+                     , fixed-list
+                     , predicate-class
+
+  -- Directories containing source files.
+  hs-source-dirs:      src
+
+
+  ghc-options:         -Wall
+
+  if flag(development)
+    ghc-options:       -Werror
+
+  -- Base language which the package is written in.
+  default-language:    Haskell2010
diff --git a/src/Database/Sql/Presto/Parser.hs b/src/Database/Sql/Presto/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Parser.hs
@@ -0,0 +1,1553 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+{-# LANGUAGE TypeFamilies #-}
+
+module Database.Sql.Presto.Parser where
+
+import Database.Sql.Type
+import Database.Sql.Position
+import Database.Sql.Helpers
+import Database.Sql.Info
+import Database.Sql.Presto.Type
+import Database.Sql.Presto.Scanner
+import Database.Sql.Presto.Parser.Internal
+
+import qualified Database.Sql.Presto.Parser.Token as Tok
+
+import qualified Text.Parsec as P
+import           Text.Parsec ( chainl1, choice, many
+                             , option, optional, optionMaybe
+                             , sepBy, sepBy1, try, (<|>), (<?>))
+
+import           Data.Char (isDigit)
+import           Data.Foldable (fold)
+import qualified Data.List as L
+import           Data.List.NonEmpty (NonEmpty ((:|)))
+import           Data.Maybe (catMaybes)
+import           Data.Monoid (Endo (..))
+import           Data.Semigroup (Semigroup (..), sconcat)
+import           Data.Set (Set)
+import qualified Data.Set as S
+import           Data.Text.Lazy (Text)
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+
+import Control.Arrow (first)
+import Control.Monad (when)
+import Control.Monad.Reader (runReader, local, asks)
+
+
+statementParser :: Parser (PrestoStatement RawNames Range)
+statementParser = do
+    maybeStmt <- optionMaybe $ choice
+        [ try $ PrestoStandardSqlStatement <$> statementP
+        , PrestoUnhandledStatement <$> explainP
+        , PrestoUnhandledStatement <$> showP
+        , PrestoUnhandledStatement <$> callP
+        , PrestoUnhandledStatement <$> describeP
+        ]
+    case maybeStmt of
+        Just stmt -> terminator >> return stmt
+        Nothing -> PrestoStandardSqlStatement <$> emptyStatementP
+  where
+    terminator = (Tok.semicolonP <|> eof) -- normal statements may be terminated by `;` or eof
+    emptyStatementP = EmptyStmt <$> Tok.semicolonP  -- but we don't allow eof here. `;` is the
+    -- only way to write the empty statement, i.e. `` (empty string) is not allowed.
+
+emptyParserScope :: ParserScope
+emptyParserScope = ParserScope
+    { selectTableAliases = Nothing }
+
+-- | parse consumes a statement, or fails
+parse :: Text -> Either P.ParseError (PrestoStatement RawNames Range)
+parse text = runReader (P.runParserT statementParser 0 "-"  . tokenize $ text) emptyParserScope
+
+-- | parseAll consumes all input as a single statement, or fails
+parseAll :: Text -> Either P.ParseError (PrestoStatement RawNames Range)
+parseAll text = runReader (P.runParserT (statementParser <* P.eof) 0 "-"  . tokenize $ text) emptyParserScope
+
+-- | parseMany consumes multiple statements, or fails
+parseMany :: Text -> Either P.ParseError [PrestoStatement RawNames Range]
+parseMany text = runReader (P.runParserT (P.many1 statementParser) 0 "-"  . tokenize $ text) emptyParserScope
+
+-- | parseManyAll consumes all input multiple statements, or fails
+parseManyAll :: Text -> Either P.ParseError [PrestoStatement RawNames Range]
+parseManyAll text = runReader (P.runParserT (P.many1 statementParser <* P.eof) 0 "-"  . tokenize $ text) emptyParserScope
+
+-- | parseManyEithers consumes all input as multiple (statements or failures)
+-- it should never fail
+parseManyEithers :: Text -> Either P.ParseError [Either (Unparsed Range) (PrestoStatement RawNames Range)]
+parseManyEithers text = runReader (P.runParserT parser 0 "-"  . tokenize $ text) emptyParserScope
+  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)]
+
+
+statementP :: Parser (Statement Presto RawNames Range)
+statementP = choice
+    [ QueryStmt <$> queryP
+    , DeleteStmt <$> deleteP
+    , do
+          _ <- try $ P.lookAhead dropViewPrefixP
+          DropViewStmt <$> dropViewP
+    , DropTableStmt <$> dropTableP
+    , GrantStmt <$> grantP
+    , RevokeStmt <$> revokeP
+    , InsertStmt <$> insertP
+    ]
+
+queryP :: Parser (Query RawNames Range)
+queryP = do
+    with <- option id withP
+    queryNoWith <- queryNoWithP
+    return $ with queryNoWith
+  where
+    withP :: Parser (Query RawNames Range -> Query RawNames Range)
+    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 :: Parser (CTE RawNames Range)
+    cteP = do
+        alias <- tableAliasP
+        columns <- option [] $ P.between Tok.openP Tok.closeP $ columnAliasP `sepBy1` Tok.commaP
+        _ <- Tok.asP
+        _ <- Tok.openP
+        query <- queryP
+        r' <- Tok.closeP
+
+        return $ CTE (getInfo alias <> r') alias columns query
+
+queryNoWithP :: Parser (Query RawNames Range)
+queryNoWithP = do
+    queryTerm <- queryTermP
+    order <- option id (orderWrapperP queryTerm)
+    limit <- option id limitP
+    return $ limit $ order queryTerm
+  where
+    queryTermP = (queryPrimaryP `chainl1` (exceptP <|> unionP))
+                `chainl1` intersectP
+
+    queryPrimaryP = choice
+        [ querySelectP
+        -- TODO table
+        -- TODO values
+        , P.between Tok.openP Tok.closeP queryNoWithP
+        ]
+
+    exceptP = do
+        r <- Tok.exceptP
+        optional Tok.distinctP
+        return $ QueryExcept r Unused
+
+    unionP = do
+        r <- Tok.unionP
+        distinct <- option (Distinct True) distinctP
+        return $ QueryUnion r distinct Unused
+
+    intersectP = do
+        r <- Tok.intersectP
+        optional Tok.distinctP
+        return $ QueryIntersect r Unused
+
+
+    orderWrapperP query = do
+        let aliases = aliasesForOrders query
+        (r, orders) <- local (introduceAliases aliases) orderTopLevelP
+        return $ \ query' -> QueryOrder (getInfo query' <> r) orders query'
+
+    aliasesForOrders query = case query of
+        QuerySelect _ s -> tableAliases $ selectFrom s
+        _ -> S.empty
+
+    limitP = do
+        r <- Tok.limitP
+        choice
+            [ do
+                  (NumericConstant r' num) <- numberConstantP
+                  let limit = Limit (r <> r') num
+                  return $ \ query -> QueryLimit (getInfo query <> r') limit query
+
+            , Tok.allP >> return id
+            ]
+
+querySelectP :: Parser (Query RawNames Range)
+querySelectP = do
+    select <- selectP
+    return $ QuerySelect (selectInfo select) select
+
+selectP :: Parser (Select RawNames Range)
+selectP = do
+    r <- Tok.selectP
+
+    selectDistinct <- option notDistinct distinctP
+
+    aliases <- try selectScopeLookAhead
+
+    selectCols <- do
+        selections <- local (introduceAliases aliases) $ selectionP `countingSepBy1` Tok.commaP
+        let r' = foldl1 (<>) $ map getInfo selections
+        return $ SelectColumns r' selections
+
+    selectFrom <- optionMaybe fromP
+    selectWhere <- optionMaybe $ local (introduceAliases aliases) whereP
+    let selectTimeseries = Nothing
+    selectGroup <- optionMaybe $ local (introduceAliases aliases) groupP
+    selectHaving <- optionMaybe $ local (introduceAliases aliases) havingP
+    let selectNamedWindow = Nothing
+        Just selectInfo = sconcat $ Just r :|
+            [ Just $ getInfo selectCols
+            , getInfo <$> selectFrom
+            , getInfo <$> selectWhere
+            , getInfo <$> selectGroup
+            , getInfo <$> selectHaving
+            ]
+
+    pure Select{..}
+
+  where
+    selectScopeLookAhead :: Parser (Set Text)
+    selectScopeLookAhead = P.lookAhead $ do
+        _ <- selectionP (-1) `sepBy1` Tok.commaP
+        from <- optionMaybe fromP
+        return $ tableAliases from
+
+
+distinctP :: Parser Distinct
+distinctP = choice $
+    [ Tok.allP >> return (Distinct False)
+    , Tok.distinctP >> return (Distinct True)
+    ]
+
+
+tableAliases :: Maybe (SelectFrom RawNames Range) -> Set Text
+tableAliases from =
+    let tablishes = case from of
+            Just (SelectFrom _ ts) -> ts
+            Nothing -> []
+      in L.foldl' S.union S.empty $ map tablishToTableAlias tablishes
+  where
+    tablishToTableAlias :: Tablish RawNames Range -> Set Text
+    tablishToTableAlias (TablishTable _ aliases tableName) = case aliases of
+        TablishAliasesNone -> tableNameToTableAlias tableName
+        TablishAliasesT (TableAlias _ name _) -> S.singleton name
+        TablishAliasesTC (TableAlias _ name _) _ -> S.singleton name
+    tablishToTableAlias (TablishSubQuery _ aliases _) = case aliases of
+        TablishAliasesNone -> S.empty
+        TablishAliasesT (TableAlias _ name _) -> S.singleton name
+        TablishAliasesTC (TableAlias _ name _) _ -> S.singleton name
+    tablishToTableAlias (TablishLateralView _ LateralView{..} _) = case lateralViewAliases of
+        TablishAliasesNone -> S.empty
+        TablishAliasesT (TableAlias _ name _) -> S.singleton name
+        TablishAliasesTC (TableAlias _ name _) _ -> S.singleton name
+    tablishToTableAlias (TablishJoin _ (JoinSemi _) _ _ _) =
+        error "this shouldn't happen: no SEMI JOIN in Presto"
+    tablishToTableAlias (TablishJoin _ _ _ lTablish rTablish) =
+        tablishToTableAlias lTablish `S.union` tablishToTableAlias rTablish
+
+tableNameToTableAlias :: OQTableName Range -> Set Text
+tableNameToTableAlias (QTableName _ Nothing tname) = S.singleton tname
+tableNameToTableAlias (QTableName _ (Just (QSchemaName _ _ _ SessionSchema)) _) =
+    error "this shouldn't happen: no SessionSchema in Presto"
+tableNameToTableAlias (QTableName _ (Just (QSchemaName _ Nothing sname _)) tname) =
+    S.fromList [ tname
+               , sname <> "." <> tname
+               ]
+tableNameToTableAlias (QTableName _ (Just (QSchemaName _ (Just (DatabaseName _ dname)) sname _)) tname) =
+    S.fromList [ tname
+               , sname <> "." <> tname
+               , dname <> "." <> sname <> "." <> tname
+               ]
+
+introduceAliases :: Set Text -> ParserScope -> ParserScope
+introduceAliases aliases = \ scope ->
+    let unioned = case selectTableAliases scope of
+            Nothing -> aliases
+            Just existing -> S.union existing aliases
+    in scope { selectTableAliases = Just unioned }
+
+
+fromP :: Parser (SelectFrom RawNames Range)
+fromP = do
+    r <- Tok.fromP
+    relations <- relationP `sepBy1` Tok.commaP
+
+    let r' = foldl (<>) r $ fmap getInfo relations
+    return $ SelectFrom r' relations
+
+relationP :: Parser (Tablish RawNames Range)
+relationP = do
+    table <- sampledRelationP
+    joins <- fmap (appEndo . fold . reverse) $ many $ Endo <$> joinP
+    return $ joins table
+
+sampledRelationP :: Parser (Tablish RawNames Range)
+sampledRelationP = do
+    table <- aliasedRelationP
+    _ <- optional tableSampleP
+    return table
+  where
+    tableSampleP :: Parser Range
+    tableSampleP = do
+        s <- Tok.tableSampleP
+        _ <- sampleTypeP
+        _ <- Tok.openP
+        _ <- numberExprP -- T655130
+        e <- Tok.closeP
+        return $ s <> e
+
+    sampleTypeP :: Parser Range
+    sampleTypeP = choice [ Tok.bernoulliP, Tok.systemP, Tok.poissonizedP ]
+
+    aliasedRelationP :: Parser (Tablish RawNames Range)
+    aliasedRelationP = do
+        let placeholder = error "placeholder aliases never got replaced!"
+        t <- choice $
+            [ do
+                  name <- tableNameP
+                  return $ TablishTable (getInfo name) placeholder name
+
+            , do
+                  r1 <- Tok.unnestP
+                  _ <- Tok.openP
+                  let lateralViewOuter = Nothing -- not an option in Presto
+                  args <- exprP `sepBy1` Tok.commaP
+                  r2 <- Tok.closeP
+                  let lateralViewExprs = [FunctionExpr (r1 <> r2) (QFunctionName r1 Nothing "unnest") notDistinct args [] Nothing Nothing]
+                  (lateralViewWithOrdinality, r3) <- option (False, r2) $ do
+                      _ <- Tok.withP
+                      (True, ) <$> Tok.ordinalityP
+
+                  let lateralViewInfo = r1 <> r3
+                      lateralViewAliases = placeholder
+                  return $ TablishLateralView lateralViewInfo LateralView{..} Nothing
+
+            , P.between Tok.openP Tok.closeP $ choice
+                [ relationP
+                , do
+                      q <- queryP
+                      return $ TablishSubQuery (getInfo q) placeholder q
+                ]
+            ]
+        as <- tablishAliasesP
+        let withAliases = case t of
+                TablishTable info _ tableRef -> TablishTable info as tableRef
+                TablishSubQuery info _ query -> TablishSubQuery info as query
+                TablishJoin _ _ _ _ _ -> error "shouldn't happen"
+                TablishLateralView info LateralView{..} lhs -> TablishLateralView info LateralView{lateralViewAliases = as, ..} lhs
+        return withAliases
+
+    tablishAliasesP :: Parser (TablishAliases Range)
+    tablishAliasesP = do
+        option TablishAliasesNone $ try $ do
+            -- the try is because TABLESAMPLE may either be a table alias OR
+            -- the start of a tablesample clause
+            _ <- optional Tok.asP
+            tAlias@(TableAlias _ name _) <- tableAliasP
+            when (TL.toLower name == "tablesample") $ P.lookAhead $ P.notFollowedBy sampleTypeP
+
+            option (TablishAliasesT tAlias) $ do
+                cAliases <- P.between Tok.openP Tok.closeP $ columnAliasP `sepBy1` Tok.commaP
+                return $ TablishAliasesTC tAlias cAliases
+
+
+tableAliasP :: Parser (TableAlias Range)
+tableAliasP = do
+    (name, r) <- Tok.tableNameP
+    makeTableAlias r name
+
+columnAliasP :: Parser (ColumnAlias Range)
+columnAliasP = do
+    (name, r) <- Tok.columnNameP
+    makeColumnAlias r name
+
+joinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
+joinP = crossJoinP <|> regularJoinP <|> naturalJoinP
+  where
+    crossJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
+    crossJoinP = do
+        r <- Tok.crossP
+        r'<- Tok.joinP
+        rhs <- sampledRelationP
+        let info = r <> r'
+            joinType = JoinInner info
+            condition = JoinOn $ ConstantExpr info $ BooleanConstant info True
+        return $ \ lhs ->
+            TablishJoin (getInfo lhs <> getInfo rhs) joinType condition lhs rhs
+
+    regularJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
+    regularJoinP = do
+        joinType <- joinTypeP
+        rhs <- relationP
+        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
+            ]
+        return $ \ lhs ->
+            TablishJoin (getInfo rhs <> getInfo lhs) joinType condition lhs rhs
+
+    naturalJoinP :: Parser (Tablish RawNames Range -> Tablish RawNames Range)
+    naturalJoinP = do
+        r <- Tok.naturalP
+        joinType <- joinTypeP
+        rhs <- sampledRelationP
+        let condition = JoinNatural r Unused
+        return $ \ lhs ->
+            TablishJoin (getInfo rhs <> getInfo lhs) joinType condition lhs rhs
+
+    joinTypeP :: Parser (JoinType Range)
+    joinTypeP = do
+        maybeJoinType <- optionMaybe $ innerJoinTypeP <|> outerJoinTypeP
+        Tok.joinP >>= \ r -> return $ case maybeJoinType of
+            Nothing -> JoinInner r
+            Just joinType -> (<> r) <$> joinType
+
+    innerJoinTypeP :: Parser (JoinType Range)
+    innerJoinTypeP = JoinInner <$> Tok.innerP
+
+    outerJoinTypeP :: Parser (JoinType Range)
+    outerJoinTypeP = do
+        joinType <- choice
+            [ JoinLeft <$> Tok.leftP
+            , JoinRight <$> Tok.rightP
+            , JoinFull <$> Tok.fullP
+            ]
+        optional Tok.outerP
+        return joinType
+
+databaseNameP :: Parser (DatabaseName Range)
+databaseNameP = do
+    (db, r) <- Tok.databaseNameP
+    return $ DatabaseName r db
+
+unqualifiedSchemaNameP :: Parser (UQSchemaName Range)
+unqualifiedSchemaNameP = uncurry mkNormalSchema <$> Tok.schemaNameP
+
+-- schemaNameP is greedy: it will consume the largest chunk of text that looks
+-- like a schema name.
+schemaNameP :: Parser (SchemaName RawNames Range)
+schemaNameP = choice
+    [ try qualifiedSchemaNameP
+    , do
+        uqsn <- unqualifiedSchemaNameP
+        pure uqsn { schemaNameDatabase = Nothing }
+    ]
+  where
+    qualifiedSchemaNameP :: Parser (SchemaName RawNames Range)
+    qualifiedSchemaNameP = do
+        db <- databaseNameP
+        _ <- Tok.dotP
+        s <- unqualifiedSchemaNameP
+        return s { schemaNameDatabase = Just db }
+
+unqualifiedTableNameP :: Parser (UQTableName Range)
+unqualifiedTableNameP = do
+    (t, r) <- Tok.tableNameP
+    return $ QTableName r None t
+
+-- tableNameP is also greedy
+tableNameP :: Parser (TableRef RawNames Range)
+tableNameP = choice
+    [ try qualifiedTableNameP
+    , do
+        uqtn <- unqualifiedTableNameP
+        pure uqtn { tableNameSchema = Nothing }
+    ]
+  where
+    qualifiedTableNameP :: Parser (TableRef RawNames Range)
+    qualifiedTableNameP = choice
+        [ try $ do
+              d <- databaseNameP
+              _ <- Tok.dotP
+              s <- unqualifiedSchemaNameP
+              _ <- Tok.dotP
+              t <- unqualifiedTableNameP
+              return t { tableNameSchema = Just s { schemaNameDatabase = Just d } }
+        , do
+              s <- unqualifiedSchemaNameP
+              _ <- Tok.dotP
+              t <- unqualifiedTableNameP
+              return $ t { tableNameSchema = Just s { schemaNameDatabase = Nothing } }
+        ]
+
+unqualifiedColumnNameP :: Parser (UQColumnName Range)
+unqualifiedColumnNameP = do
+    (c, r) <- Tok.columnNameP
+    return $ QColumnName r None c
+
+-- | parsing of qualified columnNames respects the following rules:
+--
+-- 1) you need to know what tablishes are in scope when parsing a column ref
+-- 2) column refs may only be as qualified as the table that introduced them
+-- 3) column refs are greedy w.r.t. dots (if a qualified table name and a CTE
+--    have the same prefix, a qualified column ref prefers the table)
+--
+-- If a scope is present (i.e. while parsing selections), the table name must be
+-- a member of the tableAlias list for the parser to succeed. Otherwise,
+-- the column parser fails and execution tries the next parser choice.
+--
+-- Should the scope not be set, e.g. when selectP is performing lookahead
+-- to build scope, this check is skipped.
+columnNameP :: Parser (ColumnRef RawNames Range)
+columnNameP = choice
+    [ try qualifiedColumnNameP
+    , do
+        uqcn <- unqualifiedColumnNameP
+        pure uqcn { columnNameTable = Nothing }
+    ]
+  where
+    qualifiedColumnNameP :: Parser (ColumnRef RawNames Range)
+    qualifiedColumnNameP = choice
+        [ try $ do
+              d@(DatabaseName _ dName) <- databaseNameP
+              _ <- Tok.dotP
+              s@(QSchemaName _ _ sName _) <- unqualifiedSchemaNameP
+              _ <- Tok.dotP
+              t@(QTableName _ _ tName) <- unqualifiedTableNameP
+
+              checkTableNameInScopeP $ dName <> "." <> sName <> "." <> tName
+
+              _ <- Tok.dotP
+              c <- unqualifiedColumnNameP
+              return c { columnNameTable = Just t { tableNameSchema = Just s { schemaNameDatabase = Just d } } }
+
+        , try $ do
+              s@(QSchemaName _ _ sName _) <- unqualifiedSchemaNameP
+              _ <- Tok.dotP
+              t@(QTableName _ _ tName) <- unqualifiedTableNameP
+
+              checkTableNameInScopeP $ sName <> "." <> tName
+
+              _ <- Tok.dotP
+              c <- unqualifiedColumnNameP
+              return c { columnNameTable = Just t { tableNameSchema = Just s { schemaNameDatabase = Nothing } } }
+
+        , do
+              t@(QTableName _ _ tName) <- unqualifiedTableNameP
+
+              checkTableNameInScopeP tName
+
+              _ <- Tok.dotP
+              c <- unqualifiedColumnNameP
+              return c { columnNameTable = Just t { tableNameSchema = Nothing } }
+        ]
+
+    checkTableNameInScopeP :: Text -> Parser ()
+    checkTableNameInScopeP name = do
+        maybeScope <- asks selectTableAliases
+        case maybeScope of
+            Just scope ->
+                case L.find (==name) scope of
+                    Just _  -> return ()
+                    Nothing -> fail $ "Table " ++ (show name) ++
+                        " doesn't exist in table scope " ++ show maybeScope
+            Nothing -> return ()
+
+
+-- functionNameP is also greedy
+functionNameP :: Parser (FunctionName Range)
+functionNameP = choice
+    [ try $ do
+          d <- databaseNameP
+          _ <- Tok.dotP
+          s <- unqualifiedSchemaNameP
+          _ <- Tok.dotP
+          (f, r) <- Tok.functionNameP
+          return $ QFunctionName (getInfo d <> r) (Just s { schemaNameDatabase = Just d }) f
+    , try $ do
+          s <- unqualifiedSchemaNameP
+          _ <- Tok.dotP
+          (f, r) <- Tok.functionNameP
+          return $ QFunctionName (getInfo s <> r) (Just s { schemaNameDatabase = Nothing }) f
+    , do
+          (f, r) <- Tok.functionNameP
+          return $ QFunctionName r Nothing f
+    ]
+
+-- selectStarP is also greedy
+selectStarP :: Parser (Selection RawNames Range)
+selectStarP = choice
+    [ do
+        r <- Tok.starP
+        return $ SelectStar r Nothing Unused
+
+    , try $ do
+        t <- unqualifiedTableNameP
+        _ <- Tok.dotP
+        r' <- Tok.starP
+        return $ SelectStar (getInfo t <> r') (Just t { tableNameSchema = Nothing }) Unused
+
+    , try $ do
+        s <- unqualifiedSchemaNameP
+        _ <- Tok.dotP
+        t <- unqualifiedTableNameP
+        _ <- Tok.dotP
+        r'' <- Tok.starP
+        return $ SelectStar (getInfo s <> r'') (Just t { tableNameSchema = Just s { schemaNameDatabase = Nothing }}) Unused
+
+    , try $ do
+        d <- databaseNameP
+        _ <- Tok.dotP
+        s <- unqualifiedSchemaNameP
+        _ <- Tok.dotP
+        t <- unqualifiedTableNameP
+        _ <- Tok.dotP
+        r'' <- Tok.starP
+        return $ SelectStar (getInfo d <> r'') (Just t { tableNameSchema = Just s { schemaNameDatabase = Just d }}) Unused
+    ]
+
+
+selectionP :: Integer -> Parser (Selection RawNames Range)
+selectionP idx = try selectStarP <|> do
+    expr <- exprP
+    alias <- aliasP expr idx
+    return $ SelectExpr (getInfo alias <> getInfo expr) [alias] expr
+  where
+    aliasP :: Expr RawNames Range -> Integer -> Parser (ColumnAlias Range)
+    aliasP expr idx' = choice
+        [ do
+            optional Tok.asP
+            (name, r) <- Tok.columnNameP
+            makeColumnAlias r name
+
+        , makeExprAlias expr idx'
+        ]
+
+countingSepBy1 :: (Integer -> Parser b) -> Parser c -> Parser [b]
+countingSepBy1 f sep = do
+    x <- f 0
+    xs <- rest 1
+    pure (x:xs)
+  where
+    rest n = choice
+        [ do
+            _ <- sep
+            x <- f n
+            xs <- rest (n + 1)
+            pure (x:xs)
+        , pure []
+        ]
+
+makeTableAlias :: Range -> Text -> Parser (TableAlias Range)
+makeTableAlias r alias = TableAlias r alias . TableAliasId <$> getNextCounter
+
+makeColumnAlias :: Range -> Text -> Parser (ColumnAlias Range)
+makeColumnAlias r alias = ColumnAlias r alias . ColumnAliasId <$> getNextCounter
+
+makeDummyAlias :: Range -> Integer -> Parser (ColumnAlias Range)
+makeDummyAlias r idx = makeColumnAlias r $ TL.pack $ "_col" ++ show idx
+
+makeExprAlias :: Expr RawNames Range -> Integer -> Parser (ColumnAlias Range)
+makeExprAlias (BinOpExpr info _ _ _) idx = makeDummyAlias info idx
+makeExprAlias (UnOpExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (LikeExpr info _ _ _ _) idx = makeDummyAlias info idx
+makeExprAlias (CaseExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (ColumnExpr info (QColumnName _ _ name)) _ = makeColumnAlias info name
+makeExprAlias (ConstantExpr info _) idx = makeDummyAlias info idx
+makeExprAlias (InListExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (InSubqueryExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (BetweenExpr info _ _ _) idx = makeDummyAlias info idx
+makeExprAlias (OverlapsExpr _ _ _) _ = fail "Unsupported overlaps expr in Presto: unused expr-type in this dialect"
+makeExprAlias (AtTimeZoneExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (FunctionExpr info _ _ _ _ _ _) idx = makeDummyAlias info idx
+makeExprAlias (SubqueryExpr info _) idx = makeDummyAlias info idx
+makeExprAlias (ArrayExpr info _) idx = makeDummyAlias info idx
+makeExprAlias (ExistsExpr info _) idx = makeDummyAlias info idx
+makeExprAlias (FieldAccessExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (ArrayAccessExpr info _ _) idx = makeDummyAlias info idx
+makeExprAlias (TypeCastExpr _ _ expr _) idx = makeExprAlias expr idx
+makeExprAlias (VariableSubstitutionExpr _) _ = fail "Unsupported variable substitution in Presto: unused expr-type in this dialect"
+
+
+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
+
+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
+
+
+exprP :: Parser (Expr RawNames Range)
+exprP = orExprP
+
+orExprP :: Parser (Expr RawNames Range)
+orExprP = andExprP `chainl1` (Tok.orP >>= \ r -> return (BinOpExpr r "OR"))
+
+andExprP :: Parser (Expr RawNames Range)
+andExprP = notExprP `chainl1`
+    (Tok.andP >>= \ r -> return $ BinOpExpr r "AND")
+
+notP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+notP = (\ r -> UnOpExpr r "NOT") <$> Tok.notP
+
+notExprP :: Parser (Expr RawNames Range)
+notExprP = do
+    nots <- appEndo . fold . reverse . map Endo <$> many notP
+    expr <- predicatedExprP
+    return $ nots expr
+
+
+predicatedExprP :: Parser (Expr RawNames Range)
+predicatedExprP = do
+    value <- valueExprP
+    predicate <- option id predicateP
+    return $ predicate value
+
+valueExprP :: Parser (Expr RawNames Range)
+valueExprP = concatExprP
+
+concatExprP :: Parser (Expr RawNames Range)
+concatExprP = sumExprP `chainl1` binOpP "||"
+
+sumExprP :: Parser (Expr RawNames Range)
+sumExprP = productExprP `chainl1` opP
+  where
+    opP = choice $ map binOpP ["+", "-"]
+
+productExprP :: Parser (Expr RawNames Range)
+productExprP = negateExprP `chainl1` opP
+  where
+    opP = choice $ map binOpP ["*", "/", "%"]
+
+negateExprP :: Parser (Expr RawNames Range)
+negateExprP = do
+    neg <- option id $ choice $ map unOpP ["+", "-"]
+    expr <- atTimeZoneExprP
+    return $ neg expr
+
+intervalP :: Parser (Expr RawNames Range)
+intervalP = do
+    r <- Tok.intervalP
+    sign <- option [] $ pure <$> signP
+    str <- stringConstantP
+    from <- intervalFieldP
+    maybeTo <- optionMaybe $ Tok.toP >> intervalFieldP
+
+    let (info, attrs) =
+            case maybeTo of
+                Nothing -> (r <> getInfo from, sign ++ [from])
+                Just to -> (r <> getInfo to, sign ++ [from, to])
+
+    return $ TypeCastExpr info CastFailureError str $ PrimitiveDataType info "interval" attrs
+  where
+    signP :: Parser (DataTypeParam Range)
+    signP = do
+        (sign, r) <- choice [ ("+",) <$> Tok.symbolP "+"
+                            , ("-",) <$> Tok.symbolP "-"
+                            ]
+        pure $ DataTypeParamConstant $ StringConstant r sign
+
+    intervalFieldP :: Parser (DataTypeParam Range)
+    intervalFieldP = do
+        (field, r) <- Tok.intervalFieldP
+        pure $ DataTypeParamConstant $ StringConstant r $ TL.encodeUtf8 field
+
+atTimeZoneExprP :: Parser (Expr RawNames Range)
+atTimeZoneExprP = foldl (flip ($)) <$> primaryExprP <*> many atTimeZoneP
+  where
+    atTimeZoneP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+    atTimeZoneP = do
+        _ <- Tok.atP
+        _ <- Tok.timezoneP
+        tz <- choice [stringConstantP, intervalP]
+
+        return $ \ expr ->
+            AtTimeZoneExpr (getInfo expr <> getInfo tz) expr tz
+
+stringConstantP :: Parser (Expr RawNames Range)
+stringConstantP = do
+    (str, r) <- Tok.stringP
+    return $ ConstantExpr r (StringConstant r str)
+
+
+primaryExprP :: Parser (Expr RawNames Range)
+primaryExprP = foldl (flip ($)) <$> baseP <*> many (arrayAccessP <|> structAccessP)
+  where
+    baseP = choice
+        [ extractPrimaryExprP
+        , normalizePrimaryExprP
+        , try substringPrimaryExprP -- try is because `substring` is both a special-form function and a regular function
+        , try positionPrimaryExprP -- try is because `position` could be a column name / UDF function name
+        , bareFuncPrimaryExprP
+        , arrayPrimaryExprP
+        , castPrimaryExprP
+        , casePrimaryExprP
+        , existsPrimaryExprP
+        , try subqueryPrimaryExprP -- try is for the parens (for implicitRow)
+        , implicitRowPrimaryExprP
+        , try rowPrimaryExprP -- try is for the identifier
+        , try functionCallPrimaryExprP -- try is for the identifier (for columnRef)
+        , parameterPrimaryExprP
+        , binaryLiteralPrimaryExprP
+        , intervalP
+        , try $ constantPrimaryExprP
+        , columnRefPrimaryExprP
+        ]
+
+extractPrimaryExprP :: Parser (Expr RawNames Range)
+extractPrimaryExprP = do
+    -- https://prestodb.io/docs/current/functions/datetime.html
+    r <- Tok.extractP
+    _ <- Tok.openP
+    unit <- unitP
+    _ <- Tok.fromP
+    expr <- valueExprP
+    r' <- Tok.closeP
+    return $ FunctionExpr (r <> r') (QFunctionName r Nothing "extract") notDistinct [unit, expr] [] Nothing Nothing
+  where
+    unitP = do
+        (unit, r) <- Tok.extractUnitP
+        return $ ConstantExpr r $ StringConstant r $ TL.encodeUtf8 unit
+
+normalizePrimaryExprP :: Parser (Expr RawNames Range)
+normalizePrimaryExprP = do
+    -- https://prestodb.io/docs/current/functions/string.html
+    r <- Tok.normalizeP
+    _ <- Tok.openP
+    strExpr <- valueExprP
+    (form, formR) <- option ("nfc", getInfo strExpr) $ Tok.commaP >> Tok.normalFormP
+    r' <- Tok.closeP
+
+    let formExpr = ConstantExpr formR $ StringConstant formR $ TL.encodeUtf8 form
+    return $ FunctionExpr (r <> r') (QFunctionName r Nothing "normalize") notDistinct [strExpr, formExpr] [] Nothing Nothing
+
+substringPrimaryExprP :: Parser (Expr RawNames Range)
+substringPrimaryExprP = do
+    r <- Tok.substringP
+    _ <- Tok.openP
+    str <- valueExprP
+    _ <- Tok.fromP
+    start <- valueExprP
+    maybeLen <- optionMaybe $ Tok.forP >> valueExprP
+    r' <- Tok.closeP
+
+    let args = catMaybes [Just str, Just start, maybeLen]
+    return $ FunctionExpr (r <> r') (QFunctionName r Nothing "substring") notDistinct args [] Nothing Nothing
+
+bareFuncPrimaryExprP :: Parser (Expr RawNames Range)
+bareFuncPrimaryExprP = do
+    (func, r, alwaysBare) <- Tok.possiblyBareFuncP
+    let name = QFunctionName r Nothing func
+    (args, r') <- if alwaysBare
+                  then pure ([], r)
+                  else option ([], r) precisionP
+    return $ FunctionExpr (r <> r') name notDistinct args [] Nothing Nothing
+
+  where
+    precisionP = do
+        s <- Tok.openP
+        numExpr <- numberExprP
+        e <- Tok.closeP
+        return ([numExpr], s <> e)
+
+arrayPrimaryExprP :: Parser (Expr RawNames Range)
+arrayPrimaryExprP = do
+    r <- Tok.arrayP
+    _ <- Tok.openBracketP
+    exprs <- exprP `sepBy1` Tok.commaP
+    r' <- Tok.closeBracketP
+    return $ ArrayExpr (r <> r') exprs
+
+dataTypeP :: Parser (DataType Range)
+dataTypeP = foldl (flip ($)) <$> typeP <*> many arraySuffixP
+  where
+    typeP :: Parser (DataType Range)
+    typeP = choice [ arrayTypeP
+                   , mapTypeP
+                   , rowTypeP
+                   , baseTypeP
+                   ]
+
+    arraySuffixP :: Parser (DataType Range -> DataType Range)
+    arraySuffixP = ArrayDataType <$> Tok.arrayP
+
+    baseTypeP = do
+        (name, r) <- Tok.typeNameP
+        let typeParameterP = choice
+                [ DataTypeParamConstant <$> numberConstantP
+                , DataTypeParamType <$> dataTypeP
+                ]
+        args <- option [] $ P.between Tok.openP Tok.closeP $ typeParameterP `sepBy1` Tok.commaP
+        return $ PrimitiveDataType r name args
+
+    arrayTypeP = do
+        s <- Tok.arrayP
+        _ <- Tok.openAngleP
+        itemType <- dataTypeP
+        e <- Tok.closeAngleP
+        return $ ArrayDataType (s <> e) itemType
+
+    mapTypeP = do
+        s <- Tok.mapP
+        _ <- Tok.openAngleP
+        keyType <- dataTypeP
+        _ <- Tok.commaP
+        valueType <- dataTypeP
+        e <- Tok.closeAngleP
+        return $ MapDataType (s <> e) keyType valueType
+
+    rowTypeP = do
+        s <- Tok.rowP
+        _ <- Tok.openP
+        let fieldP = do
+              (name, _) <- Tok.structFieldNameP
+              type_ <- dataTypeP
+              return (name, type_)
+        fields <- fieldP `sepBy1` Tok.commaP
+        e <- Tok.closeP
+        return $ StructDataType (s <> e) fields
+
+
+castPrimaryExprP :: Parser (Expr RawNames Range)
+castPrimaryExprP = do
+    (onFail, r) <- Tok.castFuncP
+    _ <- Tok.openP
+    e <- exprP
+    _ <- Tok.asP
+    t <- dataTypeP
+    r' <- Tok.closeP
+
+    return $ TypeCastExpr (r <> r') onFail e t
+
+casePrimaryExprP :: Parser (Expr RawNames Range)
+casePrimaryExprP = do
+    r <- Tok.caseP
+    whens <- choice
+        [ P.many1 $ do
+            _ <- Tok.whenP
+            condition <- exprP
+            _ <- Tok.thenP
+            result <- exprP
+            return (condition, result)
+
+        , do
+            value <- valueExprP
+            P.many1 $ do
+                whenr <- Tok.whenP
+                condition <- BinOpExpr whenr "=" value <$> exprP
+                _ <- Tok.thenP
+                result <- exprP
+                return (condition, result)
+        ]
+
+    melse <- optionMaybe $ do
+        _ <- Tok.elseP
+        exprP
+
+    r' <- Tok.endP
+
+    return $ CaseExpr (r <> r') whens melse
+
+existsPrimaryExprP :: Parser (Expr RawNames Range)
+existsPrimaryExprP = do
+    r <- Tok.existsP
+    _ <- Tok.openP
+    query <- queryP
+    r' <- Tok.closeP
+    return $ ExistsExpr (r <> r') query
+
+subqueryPrimaryExprP :: Parser (Expr RawNames Range)
+subqueryPrimaryExprP = P.between Tok.openP Tok.closeP $ do
+    query <- queryP
+    return $ SubqueryExpr (getInfo query) query
+
+functionCallPrimaryExprP :: Parser (Expr RawNames Range)
+functionCallPrimaryExprP = do
+    name@(QFunctionName r _ _) <- functionNameP
+    (distinct, args) <- P.between Tok.openP Tok.closeP $ choice
+        [ do
+              r' <- Tok.starP
+              return (notDistinct, [ConstantExpr r' $ NumericConstant r' "1"])
+        , do
+              isDistinct <- option notDistinct distinctP
+              exprs <- exprP `sepBy` Tok.commaP
+              return (isDistinct, exprs)
+        ]
+    let params = []
+    filter' <- optionMaybe filterP
+    over <- optionMaybe overP
+
+    let info :: Range
+        info = sconcat $ r :| concat [ maybe r getInfo filter' : []
+                                     , maybe r getInfo over : []
+                                     , map getInfo args
+                                     ]
+    return $ FunctionExpr info name distinct args params filter' over
+  where
+    filterP = do
+        r <- Tok.filterP
+        _ <- Tok.openP
+        _ <- Tok.whereP
+        expr <- exprP
+        r' <- Tok.closeP
+        return $ Filter (r <> r') expr
+
+overP :: Parser (OverSubExpr RawNames Range)
+overP = do
+    start <- Tok.overP
+    _ <- Tok.openP
+    partition <- optionMaybe partitionP
+    order <- option [] orderInWindowClauseP
+    frame <- optionMaybe frameP
+    end <- Tok.closeP
+    let info = start <> end
+    return $ OverWindowExpr info $ WindowExpr info partition order frame
+  where
+    partitionP :: Parser (Partition RawNames Range)
+    partitionP = do
+        r <- Tok.partitionP
+        _ <- Tok.byP
+        exprs <- exprP `sepBy1` Tok.commaP
+        return $ PartitionBy (sconcat $ r :| map getInfo exprs) exprs
+
+    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
+
+        , numberConstantP >>= \ expr -> choice
+            [ Tok.precedingP >>= \ r ->
+                return $ Preceding (getInfo expr <> r) expr
+
+            , Tok.followingP >>= \ r ->
+                return $ Following (getInfo expr <> r) expr
+            ]
+        ]
+
+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'
+            ]
+
+rowPrimaryExprP :: Parser (Expr RawNames Range)
+rowPrimaryExprP = do
+    r <- Tok.rowP
+    _ <- Tok.openP
+    exprs <- exprP `sepBy1` Tok.commaP
+    r' <- Tok.closeP
+
+    let name = QFunctionName r Nothing "row"
+    return $ FunctionExpr (r <> r') name notDistinct exprs [] Nothing Nothing
+
+implicitRowPrimaryExprP :: Parser (Expr RawNames Range)
+implicitRowPrimaryExprP = do
+    r <- Tok.openP
+    exprs <- exprP `sepBy1` Tok.commaP
+    r' <- Tok.closeP
+
+    case exprs of
+        [] -> error "this shouldn't happen with sepBy1"
+        [e] -> return e
+        es -> let name = QFunctionName r Nothing "implicit row"
+               in return $ FunctionExpr (r <> r') name notDistinct es [] Nothing Nothing
+
+positionPrimaryExprP :: Parser (Expr RawNames Range)
+positionPrimaryExprP = do
+    r <- Tok.positionP
+    _ <- Tok.openP
+    substring <- valueExprP
+    _ <- Tok.inP
+    string <- valueExprP
+    r' <- Tok.closeP
+    return $ FunctionExpr (r <> r') (QFunctionName r Nothing "position") notDistinct [substring, string] [] Nothing Nothing
+
+parameterPrimaryExprP :: Parser (Expr RawNames Range)
+parameterPrimaryExprP = do
+    r <- Tok.questionMarkP
+    return $ ConstantExpr r $ ParameterConstant r
+
+binaryLiteralPrimaryExprP :: Parser (Expr RawNames Range)
+binaryLiteralPrimaryExprP = do
+    (bytes, r) <- Tok.binaryLiteralP
+    return $ ConstantExpr r $ StringConstant r bytes
+
+constantPrimaryExprP :: Parser (Expr RawNames Range)
+constantPrimaryExprP = do
+  val <- choice [ stringP
+                , booleanP
+                , numberConstantP
+                , typedConstantP
+                , nullP
+                ]
+  return $ ConstantExpr (getInfo val) val
+  where
+    stringP = uncurry (flip StringConstant) <$> Tok.stringP
+
+    booleanP = uncurry (flip BooleanConstant) <$> choice
+        [ Tok.trueP >>= \ r -> return (True, r)
+        , Tok.falseP >>= \ r -> return (False, r)
+        ]
+
+    typedConstantP = do
+        (typeStr, r) <- Tok.typedConstantTypeP
+        let dataType = PrimitiveDataType r typeStr []
+        (string, r') <- first TL.decodeUtf8 <$> Tok.stringP
+        pure $ TypedConstant (r <> r') string dataType
+
+    nullP = NullConstant <$> Tok.nullP
+
+numberExprP :: Parser (Expr RawNames Range)
+numberExprP = do
+    num <- numberConstantP
+    return $ ConstantExpr (getInfo num) num
+
+numberConstantP :: Parser (Constant Range)
+numberConstantP = uncurry (flip NumericConstant) <$> Tok.numberP
+
+columnRefPrimaryExprP :: Parser (Expr RawNames Range)
+columnRefPrimaryExprP = do
+    name <- columnNameP
+    return $ ColumnExpr (getInfo name) name
+
+structAccessP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+structAccessP = do
+    _ <- Tok.dotP
+    field <- structFieldNameP
+    return $ \ struct ->
+        let r = getInfo struct <> getInfo field
+         in FieldAccessExpr r struct field
+  where
+    structFieldNameP :: Parser (StructFieldName Range)
+    structFieldNameP = uncurry (flip StructFieldName) <$> Tok.structFieldNameP
+
+arrayAccessP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+arrayAccessP = do
+    _ <- Tok.openBracketP
+    index <- valueExprP
+    e <- Tok.closeBracketP
+    return $ \ expr ->
+        let exprR = getInfo expr <> e
+        in ArrayAccessExpr exprR expr index
+
+
+predicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+predicateP = choice
+    [ isPredicateP
+    , try likePredicateP  -- the try is for the optional NOT
+    , try inPredicateP  -- the try is for the optional NOT
+    , betweenPredicateP
+    , try quantifiedComparisonPredicateP -- the try is for the comparison operator
+    , unquantifiedComparisonPredicateP
+    ]
+
+optionalNotWrapper :: Parser (Expr RawNames Range -> Expr RawNames Range)
+optionalNotWrapper = do
+    maybeNot <- optionMaybe Tok.notP
+    return $ maybe id (\ r -> UnOpExpr r "NOT") maybeNot
+
+isPredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+isPredicateP = do
+    r <- Tok.isP
+
+    notWrapper <- optionalNotWrapper
+    predicate <- choice [ Left <$> (Tok.distinctP >> Tok.fromP >> valueExprP)
+                        , Right <$> Tok.nullP
+                        ]
+    return $ case predicate of
+        Left expr ->
+            \ expr' -> notWrapper $ BinOpExpr (r <> getInfo expr) "IS DISTINCT FROM" expr expr'
+        Right nullKeyword ->
+            \ expr' -> notWrapper $ UnOpExpr (r <> nullKeyword) "ISNULL" expr'
+
+likePredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+likePredicateP = do
+    notWrapper <- optionalNotWrapper
+    r <- Tok.likeP
+    pattern <- Pattern <$> valueExprP
+    escape <- optionMaybe $ do
+        _ <- Tok.escapeP
+        Escape <$> valueExprP
+    return $ \ expr -> notWrapper $ LikeExpr r "LIKE" escape pattern expr
+
+inPredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+inPredicateP = do
+    notWrapper <- optionalNotWrapper
+    r <- Tok.inP
+    _ <- Tok.openP
+    predicate <- choice
+        [ Left <$> queryP
+        , Right <$> exprP `sepBy1` Tok.commaP
+        ]
+    r' <- Tok.closeP
+
+    return $ case predicate of
+        Left query ->
+            \ expr -> notWrapper $ InSubqueryExpr (r <> r') query expr
+        Right exprs ->
+            \ expr -> notWrapper $ InListExpr (r <> r') exprs expr
+
+betweenPredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+betweenPredicateP = do
+    notWrapper <- optionalNotWrapper
+    _ <- Tok.betweenP
+    lower <- valueExprP
+    _ <- Tok.andP
+    upper <- valueExprP
+    return $ \ expr -> notWrapper $ BetweenExpr (getInfo expr <> getInfo upper) lower upper expr
+
+
+quantifiedComparisonPredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+quantifiedComparisonPredicateP = do
+    (op, _) <- Tok.comparisonOperatorP
+    (quantifier, _) <- Tok.comparisonQuantifierP
+    _ <- Tok.openP
+    query <- queryP
+    r <- Tok.closeP
+
+    let op' = Operator $ TL.unwords [op, quantifier]
+        subquery = SubqueryExpr (getInfo query) query
+    return $ \ expr -> BinOpExpr (getInfo expr <> r) op' expr subquery
+
+unquantifiedComparisonPredicateP :: Parser (Expr RawNames Range -> Expr RawNames Range)
+unquantifiedComparisonPredicateP = do
+    (op, _) <- Tok.comparisonOperatorP
+    rhs <- valueExprP
+    return $ \ lhs -> BinOpExpr (getInfo lhs <> getInfo rhs) (Operator op) lhs rhs
+
+
+whereP :: Parser (SelectWhere RawNames Range)
+whereP = do
+    r <- Tok.whereP
+    condition <- exprP
+    return $ SelectWhere (r <> getInfo condition) condition
+
+
+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
+
+groupP :: Parser (SelectGroup RawNames Range)
+groupP = do
+    r <- Tok.groupP
+    _ <- Tok.byP
+    optional distinctP
+
+    selectGroupGroupingElements <- concat <$> groupingElementP `sepBy1` Tok.commaP
+    let selectGroupInfo = foldl (<>) r $ fmap getInfo selectGroupGroupingElements
+
+    return SelectGroup{..}
+  where
+    toGroupingElement :: PositionOrExpr RawNames Range -> GroupingElement RawNames Range
+    toGroupingElement posOrExpr = GroupingElementExpr (getInfo posOrExpr) posOrExpr
+
+    groupingElementP :: Parser [GroupingElement RawNames Range]
+    groupingElementP = choice
+        [ singleExprP
+        , parenExprsP
+        , rollupP
+        , cubeP
+        , groupingSetsP
+        ]
+
+    singleExprP = do
+        e <- exprP
+        return [toGroupingElement $ handlePositionalReferences e]
+
+    parenExprsP = do
+        _ <- Tok.openP
+        es <- exprP `sepBy` Tok.commaP
+        _ <- Tok.closeP
+        return $ map (toGroupingElement . handlePositionalReferences) es
+
+    toGroupingSet :: Range -> [Expr RawNames Range] -> GroupingElement RawNames Range
+    toGroupingSet r [] = GroupingElementSet r []
+    toGroupingSet _ exprs =
+        let s = getInfo $ head exprs
+            e = getInfo $ last exprs
+         in GroupingElementSet (s <> e) exprs
+
+    rollupP = do
+        _ <- Tok.rollupP
+        _ <- Tok.openP
+        cols <- columnRefPrimaryExprP `sepBy1` Tok.commaP
+        _ <- Tok.closeP
+        let dimensions = L.reverse $ L.inits cols
+            defaultRange = (getInfo $ head cols) <> (getInfo $ last cols)
+        return $ map (toGroupingSet defaultRange) dimensions
+
+    cubeP = do
+        _ <- Tok.cubeP
+        _ <- Tok.openP
+        cols <- columnRefPrimaryExprP `sepBy1` Tok.commaP
+        _ <- Tok.closeP
+        let dimensions = L.subsequences cols
+            defaultRange = (getInfo $ head cols) <> (getInfo $ last cols)
+        return $ map (toGroupingSet defaultRange) dimensions
+
+    groupingSetP = choice $
+        [ do
+              s <- Tok.openP
+              sets <- columnRefPrimaryExprP `sepBy1` Tok.commaP
+              e <- Tok.closeP
+              return $ GroupingElementSet (s <> e) sets
+        , do
+              -- if no parens, it will be the singleton list.
+              col <- columnRefPrimaryExprP
+              return $ GroupingElementSet (getInfo col) [col]
+        ]
+
+    groupingSetsP = do
+        _ <- Tok.groupingP
+        _ <- Tok.setsP
+        _ <- Tok.openP
+        sets <- groupingSetP `sepBy1` Tok.commaP
+        _ <- Tok.closeP
+        return sets
+
+
+havingP :: Parser (SelectHaving RawNames Range)
+havingP = do
+    r <- Tok.havingP
+    condition <- exprP
+    return $ SelectHaving (r <> getInfo condition) [condition]
+
+
+deleteP :: Parser (Delete RawNames Range)
+deleteP = do
+    r <- Tok.deleteP
+
+    _ <- Tok.fromP
+    table <- tableNameP
+
+    maybeExpr <- optionMaybe $ do
+        _ <- Tok.whereP
+        local (introduceAliases $ tableNameToTableAlias table) exprP
+
+    let r' = case maybeExpr of
+          Nothing -> getInfo table
+          Just expr -> getInfo expr
+        info = r <> r'
+
+    pure $ Delete info table maybeExpr
+
+
+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
+    table <- tableNameP
+
+    let dropTableInfo = s <> getInfo table
+        dropTableNames = table :| []
+    pure DropTable{..}
+
+ifExistsP :: Parser Range
+ifExistsP = do
+    s <- Tok.ifP
+    e <- Tok.existsP
+    pure $ s <> e
+
+
+grantP :: Parser (Grant Range)
+grantP = do
+    s <- Tok.grantP
+    e <- P.many1 Tok.notSemicolonP
+    return $ Grant (s <> (last e))
+
+
+revokeP :: Parser (Revoke Range)
+revokeP = do
+    s <- Tok.revokeP
+    e <- P.many1 Tok.notSemicolonP
+    return $ Revoke (s <> (last e))
+
+
+insertP :: Parser (Insert RawNames Range)
+insertP = do
+    r <- Tok.insertP
+    insertBehavior <- InsertAppend <$> Tok.intoP
+
+    insertTable <- tableNameP
+
+    insertColumns <- optionMaybe $ try $ do
+        _ <- Tok.openP
+        let oqColumnNameP = (\ (c, r') -> QColumnName r' Nothing c) <$> Tok.columnNameP
+        c:cs <- oqColumnNameP `sepBy1` Tok.commaP
+        _ <- Tok.closeP
+        pure (c :| cs)
+
+    insertValues <- choice
+        [ do
+            s <- Tok.valuesP
+            (e, rows) <- rowsOfValuesP
+            pure $ InsertExprValues (s <> e) rows
+        , InsertSelectValues <$> queryP
+        ]
+
+    let insertInfo = r <> getInfo insertValues
+
+    pure Insert{..}
+  where
+    valueP :: Parser (DefaultExpr RawNames Range)
+    valueP = ExprValue <$> exprP
+
+    rowOfValuesP = do
+        s <- Tok.openP
+        x:xs <- valueP `sepBy1` Tok.commaP
+        e <- Tok.closeP
+        pure $ (s <> e, x :| xs)
+
+    rowsOfValuesP = do
+        rows <- rowOfValuesP `sepBy1` Tok.commaP
+        let infos = map fst rows
+            r:rs = map snd rows
+        pure $ (head infos <> last infos, r :| rs)
+
+
+explainP :: Parser Range
+explainP = do
+    s <- Tok.explainP
+    e <- getInfo <$> queryP
+    return $ s <> e
+
+
+showP :: Parser Range
+showP = do
+    s <- Tok.showP
+    e <- P.many1 Tok.notSemicolonP
+    return $ s <> (last e)
+
+
+callP :: Parser Range
+callP = do
+    s <- Tok.callP
+    e <- P.many1 Tok.notSemicolonP
+    return $ s <> (last e)
+
+
+describeP :: Parser Range
+describeP = do
+    s <- Tok.describeP
+    e <- P.many1 Tok.notSemicolonP
+    return $ s <> last e
diff --git a/src/Database/Sql/Presto/Parser/Internal.hs b/src/Database/Sql/Presto/Parser/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Parser/Internal.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+module Database.Sql.Presto.Parser.Internal where
+
+import qualified Text.Parsec as P
+
+import Database.Sql.Presto.Token
+import Database.Sql.Position
+import Control.Monad.Reader
+import Data.Text.Lazy (Text)
+import Data.Set (Set)
+
+type ScopeTableRef = Text
+
+data ParserScope = ParserScope
+    { selectTableAliases :: Maybe (Set ScopeTableRef) }
+    deriving (Eq, Ord, Show)
+
+type Parser = P.ParsecT [(Token, Position, Position)] Integer (Reader ParserScope)
+
+getNextCounter :: Parser Integer
+getNextCounter = P.modifyState (+1) >> P.getState
diff --git a/src/Database/Sql/Presto/Parser/Token.hs b/src/Database/Sql/Presto/Parser/Token.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Parser/Token.hs
@@ -0,0 +1,638 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+module Database.Sql.Presto.Parser.Token where
+
+import Database.Sql.Type.Query (CastFailureAction(..))
+import Database.Sql.Presto.Token
+import Database.Sql.Presto.Parser.Internal
+
+import Database.Sql.Position
+
+import qualified Text.Parsec as P
+import qualified Text.Parsec.Pos as P
+
+import           Data.ByteString.Lazy (ByteString)
+import           Data.Text.Lazy (Text)
+import qualified Data.Text.Lazy as TL
+
+import qualified Data.Map as M
+import Data.Semigroup ((<>))
+
+
+-- helpers
+
+showTok :: (Token, Position, Position) -> String
+showTok (t, _, _) = show t
+
+posFromTok :: P.SourcePos ->
+              (Token, Position, Position) ->
+              [(Token, Position, Position)] ->
+              P.SourcePos
+posFromTok _ (_, pos, _) _ = flip P.setSourceLine (fromEnum $ positionLine pos)
+                           $ flip P.setSourceColumn (fromEnum $ positionColumn pos)
+                           $ P.initialPos "-"
+
+tokEqualsP :: Token -> Parser Range
+tokEqualsP tok = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok', s, e) =
+        if tok == tok'
+         then Just $ Range s e
+         else Nothing
+
+tokNotEqualsP :: Token -> Parser Range
+tokNotEqualsP tok = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok', s, e) =
+        if tok /= tok'
+         then Just $ Range s e
+         else Nothing
+
+testNameTok :: (Token, Position, Position) -> Maybe (Text, Range)
+testNameTok (tok, s, e) =
+  case tok of
+    TokWord _ name -> Just (name, Range s e)
+    _ -> Nothing
+
+symbolP :: Text -> Parser Range
+symbolP op = tokEqualsP $ TokSymbol op
+
+keywordP :: Text -> Parser Range
+keywordP keyword = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord False name
+            | name == keyword -> Just (Range s e)
+
+        _ -> Nothing
+
+-- onto the actual parsers
+
+dotP :: Parser Range
+dotP = symbolP "."
+
+starP :: Parser Range
+starP = symbolP "*"
+
+plusP :: Parser Range
+plusP = symbolP "+"
+
+minusP :: Parser Range
+minusP = symbolP "-"
+
+commaP :: Parser Range
+commaP = symbolP ","
+
+openP :: Parser Range
+openP = symbolP "("
+
+closeP :: Parser Range
+closeP = symbolP ")"
+
+openBracketP :: Parser Range
+openBracketP = symbolP "["
+
+closeBracketP :: Parser Range
+closeBracketP = symbolP "]"
+
+openAngleP :: Parser Range
+openAngleP = symbolP "<"
+
+closeAngleP :: Parser Range
+closeAngleP = symbolP ">"
+
+questionMarkP :: Parser Range
+questionMarkP = symbolP "?"
+
+stringP :: Parser (ByteString, Range)
+stringP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokString string -> Just (string, Range s e)
+        _ -> Nothing
+
+binaryLiteralP :: Parser (ByteString, Range)
+binaryLiteralP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokBinary bytes -> Just (bytes, Range s e)
+        _ -> Nothing
+
+numberP :: Parser (Text, Range)
+numberP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokNumber number -> Just (number, Range s e)
+        _ -> Nothing
+
+typeNameP :: Parser (Text, Range)
+typeNameP = P.choice
+    [ P.try $ multiWordTypeP -- the try is for plain old `double`, `time`, or `timestamp`
+    , singleWordTypeP
+    ]
+  where
+    multiWordTypeP = P.choice
+        [ do
+              s <- doubleP
+              e <- precisionP
+              return ("double precision", s <> e)
+        , do
+              r <- Left <$> timeP P.<|> Right <$> timestampP
+              _ <- withP
+              e <- timezoneP
+              case r of
+                  Left s -> return ("time with time zone", s <> e)
+                  Right s -> return ("timestamp with time zone", s <> e)
+        ]
+
+    singleWordTypeP = P.tokenPrim showTok posFromTok testNameTok
+
+intervalFieldP :: Parser (Text, Range)
+intervalFieldP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `elem` fields -> Just (TL.toLower name, Range s e)
+        _ -> Nothing
+
+    fields = ["year", "month", "day", "hour", "minute", "second"]
+
+comparisonOperatorP :: Parser (Text, Range)
+comparisonOperatorP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (TokSymbol op, s, e)
+        | op `elem` ["=", "<>", "!=", "<", "<=", ">", ">="] = Just (op, Range s e)
+
+    testTok _ = Nothing
+
+comparisonQuantifierP :: Parser (Text, Range)
+comparisonQuantifierP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `elem` quantifiers -> Just (TL.toLower name, Range s e)
+        _ -> Nothing
+
+    quantifiers = ["all", "some", "any"]
+
+extractUnitP :: Parser (Text, Range)
+extractUnitP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `elem` units -> Just (TL.toLower name, Range s e)
+        _ -> Nothing
+
+    units = [ "year", "quarter", "month", "week"
+            , "day", "day_of_month", "day_of_week", "dow", "day_of_year", "doy"
+            , "year_of_week", "yow"
+            , "hour", "minute", "second"
+            , "timezone_hour", "timezone_minute"
+            ]
+
+typedConstantTypeP :: Parser (Text, Range)
+typedConstantTypeP = P.choice
+    [ P.try $ multiWordTypeP -- the try is for plain old `double`
+    , singleWordTypeP
+    ]
+  where
+    multiWordTypeP = do
+        s <- doubleP
+        e <- precisionP
+        return ("double precision", s <> e)
+
+    singleWordTypeP = P.tokenPrim showTok posFromTok testTok
+
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `elem` types -> Just (TL.toLower name, Range s e)
+        _ -> Nothing
+
+    -- generated with some trial and error (and guesswork) based on
+    -- https://github.com/prestodb/presto/blob/master/presto-main/src/main/java/com/facebook/presto/type/TypeRegistry.java
+    types = [ "boolean", "bigint", "integer", "smallint", "tinyint"
+            , "double", "real", "varbinary", "date", "time", "timestamp"
+            , "hyperloglog", "p4hyperloglog", "joniregexp", "re2jregexp"
+            , "likepattern", "jsonpath", "color", "json", "codepoints"
+            , "varchar", "char", "decimal"
+            ]
+
+normalFormP :: Parser (Text, Range)
+normalFormP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `elem` forms -> Just (TL.toLower name, Range s e)
+        _ -> Nothing
+
+    forms = [ "nfc", "nfd", "nfkc", "nfkd" ]
+
+possiblyBareFuncP :: Parser (Text, Range, Bool)
+possiblyBareFuncP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name `M.member` funcs ->
+                           let name' = TL.toLower name
+                               r = Range s e
+                               Just alwaysBare = M.lookup name' funcs
+                            in Just (name', r, alwaysBare)
+        _ -> Nothing
+
+    funcs = M.fromList [ ("current_date",      True)
+                       , ("current_time",      False)
+                       , ("current_timestamp", False)
+                       , ("localtime",         False)
+                       , ("localtimestamp",    False)
+                       ]
+
+castFuncP :: Parser (CastFailureAction, Range)
+castFuncP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord _ name | TL.toLower name == "cast" -> Just (CastFailureError, Range s e)
+        TokWord _ name | TL.toLower name == "try_cast" -> Just (CastFailureToNull, Range s e)
+        _ -> Nothing
+
+
+-- In presto, databases are called "catalogs".
+-- Notionally they are "one coarser than schema"
+databaseNameP :: Parser (Text, Range)
+databaseNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeSchemaName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+schemaNameP :: Parser (Text, Range)
+schemaNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeSchemaName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+tableNameP :: Parser (Text, Range)
+tableNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeTableName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+columnNameP :: Parser (Text, Range)
+columnNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeColumnName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+structFieldNameP :: Parser (Text, Range)
+structFieldNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeColumnName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+paramNameP :: Parser (Text, Range)
+paramNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeColumnName (wordInfo name) -> Just (name, Range s e)
+
+        _ -> Nothing
+
+functionNameP :: Parser (Text, Range)
+functionNameP = P.tokenPrim showTok posFromTok testTok
+  where
+    testTok (tok, s, e) = case tok of
+        TokWord True name -> Just (name, Range s e)
+        TokWord False name
+            | wordCanBeFunctionName (wordInfo name) ->
+                Just (name, Range s e)
+
+        _ -> Nothing
+
+allP :: Parser Range
+allP = keywordP "all"
+
+andP :: Parser Range
+andP = keywordP "and"
+
+arrayP :: Parser Range
+arrayP = keywordP "array"
+
+asP :: Parser Range
+asP = keywordP "as"
+
+ascP :: Parser Range
+ascP = keywordP "asc"
+
+atP :: Parser Range
+atP = keywordP "at"
+
+bernoulliP :: Parser Range
+bernoulliP = keywordP "bernoulli"
+
+betweenP :: Parser Range
+betweenP = keywordP "between"
+
+byP :: Parser Range
+byP = keywordP "by"
+
+callP :: Parser Range
+callP = keywordP "call"
+
+caseP :: Parser Range
+caseP = keywordP "case"
+
+crossP :: Parser Range
+crossP = keywordP "cross"
+
+cubeP :: Parser Range
+cubeP = keywordP "cube"
+
+currentP :: Parser Range
+currentP = keywordP "current"
+
+deleteP :: Parser Range
+deleteP = keywordP "delete"
+
+descP :: Parser Range
+descP = keywordP "desc"
+
+describeP :: Parser Range
+describeP = keywordP "describe" P.<|> keywordP "desc"
+
+distinctP :: Parser Range
+distinctP = keywordP "distinct"
+
+doubleP :: Parser Range
+doubleP = keywordP "double"
+
+dropP :: Parser Range
+dropP = keywordP "drop"
+
+elseP :: Parser Range
+elseP = keywordP "else"
+
+endP :: Parser Range
+endP = keywordP "end"
+
+escapeP :: Parser Range
+escapeP = keywordP "escape"
+
+exceptP :: Parser Range
+exceptP = keywordP "except"
+
+existsP :: Parser Range
+existsP = keywordP "exists"
+
+explainP :: Parser Range
+explainP = keywordP "explain"
+
+extractP :: Parser Range
+extractP = keywordP "extract"
+
+falseP :: Parser Range
+falseP = keywordP "false"
+
+filterP :: Parser Range
+filterP = keywordP "filter"
+
+firstP :: Parser Range
+firstP = keywordP "first"
+
+followingP :: Parser Range
+followingP = keywordP "following"
+
+forP :: Parser Range
+forP = keywordP "for"
+
+fromP :: Parser Range
+fromP = keywordP "from"
+
+fullP :: Parser Range
+fullP = keywordP "full"
+
+grantP :: Parser Range
+grantP = keywordP "grant"
+
+groupP :: Parser Range
+groupP = keywordP "group"
+
+groupingP :: Parser Range
+groupingP = keywordP "grouping"
+
+havingP :: Parser Range
+havingP = keywordP "having"
+
+ifP :: Parser Range
+ifP = keywordP "if"
+
+intervalP :: Parser Range
+intervalP = keywordP "interval"
+
+inP :: Parser Range
+inP = keywordP "in"
+
+innerP :: Parser Range
+innerP = keywordP "inner"
+
+insertP :: Parser Range
+insertP = keywordP "insert"
+
+intersectP :: Parser Range
+intersectP = keywordP "intersect"
+
+intoP :: Parser Range
+intoP = keywordP "into"
+
+isP :: Parser Range
+isP = keywordP "is"
+
+joinP :: Parser Range
+joinP = keywordP "join"
+
+lastP :: Parser Range
+lastP = keywordP "last"
+
+leftP :: Parser Range
+leftP = keywordP "left"
+
+likeP :: Parser Range
+likeP = keywordP "like"
+
+limitP :: Parser Range
+limitP = keywordP "limit"
+
+mapP :: Parser Range
+mapP = keywordP "map"
+
+naturalP :: Parser Range
+naturalP = keywordP "natural"
+
+normalizeP :: Parser Range
+normalizeP = keywordP "normalize"
+
+notP :: Parser Range
+notP = keywordP "not"
+
+nullP :: Parser Range
+nullP = keywordP "null"
+
+nullsP :: Parser Range
+nullsP = keywordP "nulls"
+
+onP :: Parser Range
+onP = keywordP "on"
+
+orP :: Parser Range
+orP = keywordP "or"
+
+orderP :: Parser Range
+orderP = keywordP "order"
+
+ordinalityP :: Parser Range
+ordinalityP = keywordP "ordinality"
+
+outerP :: Parser Range
+outerP = keywordP "outer"
+
+overP :: Parser Range
+overP = keywordP "over"
+
+partitionP :: Parser Range
+partitionP = keywordP "partition"
+
+poissonizedP :: Parser Range
+poissonizedP = keywordP "poissonized"
+
+positionP :: Parser Range
+positionP = keywordP "position"
+
+precedingP :: Parser Range
+precedingP = keywordP "preceding"
+
+precisionP :: Parser Range
+precisionP = keywordP "precision"
+
+rangeP :: Parser Range
+rangeP = keywordP "range"
+
+revokeP :: Parser Range
+revokeP = keywordP "revoke"
+
+rightP :: Parser Range
+rightP = keywordP "right"
+
+rollupP :: Parser Range
+rollupP = keywordP "rollup"
+
+rowP :: Parser Range
+rowP = keywordP "row"
+
+rowsP :: Parser Range
+rowsP = keywordP "rows"
+
+selectP :: Parser Range
+selectP = keywordP "select"
+
+semicolonP :: Parser Range
+semicolonP = symbolP ";"
+
+notSemicolonP :: Parser Range
+notSemicolonP = tokNotEqualsP $ TokSymbol ";"
+
+setsP :: Parser Range
+setsP = keywordP "sets"
+
+showP :: Parser Range
+showP = keywordP "show"
+
+substringP :: Parser Range
+substringP = keywordP "substring"
+
+systemP :: Parser Range
+systemP = keywordP "system"
+
+tableP :: Parser Range
+tableP = keywordP "table"
+
+tableSampleP :: Parser Range
+tableSampleP = keywordP "tablesample"
+
+thenP :: Parser Range
+thenP = keywordP "then"
+
+timeP :: Parser Range
+timeP = keywordP "time"
+
+timestampP :: Parser Range
+timestampP = keywordP "timestamp"
+
+timezoneP :: Parser Range
+timezoneP = do
+    s <- keywordP "time"
+    e <- keywordP "zone"
+    pure $ s <> e
+
+toP :: Parser Range
+toP = keywordP "to"
+
+trueP :: Parser Range
+trueP = keywordP "true"
+
+unboundedP :: Parser Range
+unboundedP = keywordP "unbounded"
+
+unionP :: Parser Range
+unionP = keywordP "union"
+
+unnestP :: Parser Range
+unnestP = keywordP "unnest"
+
+usingP :: Parser Range
+usingP = keywordP "using"
+
+valuesP :: Parser Range
+valuesP = keywordP "values"
+
+viewP :: Parser Range
+viewP = keywordP "view"
+
+whenP :: Parser Range
+whenP = keywordP "when"
+
+whereP :: Parser Range
+whereP = keywordP "where"
+
+withP :: Parser Range
+withP = keywordP "with"
diff --git a/src/Database/Sql/Presto/Scanner.hs b/src/Database/Sql/Presto/Scanner.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Scanner.hs
@@ -0,0 +1,264 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+module Database.Sql.Presto.Scanner where
+
+import Prelude hiding ((&&), (||), not)
+
+import Control.Monad.State
+
+import           Data.Text.Lazy (Text)
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import           Data.ByteString.Lazy (ByteString)
+import qualified Data.ByteString.Builder as BB
+
+import Data.List (sortBy)
+import Data.Foldable (asum)
+import Data.Char (isAlphaNum, isAlpha, isSpace, isDigit, isHexDigit)
+import Data.Int (Int64)
+
+import Database.Sql.Position
+import Database.Sql.Presto.Token
+
+import Data.Predicate.Class
+
+import Numeric (readHex)
+
+
+isWordHead :: Char -> Bool
+isWordHead = isAlpha || (== '_')
+
+isWordBody :: Char -> Bool
+isWordBody = isAlphaNum || (== '_')
+
+isHSpace :: Char -> Bool
+isHSpace = isSpace && not (== '\n')
+
+operators :: [Text]
+operators = sortBy (flip compare)
+    [ "+", "-", "*", "/", "%"
+    , "!=", "<>", ">", "<", ">=", "<=", "="
+    , "||"
+    , "(", ")", "[", "]", ",", ";"
+    , "?"
+    ]
+
+isOperator :: Char -> Bool
+isOperator c = elem c $ map TL.head operators
+
+tokenize :: Text -> [(Token, Position, Position)]
+tokenize = go (Position 1 0 0)
+  where
+    go :: Position -> Text -> [(Token, Position, Position)]
+    go _ "" = []
+    go p t = case TL.head t of
+        c | c `elem` ['x', 'X'] && TL.length t >= 2 && TL.index t 1 == '\'' ->
+            case tokBinaryLiteral p $ TL.drop 2 t of
+                Left (err, p') -> [(TokError err, p, p')]
+                Right (bytes, rest, p') -> (TokBinary bytes, p, p') : go p' rest
+
+
+        c | isWordHead c || c == '"' ->
+            case tokName p t of
+                Left token -> [token]
+                Right (name, quoted, rest, p') -> (TokWord quoted name, p, p') : go p' rest
+
+        c | (== '\n') c -> let (newlines, rest) = TL.span (== '\n') t
+                               p' = advanceVertical (TL.length newlines) p
+                            in go p' rest
+
+        c | isHSpace c -> let (spaces, rest) = TL.span isHSpace t
+                              p' = advanceHorizontal (TL.length spaces) p
+                           in go p' rest
+
+        '-' | "--" `TL.isPrefixOf` t ->
+            let (comment, rest) = TL.span (/= '\n') t
+                p' = advanceVertical 1
+                        (advanceHorizontal (TL.length comment) p)
+
+             in go p' (TL.drop 1 rest)
+
+        '/' | "/*" `TL.isPrefixOf` t ->
+            case TL.breakOn "*/" t of
+                (comment, "") ->
+                    let p' = advance comment p
+                     in [(TokError "unterminated comment", p, p')]
+                (comment, rest) ->
+                    let p' = advance (TL.append comment "*/") p
+                     in go p' $ TL.drop 2 rest
+
+        '\'' ->
+            case tokString p '\'' t of
+                Left p' -> [(TokError "end of input inside string", p, p')]
+                Right (string, rest, p') -> (TokString $ TL.encodeUtf8 string, p, p') : go p' rest
+
+        '.' ->
+            case TL.span isDigit (TL.tail t) of
+                ("", _) ->
+                    let p' = advanceHorizontal 1 p
+                     in (TokSymbol ".", p, p') : go p' (TL.tail t)
+
+                _ ->
+                    let ((tok, len), _) = tokNumber t
+                        p' = advanceHorizontal len p
+                        rest = TL.drop len t
+                     in (tok, p, p') : go p' rest
+
+        c | isDigit c ->
+            let ((tok, len), _) = tokNumber t
+                p' = advanceHorizontal len p
+                rest = TL.drop len t
+             in (tok, p, p') : go p' rest
+
+        c | isOperator c -> case readOperator t of
+
+            Just (sym, rest) -> let p' = advanceHorizontal (TL.length sym) p
+                                 in (TokSymbol sym, p, p') : go p' rest
+
+            Nothing ->
+                let opchars = TL.take 5 t
+                    p' = advance opchars p
+                    message = unwords
+                        [ "unrecognized operator starting with"
+                        , show opchars
+                        ]
+                 in [(TokError message, p, p')]
+
+        c ->
+            let message = unwords
+                    [ "unmatched character (" ++ show c ++ ") at position"
+                    , show p
+                    ]
+
+             in [(TokError message, p, advanceHorizontal 1 p)]
+
+    readOperator t = asum
+        $ map (\ op -> (op,) <$> TL.stripPrefix op t) operators
+
+
+tokString :: Position -> Char -> Text -> Either Position (Text, Text, Position)
+tokString pos d = go (advanceHorizontal 1 pos) [] . TL.tail
+  where
+    go p _ "" = Left p
+    go p ts input = case TL.head input of
+         c | c == d ->
+            let (quotes, rest) = TL.span (== d) input
+                len = TL.length quotes
+                t = TL.take (len `div` 2) quotes
+            in if len `mod` 2 == 0
+                then go (advanceHorizontal len p) (t:ts) rest
+
+                else let str = TL.concat $ reverse $ t:ts
+                         p' = advanceHorizontal len p
+                      in Right (str, rest, p')
+
+         _ -> let (t, rest) = TL.span (/= d) input
+               in go (advance t p) (t:ts) rest
+
+
+tokBinaryLiteral :: Position -> Text -> Either (String, Position) (ByteString, Text, Position)
+tokBinaryLiteral p "" = Left ("end of input inside binary literal", p)
+tokBinaryLiteral p t =
+    let (body, rest) = TL.span (/= '\'')t
+        collapsed = TL.filter (not . isSpace) body
+        bad_digits = TL.filter (not . isHexDigit) collapsed
+        good_digits = TL.filter isHexDigit collapsed
+        has_even_num_digits = (TL.length good_digits) `mod` 2 == 0
+        p' = advanceHorizontal 2 $ advance body $ advanceHorizontal 1 p
+     in case () of
+          _ | TL.null rest -> Left ("end of input inside binary literal", p)
+          _ | not $ TL.null bad_digits -> Left ("binary literal must only have hex-digits", p')
+          _ | not has_even_num_digits -> Left ("binary literal must contain an even number of hex-digits", p')
+          _ -> Right (toByteString good_digits, TL.drop 1 rest, p')
+  where
+    toByteString :: Text -> ByteString
+    toByteString "" = ""
+    toByteString t' = let [(num, _)] = (readHex $ TL.unpack t') :: [(Int, String)]
+                      in BB.toLazyByteString $ BB.word8 $ fromIntegral num
+
+tokName :: Position -> Text -> Either (Token, Position, Position) (Text, Bool, Text, Position)
+tokName pos = go pos
+  where
+    go :: Position -> Text -> Either (Token, Position, Position) (Text, Bool, Text, Position)
+    go p ""  = error $ "unexpected call to tokName (at EOF) at " ++ show p
+    go p input = case TL.head input of
+      c | isWordHead c ->
+        let (body, rest) = TL.span isWordBody $ TL.tail input
+            word = TL.toLower $ TL.cons c body
+            p' = advanceHorizontal (TL.length word) p
+         in Right (word, False, rest, p')
+
+      c | c == '"' ->
+        case tokString p '"' input of
+            Left p' -> Left (TokError "end of input inside quoted name", p, p')
+            Right (quoted, rest, p') -> Right (quoted, True, rest, p')
+
+      _ -> error $ "unexpected call to tokName (not at word head) at " ++ show p
+
+tokNumber :: Text -> ((Token, Int64), Text)
+tokNumber = runState $ do
+    ipart <- state $ TL.span isDigit
+    gets (TL.take 1) >>= \case
+        "" -> pure (TokNumber ipart, TL.length ipart)
+        "." -> do
+            modify $ TL.drop 1
+            fpart <- state $ TL.span isDigit
+            gets (TL.take 1) >>= \case
+                "e" -> do
+                    modify $ TL.drop 1
+                    sign <- gets (TL.take 1) >>= \case
+                        s | isPlusOrMinus s -> modify (TL.drop 1) >> pure s
+                        _ -> pure ""
+                    state (TL.span isDigit) >>= \ epart ->
+                        let number = case True of
+                                       _ | TL.null fpart -> TL.concat [ipart, ".", fpart]
+                                       _ | TL.null epart -> TL.concat [ipart, ".", fpart]
+                                       _ -> TL.concat [ipart, ".", fpart, "e", sign, epart]
+                         in pure ( TokNumber number, TL.length number )
+
+                _ ->
+                    let number = TL.concat [ipart, ".", fpart]
+                     in pure ( TokNumber number, TL.length number )
+        "e" -> do
+            modify $ TL.drop 1
+            sign <- gets (TL.take 1) >>= \case
+                s | isPlusOrMinus s -> modify (TL.drop 1) >> pure s
+                _ -> pure ""
+            epart <- state $ TL.span isDigit
+            gets (TL.take 1) >>= \case
+                c | (not . TL.null && isWordBody . TL.head) c || TL.null epart -> do
+                    rest <- state $ TL.span isWordBody
+                    let word = TL.concat [ipart, "e", sign, epart, rest]
+                    pure (TokError "identifiers must not start with a digit", TL.length word)
+                  | otherwise ->
+                    let number = TL.concat [ipart, "e", sign, epart]
+                     in pure (TokNumber number, TL.length number)
+
+        c | (isAlpha || (== '_')) (TL.head c) -> do
+            rest <- state $ TL.span (isAlpha || (== '_'))
+            let word = TL.concat [ipart, rest]
+            pure (TokError "identifiers must not start with a digit", TL.length word)
+
+        _ -> pure (TokNumber ipart, TL.length ipart)
+
+  where
+    isPlusOrMinus :: Text -> Bool
+    isPlusOrMinus s = elem s ["+", "-"]
diff --git a/src/Database/Sql/Presto/Token.hs b/src/Database/Sql/Presto/Token.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Token.hs
@@ -0,0 +1,125 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+module Database.Sql.Presto.Token where
+
+import           Data.Text.Lazy (Text)
+import           Data.ByteString.Lazy (ByteString)
+import qualified Data.Map as M
+
+data Token = TokWord !Bool !Text
+           | TokString !ByteString
+           | TokBinary !ByteString
+           | TokNumber !Text
+           | TokSymbol !Text
+           | TokError !String
+             deriving (Show, Eq)
+
+data WordInfo = WordInfo
+              { wordCanBeSchemaName :: !Bool
+              , wordCanBeTableName :: !Bool
+              , wordCanBeColumnName :: !Bool
+              , wordCanBeFunctionName :: !Bool
+              }
+
+wordInfo :: Text -> WordInfo
+wordInfo word = maybe (WordInfo True True True True) id $ M.lookup word $ M.fromList
+    [ ("all",               WordInfo False False False False)
+    , ("alter",             WordInfo False False False False)
+    , ("and",               WordInfo False False False False)
+    , ("any",               WordInfo False False False False)
+    , ("as",                WordInfo False False False False)
+    , ("asc",               WordInfo False False False False)
+    , ("between",           WordInfo False False False False)
+    , ("by",                WordInfo False False False False)
+    , ("case",              WordInfo False False False False)
+    , ("cast",              WordInfo False False False False)
+    , ("constraint",        WordInfo False False False False)
+    , ("create",            WordInfo False False False False)
+    , ("cross",             WordInfo False False False False)
+    , ("cube",              WordInfo False False False False)
+    , ("current_date",      WordInfo False False False False)
+    , ("current_time",      WordInfo False False False False)
+    , ("current_timestamp", WordInfo False False False False)
+    , ("deallocate",        WordInfo False False False False)
+    , ("delete",            WordInfo False False False False)
+    , ("desc",              WordInfo False False False False)
+    , ("describe",          WordInfo False False False False)
+    , ("distinct",          WordInfo False False False False)
+    , ("drop",              WordInfo False False False False)
+    , ("else",              WordInfo False False False False)
+    , ("end",               WordInfo False False False False)
+    , ("escape",            WordInfo False False False False)
+    , ("except",            WordInfo False False False False)
+    , ("execute",           WordInfo False False False False)
+    , ("exists",            WordInfo False False False False)
+    , ("extract",           WordInfo False False False False)
+    , ("false",             WordInfo False False False False)
+    , ("first",             WordInfo False False False False)
+    , ("for",               WordInfo False False False False)
+    , ("from",              WordInfo False False False False)
+    , ("full",              WordInfo False False False False)
+    , ("group",             WordInfo False False False False)
+    , ("grouping",          WordInfo False False False False)
+    , ("having",            WordInfo False False False False)
+    , ("in",                WordInfo False False False False)
+    , ("inner",             WordInfo False False False False)
+    , ("insert",            WordInfo False False False False)
+    , ("intersect",         WordInfo False False False False)
+    , ("into",              WordInfo False False False False)
+    , ("is",                WordInfo False False False False)
+    , ("join",              WordInfo False False False False)
+    , ("last",              WordInfo False False False False)
+    , ("left",              WordInfo False False False False)
+    , ("like",              WordInfo False False False False)
+    , ("limit",             WordInfo False False False False)
+    , ("localtime",         WordInfo False False False False)
+    , ("localtimestamp",    WordInfo False False False False)
+    , ("natural",           WordInfo False False False False)
+    , ("normalize",         WordInfo False False False False)
+    , ("not",               WordInfo False False False False)
+    , ("null",              WordInfo False False False False)
+    , ("nulls",             WordInfo False False False False)
+    , ("on",                WordInfo False False False False)
+    , ("or",                WordInfo False False False False)
+    , ("order",             WordInfo False False False False)
+    , ("ordinality",        WordInfo False False False False)
+    , ("outer",             WordInfo False False False False)
+    , ("prepare",           WordInfo False False False False)
+    , ("recursive",         WordInfo False False False False)
+    , ("rename",            WordInfo False False False False)
+    , ("right",             WordInfo False False False False)
+    , ("rollup",            WordInfo False False False False)
+    , ("select",            WordInfo False False False False)
+    , ("sets",              WordInfo False False False False)
+    , ("some",              WordInfo False False False False)
+    , ("table",             WordInfo False False False False)
+    , ("then",              WordInfo False False False False)
+    , ("true",              WordInfo False False False False)
+    , ("try_cast",          WordInfo False False False False)
+    , ("unbounded",         WordInfo False False False False)
+    , ("union",             WordInfo False False False False)
+    , ("unnest",            WordInfo False False False False)
+    , ("using",             WordInfo False False False False)
+    , ("values",            WordInfo False False False False)
+    , ("when",              WordInfo False False False False)
+    , ("where",             WordInfo False False False False)
+    , ("with",              WordInfo False False False False)
+    ]
diff --git a/src/Database/Sql/Presto/Type.hs b/src/Database/Sql/Presto/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Sql/Presto/Type.hs
@@ -0,0 +1,122 @@
+-- Copyright (c) 2017 Uber Technologies, Inc.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+-- THE SOFTWARE.
+
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Database.Sql.Presto.Type where
+
+import Database.Sql.Type
+import Database.Sql.Position (Range)
+import Database.Sql.Util.Columns
+import Database.Sql.Util.Joins
+import Database.Sql.Util.Lineage.Table
+import Database.Sql.Util.Lineage.ColumnPlus
+import Database.Sql.Util.Schema
+import Database.Sql.Util.Scope
+import Database.Sql.Util.Tables
+
+import Data.Aeson as JSON
+import Data.Proxy (Proxy (..))
+
+import qualified Data.Set as S
+import qualified Data.Map as M
+
+import Data.Data (Data)
+import GHC.Generics (Generic)
+
+data Presto
+
+deriving instance Data Presto
+
+dialectProxy :: Proxy Presto
+dialectProxy = Proxy
+
+instance Dialect Presto where
+
+    shouldCTEsShadowTables _ = True
+
+    resolveCreateTableExtra _ Unused = pure Unused
+
+    getSelectScope _ fromColumns selectionAliases = SelectScope
+        { bindForWhere = bindFromColumns fromColumns
+        , bindForGroup = bindFromColumns fromColumns
+        , bindForHaving = bindFromColumns fromColumns
+        , bindForOrder = bindBothColumns fromColumns selectionAliases
+        , bindForNamedWindow = bindColumns [] -- Presto doesn't have this language feature
+        }
+
+    areLcolumnsVisibleInLateralViews _ = True
+
+data PrestoStatement r a = PrestoStandardSqlStatement (Statement Presto r a)
+                           | PrestoUnhandledStatement a
+
+deriving instance (ConstrainSNames Data r a, Data r) => Data (PrestoStatement r a)
+deriving instance Generic (PrestoStatement r a)
+deriving instance ConstrainSNames Eq r a => Eq (PrestoStatement r a)
+deriving instance ConstrainSNames Show r a => Show (PrestoStatement r a)
+deriving instance ConstrainSASNames Functor r => Functor (PrestoStatement r)
+deriving instance ConstrainSASNames Foldable r => Foldable (PrestoStatement r)
+deriving instance ConstrainSASNames Traversable r => Traversable (PrestoStatement r)
+
+
+instance HasJoins (PrestoStatement ResolvedNames a) where
+    getJoins (PrestoStandardSqlStatement stmt) = getJoins stmt
+    getJoins (PrestoUnhandledStatement _) = S.empty
+
+instance HasTableLineage (PrestoStatement ResolvedNames a) where
+    getTableLineage (PrestoStandardSqlStatement stmt) = tableLineage stmt
+    getTableLineage (PrestoUnhandledStatement _) = M.empty
+
+instance HasColumnLineage (PrestoStatement ResolvedNames Range) where
+    getColumnLineage (PrestoStandardSqlStatement stmt) = columnLineage stmt
+    getColumnLineage (PrestoUnhandledStatement _) = returnNothing M.empty
+
+resolvePrestoStatement :: PrestoStatement RawNames a -> Resolver (PrestoStatement ResolvedNames) a
+resolvePrestoStatement (PrestoStandardSqlStatement stmt) =
+    PrestoStandardSqlStatement <$> resolveStatement stmt
+resolvePrestoStatement (PrestoUnhandledStatement info) = pure $ PrestoUnhandledStatement info
+
+instance HasSchemaChange (PrestoStatement ResolvedNames a) where
+    getSchemaChange (PrestoStandardSqlStatement stmt) = getSchemaChange stmt
+    getSchemaChange (PrestoUnhandledStatement _) = []
+
+instance (ConstrainSNames ToJSON r a, ToJSON a) => ToJSON (PrestoStatement r a) where
+    toJSON (PrestoStandardSqlStatement stmt) = toJSON stmt
+    toJSON (PrestoUnhandledStatement info) = JSON.object
+        [ "tag" .= JSON.String "PrestoUnhandledStatement"
+        , "info" .= info
+        ]
+
+typeExample :: ()
+typeExample = const () $ toJSON (undefined :: PrestoStatement ResolvedNames Range)
+
+instance HasTables (PrestoStatement ResolvedNames a) where
+    goTables (PrestoStandardSqlStatement s) = goTables s
+    goTables (PrestoUnhandledStatement _) = return ()
+
+instance HasColumns (PrestoStatement ResolvedNames a) where
+    goColumns (PrestoStandardSqlStatement s) = goColumns s
+    goColumns (PrestoUnhandledStatement _) = return ()
