├── .github └── workflows │ ├── format-and-test.yaml │ └── release.yaml ├── LICENSE ├── README.md ├── cabal.project ├── hedgehog-test ├── Main.hs └── Main │ └── Gen.hs ├── library └── PostgresqlSyntax │ ├── Ast.hs │ ├── CharSet.hs │ ├── Extras │ ├── HeadedMegaparsec.hs │ ├── NonEmpty.hs │ └── TextBuilder.hs │ ├── KeywordSet.hs │ ├── Parsing.hs │ ├── Predicate.hs │ ├── Prelude.hs │ ├── Rendering.hs │ └── Validation.hs ├── postgresql-syntax.cabal ├── references ├── gram.y ├── kwlist.h ├── operator-token-scanner ├── postgres-gram-defs └── scan.l └── tasty-test └── Main.hs /.github/workflows/format-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Format and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_call: 9 | 10 | concurrency: 11 | group: format-and-test 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | format: 16 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/format.yaml@v4 17 | secrets: inherit 18 | 19 | check-cabal: 20 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/check-cabal.yaml@v4 21 | secrets: inherit 22 | 23 | generate-docs: 24 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/generate-docs.yaml@v4 25 | secrets: inherit 26 | 27 | build-with-ghc-8-8: 28 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/build.yaml@v4 29 | secrets: inherit 30 | with: 31 | ghc: "8.8.1" 32 | skip-doctest: true 33 | skip-benchmarks: true 34 | 35 | build-with-latest: 36 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/build.yaml@v4 37 | secrets: inherit 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release the lib to Hackage 2 | 3 | on: 4 | push: 5 | branches: 6 | - supermajor 7 | - major 8 | - minor 9 | - patch 10 | 11 | concurrency: 12 | group: release 13 | cancel-in-progress: false 14 | 15 | jobs: 16 | format-and-test: 17 | uses: ./.github/workflows/format-and-test.yaml 18 | secrets: inherit 19 | 20 | release: 21 | needs: 22 | - format-and-test 23 | uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/release.yaml@v4 24 | secrets: inherit 25 | with: 26 | prefix-tag-with-v: false 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Nikita Volkov 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | Postgres syntax tree and related utils extracted from [the "hasql-th" package](https://github.com/nikita-volkov/hasql-th). 4 | The API is in a rather raw "guts out" state with most documentation lacking, 5 | but the codebase is well tested. 6 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /hedgehog-test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified Data.Text as Text 4 | import Hedgehog 5 | import Hedgehog.Main 6 | import qualified Main.Gen as Gen 7 | import qualified PostgresqlSyntax.Parsing as Parsing 8 | import qualified PostgresqlSyntax.Rendering as Rendering 9 | import Prelude 10 | 11 | main :: IO () 12 | main = 13 | defaultMain 14 | [ checkParallel 15 | $ Group "Parsing a rendered AST produces the same AST" 16 | $ let p name amount gen parser renderer = 17 | (,) name 18 | $ withDiscards (fromIntegral amount * 200) 19 | $ withTests amount 20 | $ property 21 | $ do 22 | ast <- forAll gen 23 | let sql = Rendering.toText (renderer ast) 24 | in do 25 | footnote ("SQL: " <> Text.unpack sql) 26 | case Parsing.run parser sql of 27 | Left err -> do 28 | footnote err 29 | failure 30 | Right ast' -> ast === ast' 31 | in [ p "typename" 10000 Gen.typename Parsing.typename Rendering.typename, 32 | p "tableRef" 10000 Gen.tableRef Parsing.tableRef Rendering.tableRef, 33 | p "aExpr" 60000 Gen.aExpr Parsing.aExpr Rendering.aExpr, 34 | p "preparableStmt" 30000 Gen.preparableStmt Parsing.preparableStmt Rendering.preparableStmt 35 | ] 36 | ] 37 | -------------------------------------------------------------------------------- /hedgehog-test/Main/Gen.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-missing-signatures #-} 2 | 3 | module Main.Gen where 4 | 5 | import qualified Data.HashSet as HashSet 6 | import qualified Data.List as List 7 | import qualified Data.Text as Text 8 | import Hedgehog (Gen) 9 | import Hedgehog.Gen 10 | import qualified Hedgehog.Range as Range 11 | import PostgresqlSyntax.Ast 12 | import qualified PostgresqlSyntax.KeywordSet as KeywordSet 13 | import qualified PostgresqlSyntax.Validation as Validation 14 | import Prelude hiding (bit, bool, filter, fromList, maybe, sortBy) 15 | 16 | -- * Generic 17 | 18 | inSet set = filter (flip HashSet.member set) 19 | 20 | notInSet set = filter (not . flip HashSet.member set) 21 | 22 | -- * Statements 23 | 24 | preparableStmt = 25 | choice 26 | [ SelectPreparableStmt <$> selectStmt, 27 | InsertPreparableStmt <$> insertStmt, 28 | UpdatePreparableStmt <$> updateStmt, 29 | DeletePreparableStmt <$> deleteStmt, 30 | CallPreparableStmt <$> callStmt 31 | ] 32 | 33 | -- * Call 34 | 35 | callStmt = CallStmt <$> funcApplication 36 | 37 | -- * Insert 38 | 39 | insertStmt = InsertStmt <$> maybe withClause <*> insertTarget <*> insertRest <*> maybe onConflict <*> maybe returningClause 40 | 41 | insertTarget = InsertTarget <$> qualifiedName <*> maybe colId 42 | 43 | insertRest = 44 | choice 45 | [ SelectInsertRest <$> maybe insertColumnList <*> maybe overrideKind <*> selectStmt, 46 | pure DefaultValuesInsertRest 47 | ] 48 | 49 | overrideKind = enumBounded 50 | 51 | insertColumnList = nonEmpty (Range.exponential 1 7) insertColumnItem 52 | 53 | insertColumnItem = InsertColumnItem <$> colId <*> maybe indirection 54 | 55 | onConflict = OnConflict <$> maybe confExpr <*> onConflictDo 56 | 57 | onConflictDo = 58 | choice 59 | [ UpdateOnConflictDo <$> setClauseList <*> maybe whereClause, 60 | pure NothingOnConflictDo 61 | ] 62 | 63 | confExpr = 64 | choice 65 | [ WhereConfExpr <$> indexParams <*> maybe whereClause, 66 | ConstraintConfExpr <$> name 67 | ] 68 | 69 | returningClause = targetList 70 | 71 | -- * Update 72 | 73 | updateStmt = UpdateStmt <$> maybe withClause <*> relationExprOptAlias <*> setClauseList <*> maybe fromClause <*> maybe whereOrCurrentClause <*> maybe returningClause 74 | 75 | setClauseList = nonEmpty (Range.exponential 1 10) setClause 76 | 77 | setClause = 78 | choice 79 | [ TargetSetClause <$> setTarget <*> aExpr, 80 | TargetListSetClause <$> setTargetList <*> aExpr 81 | ] 82 | 83 | setTarget = SetTarget <$> colId <*> maybe indirection 84 | 85 | setTargetList = nonEmpty (Range.exponential 1 10) setTarget 86 | 87 | -- * Delete 88 | 89 | deleteStmt = DeleteStmt <$> maybe withClause <*> relationExprOptAlias <*> maybe usingClause <*> maybe whereOrCurrentClause <*> maybe returningClause 90 | 91 | usingClause = fromList 92 | 93 | -- * Select 94 | 95 | selectStmt = Left <$> selectNoParens 96 | 97 | -- ** selectNoParens 98 | 99 | selectNoParens = 100 | frequency 101 | [ (90, SelectNoParens <$> maybe withClause <*> (Left <$> simpleSelect) <*> maybe sortClause <*> maybe selectLimit <*> maybe forLockingClause), 102 | (10, SelectNoParens <$> fmap Just withClause <*> selectClause <*> fmap Just sortClause <*> fmap Just selectLimit <*> fmap Just forLockingClause) 103 | ] 104 | 105 | terminalSelectNoParens = 106 | SelectNoParens <$> pure Nothing <*> (Left <$> terminalSimpleSelect) <*> pure Nothing <*> pure Nothing <*> pure Nothing 107 | 108 | -- ** selectWithParens 109 | 110 | selectWithParens = sized $ \size -> 111 | if size <= 1 112 | then discard 113 | else 114 | frequency 115 | [ (95, NoParensSelectWithParens <$> selectNoParens), 116 | (5, WithParensSelectWithParens <$> selectWithParens) 117 | ] 118 | 119 | terminalSelectWithParens = NoParensSelectWithParens <$> terminalSelectNoParens 120 | 121 | -- ** selectClause 122 | 123 | selectClause = 124 | choice 125 | [ Left <$> simpleSelect, 126 | Right <$> small selectWithParens 127 | ] 128 | 129 | nonTrailingSelectClause = Left <$> nonTrailingSimpleSelect 130 | 131 | -- ** simpleSelect 132 | 133 | simpleSelect = 134 | choice 135 | [ normalSimpleSelect, 136 | tableSimpleSelect, 137 | valuesSimpleSelect, 138 | small nonTrailingSelectClause >>= binSimpleSelect 139 | ] 140 | 141 | nonTrailingSimpleSelect = choice [normalSimpleSelect, valuesSimpleSelect, tableSimpleSelect] 142 | 143 | normalSimpleSelect = NormalSimpleSelect <$> maybe targeting <*> maybe intoClause <*> maybe fromClause <*> maybe whereClause <*> maybe groupClause <*> maybe havingClause <*> maybe windowClause 144 | 145 | tableSimpleSelect = TableSimpleSelect <$> relationExpr 146 | 147 | valuesSimpleSelect = ValuesSimpleSelect <$> valuesClause 148 | 149 | binSimpleSelect leftSelect = 150 | BinSimpleSelect <$> selectBinOp <*> pure leftSelect <*> maybe allOrDistinct <*> small selectClause 151 | 152 | terminalSimpleSelect = pure (NormalSimpleSelect Nothing Nothing Nothing Nothing Nothing Nothing Nothing) 153 | 154 | -- * Targeting 155 | 156 | targeting = 157 | choice 158 | [ NormalTargeting <$> targetList, 159 | AllTargeting <$> maybe targetList, 160 | DistinctTargeting <$> maybe (nonEmpty (Range.exponential 1 8) aExpr) <*> targetList 161 | ] 162 | 163 | targetList = nonEmpty (Range.exponential 1 8) targetEl 164 | 165 | targetEl = 166 | choice 167 | [ pure AsteriskTargetEl, 168 | AliasedExprTargetEl <$> aExpr <*> colLabel, 169 | ImplicitlyAliasedExprTargetEl <$> prefixAExpr <*> ident, 170 | ExprTargetEl <$> aExpr 171 | ] 172 | 173 | -- * BinSimpleSelect 174 | 175 | selectBinOp = element [UnionSelectBinOp, IntersectSelectBinOp, ExceptSelectBinOp] 176 | 177 | -- * With Clause 178 | 179 | withClause = WithClause <$> bool <*> nonEmpty (Range.exponential 1 7) commonTableExpr 180 | 181 | commonTableExpr = CommonTableExpr <$> name <*> maybe (nonEmpty (Range.exponential 1 8) name) <*> maybe bool <*> small preparableStmt 182 | 183 | -- * Into Clause 184 | 185 | intoClause = optTempTableName 186 | 187 | optTempTableName = 188 | choice 189 | [ TemporaryOptTempTableName <$> bool <*> qualifiedName, 190 | TempOptTempTableName <$> bool <*> qualifiedName, 191 | LocalTemporaryOptTempTableName <$> bool <*> qualifiedName, 192 | LocalTempOptTempTableName <$> bool <*> qualifiedName, 193 | GlobalTemporaryOptTempTableName <$> bool <*> qualifiedName, 194 | GlobalTempOptTempTableName <$> bool <*> qualifiedName, 195 | UnloggedOptTempTableName <$> bool <*> qualifiedName, 196 | TableOptTempTableName <$> qualifiedName, 197 | QualifedOptTempTableName <$> qualifiedName 198 | ] 199 | 200 | -- * From Clause 201 | 202 | fromList = nonEmpty (Range.exponential 1 8) tableRef 203 | 204 | fromClause = fromList 205 | 206 | tableRef = choice [relationExprTableRef, selectTableRef, joinTableRef] 207 | 208 | relationExprTableRef = RelationExprTableRef <$> relationExpr <*> maybe aliasClause <*> maybe tablesampleClause 209 | 210 | funcTableRef = FuncTableRef <$> bool <*> funcTable <*> maybe funcAliasClause 211 | 212 | selectTableRef = SelectTableRef <$> bool <*> small selectWithParens <*> maybe aliasClause 213 | 214 | joinTableRef = JoinTableRef <$> joinedTable <*> maybe aliasClause 215 | 216 | relationExpr = 217 | choice 218 | [ SimpleRelationExpr <$> qualifiedName <*> bool, 219 | OnlyRelationExpr <$> qualifiedName <*> bool 220 | ] 221 | 222 | relationExprOptAlias = RelationExprOptAlias <$> relationExpr <*> maybe ((,) <$> bool <*> colId) 223 | 224 | tablesampleClause = TablesampleClause <$> funcName <*> exprList <*> maybe repeatableClause 225 | 226 | repeatableClause = aExpr 227 | 228 | funcTable = 229 | choice 230 | [ FuncExprFuncTable <$> funcExprWindowless <*> optOrdinality, 231 | RowsFromFuncTable <$> rowsfromList <*> optOrdinality 232 | ] 233 | 234 | rowsfromItem = RowsfromItem <$> funcExprWindowless <*> maybe colDefList 235 | 236 | rowsfromList = nonEmpty (Range.exponential 1 8) rowsfromItem 237 | 238 | colDefList = tableFuncElementList 239 | 240 | optOrdinality = bool 241 | 242 | tableFuncElementList = nonEmpty (Range.exponential 1 7) tableFuncElement 243 | 244 | tableFuncElement = TableFuncElement <$> colId <*> typename <*> maybe collateClause 245 | 246 | collateClause = anyName 247 | 248 | aliasClause = AliasClause <$> bool <*> name <*> maybe (nonEmpty (Range.exponential 1 8) name) 249 | 250 | funcAliasClause = 251 | choice 252 | [ AliasFuncAliasClause <$> aliasClause, 253 | AsFuncAliasClause <$> tableFuncElementList, 254 | AsColIdFuncAliasClause <$> colId <*> tableFuncElementList, 255 | ColIdFuncAliasClause <$> colId <*> tableFuncElementList 256 | ] 257 | 258 | joinedTable = 259 | frequency 260 | [ (5,) $ InParensJoinedTable <$> joinedTable, 261 | (95,) $ MethJoinedTable <$> joinMeth <*> tableRef <*> choice [relationExprTableRef, selectTableRef, funcTableRef] 262 | ] 263 | 264 | joinMeth = 265 | choice 266 | [ pure CrossJoinMeth, 267 | QualJoinMeth <$> maybe joinType <*> joinQual, 268 | NaturalJoinMeth <$> maybe joinType 269 | ] 270 | 271 | joinType = 272 | choice 273 | [ FullJoinType <$> bool, 274 | LeftJoinType <$> bool, 275 | RightJoinType <$> bool, 276 | pure InnerJoinType 277 | ] 278 | 279 | joinQual = 280 | choice 281 | [ UsingJoinQual <$> nonEmpty (Range.exponential 1 8) name, 282 | OnJoinQual <$> aExpr 283 | ] 284 | 285 | -- * Group Clause 286 | 287 | groupClause = nonEmpty (Range.exponential 1 8) groupByItem 288 | 289 | groupByItem = 290 | choice 291 | [ ExprGroupByItem <$> aExpr, 292 | pure EmptyGroupingSetGroupByItem, 293 | RollupGroupByItem <$> nonEmpty (Range.exponential 1 8) aExpr, 294 | CubeGroupByItem <$> nonEmpty (Range.exponential 1 8) aExpr, 295 | GroupingSetsGroupByItem <$> nonEmpty (Range.exponential 1 3) groupByItem 296 | ] 297 | 298 | -- * Having Clause 299 | 300 | havingClause = aExpr 301 | 302 | -- * Where Clause 303 | 304 | whereClause = aExpr 305 | 306 | whereOrCurrentClause = 307 | choice 308 | [ ExprWhereOrCurrentClause <$> aExpr, 309 | CursorWhereOrCurrentClause <$> cursorName 310 | ] 311 | 312 | -- * Window Clause 313 | 314 | windowClause = nonEmpty (Range.exponential 1 8) windowDefinition 315 | 316 | windowDefinition = WindowDefinition <$> name <*> windowSpecification 317 | 318 | windowSpecification = WindowSpecification <$> maybe name <*> maybe (nonEmpty (Range.exponential 1 8) nonSuffixOpAExpr) <*> maybe sortClause <*> maybe frameClause 319 | 320 | frameClause = FrameClause <$> frameClauseMode <*> frameExtent <*> maybe windowExclusionClause 321 | 322 | frameClauseMode = element [RangeFrameClauseMode, RowsFrameClauseMode, GroupsFrameClauseMode] 323 | 324 | frameExtent = 325 | choice 326 | [ SingularFrameExtent <$> frameBound, 327 | BetweenFrameExtent <$> frameBound <*> frameBound 328 | ] 329 | 330 | frameBound = 331 | choice 332 | [ pure UnboundedPrecedingFrameBound, 333 | pure UnboundedFollowingFrameBound, 334 | pure CurrentRowFrameBound, 335 | PrecedingFrameBound <$> prefixAExpr, 336 | FollowingFrameBound <$> prefixAExpr 337 | ] 338 | 339 | windowExclusionClause = element [CurrentRowWindowExclusionClause, GroupWindowExclusionClause, TiesWindowExclusionClause, NoOthersWindowExclusionClause] 340 | 341 | -- * Values Clause 342 | 343 | valuesClause = nonEmpty (Range.exponential 1 8) (nonEmpty (Range.exponential 1 8) aExpr) 344 | 345 | -- * Sort Clause 346 | 347 | sortClause = nonEmpty (Range.exponential 1 8) sortBy 348 | 349 | sortBy = 350 | choice 351 | [ UsingSortBy <$> nonSuffixOpAExpr <*> qualAllOp <*> maybe nullsOrder, 352 | AscDescSortBy <$> nonSuffixOpAExpr <*> maybe ascDesc <*> maybe nullsOrder 353 | ] 354 | 355 | -- * All or distinct 356 | 357 | allOrDistinct = bool 358 | 359 | -- * Limit 360 | 361 | selectLimit = 362 | choice 363 | [ LimitOffsetSelectLimit <$> limitClause <*> offsetClause, 364 | OffsetLimitSelectLimit <$> offsetClause <*> limitClause, 365 | LimitSelectLimit <$> limitClause, 366 | OffsetSelectLimit <$> offsetClause 367 | ] 368 | 369 | limitClause = 370 | choice 371 | [ LimitLimitClause <$> selectLimitValue <*> maybe aExpr, 372 | FetchOnlyLimitClause <$> bool <*> maybe selectFetchFirstValue <*> bool 373 | ] 374 | 375 | selectFetchFirstValue = 376 | choice 377 | [ ExprSelectFetchFirstValue <$> cExpr, 378 | NumSelectFetchFirstValue <$> bool <*> iconstOrFconst 379 | ] 380 | 381 | selectLimitValue = 382 | choice 383 | [ ExprSelectLimitValue <$> aExpr, 384 | pure AllSelectLimitValue 385 | ] 386 | 387 | offsetClause = 388 | choice 389 | [ ExprOffsetClause <$> aExpr, 390 | FetchFirstOffsetClause <$> selectFetchFirstValue <*> bool 391 | ] 392 | 393 | -- * For Locking 394 | 395 | forLockingClause = 396 | choice 397 | [ ItemsForLockingClause <$> nonEmpty (Range.exponential 1 8) forLockingItem, 398 | pure ReadOnlyForLockingClause 399 | ] 400 | 401 | forLockingItem = ForLockingItem <$> forLockingStrength <*> maybe (nonEmpty (Range.exponential 1 8) qualifiedName) <*> maybe bool 402 | 403 | forLockingStrength = 404 | element 405 | [ UpdateForLockingStrength, 406 | NoKeyUpdateForLockingStrength, 407 | ShareForLockingStrength, 408 | KeyForLockingStrength 409 | ] 410 | 411 | -- * Expressions 412 | 413 | exprList = nonEmpty (Range.exponential 1 7) aExpr 414 | 415 | aExpr = 416 | recursive 417 | choice 418 | [ CExprAExpr <$> cExpr, 419 | pure DefaultAExpr 420 | ] 421 | [ TypecastAExpr <$> prefixAExpr <*> typename, 422 | CollateAExpr <$> prefixAExpr <*> anyName, 423 | AtTimeZoneAExpr <$> prefixAExpr <*> aExpr, 424 | PlusAExpr <$> aExpr, 425 | MinusAExpr <$> aExpr, 426 | SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> aExpr, 427 | PrefixQualOpAExpr <$> qualOp <*> aExpr, 428 | SuffixQualOpAExpr <$> prefixAExpr <*> qualOp, 429 | AndAExpr <$> prefixAExpr <*> aExpr, 430 | OrAExpr <$> prefixAExpr <*> aExpr, 431 | NotAExpr <$> aExpr, 432 | VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe aExpr, 433 | ReversableOpAExpr <$> prefixAExpr <*> bool <*> aExprReversableOp, 434 | IsnullAExpr <$> prefixAExpr, 435 | NotnullAExpr <$> prefixAExpr, 436 | OverlapsAExpr <$> row <*> row, 437 | SubqueryAExpr <$> prefixAExpr <*> subqueryOp <*> subType <*> choice [Left <$> selectWithParens, Right <$> nonSelectAExpr], 438 | UniqueAExpr <$> selectWithParens 439 | ] 440 | 441 | prefixAExpr = 442 | choice 443 | [ CExprAExpr <$> cExpr, 444 | pure DefaultAExpr, 445 | UniqueAExpr <$> selectWithParens 446 | ] 447 | 448 | nonSuffixOpAExpr = 449 | recursive 450 | choice 451 | [ CExprAExpr <$> cExpr, 452 | pure DefaultAExpr 453 | ] 454 | [ TypecastAExpr <$> prefixAExpr <*> typename, 455 | CollateAExpr <$> prefixAExpr <*> anyName, 456 | AtTimeZoneAExpr <$> prefixAExpr <*> nonSuffixOpAExpr, 457 | PlusAExpr <$> nonSuffixOpAExpr, 458 | MinusAExpr <$> nonSuffixOpAExpr, 459 | SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> nonSuffixOpAExpr, 460 | PrefixQualOpAExpr <$> qualOp <*> nonSuffixOpAExpr, 461 | AndAExpr <$> prefixAExpr <*> nonSuffixOpAExpr, 462 | OrAExpr <$> prefixAExpr <*> nonSuffixOpAExpr, 463 | NotAExpr <$> nonSuffixOpAExpr, 464 | VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe nonSuffixOpAExpr, 465 | IsnullAExpr <$> prefixAExpr, 466 | NotnullAExpr <$> prefixAExpr, 467 | UniqueAExpr <$> selectWithParens 468 | ] 469 | 470 | nonSelectAExpr = 471 | choice 472 | [ TypecastAExpr <$> prefixAExpr <*> typename, 473 | CollateAExpr <$> prefixAExpr <*> anyName, 474 | AtTimeZoneAExpr <$> prefixAExpr <*> aExpr, 475 | PlusAExpr <$> aExpr, 476 | MinusAExpr <$> aExpr, 477 | SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> aExpr, 478 | PrefixQualOpAExpr <$> qualOp <*> aExpr, 479 | SuffixQualOpAExpr <$> prefixAExpr <*> qualOp, 480 | AndAExpr <$> prefixAExpr <*> aExpr, 481 | OrAExpr <$> prefixAExpr <*> aExpr, 482 | NotAExpr <$> aExpr, 483 | VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe aExpr, 484 | ReversableOpAExpr <$> prefixAExpr <*> bool <*> aExprReversableOp, 485 | IsnullAExpr <$> prefixAExpr, 486 | NotnullAExpr <$> prefixAExpr, 487 | OverlapsAExpr <$> row <*> row, 488 | SubqueryAExpr <$> prefixAExpr <*> subqueryOp <*> subType <*> choice [Left <$> selectWithParens, Right <$> nonSelectAExpr], 489 | UniqueAExpr <$> selectWithParens 490 | ] 491 | 492 | bExpr = 493 | recursive 494 | choice 495 | [ CExprBExpr <$> cExpr 496 | ] 497 | [ TypecastBExpr <$> prefixBExpr <*> typename, 498 | PlusBExpr <$> bExpr, 499 | MinusBExpr <$> bExpr, 500 | SymbolicBinOpBExpr <$> prefixBExpr <*> symbolicExprBinOp <*> bExpr, 501 | QualOpBExpr <$> qualOp <*> bExpr, 502 | IsOpBExpr <$> prefixBExpr <*> bool <*> bExprIsOp 503 | ] 504 | 505 | prefixBExpr = 506 | choice 507 | [ CExprBExpr <$> cExpr 508 | ] 509 | 510 | cExpr = 511 | recursive 512 | choice 513 | [ ColumnrefCExpr <$> columnref 514 | ] 515 | [ AexprConstCExpr <$> aexprConst, 516 | ParamCExpr <$> integral (Range.linear 1 19) <*> maybe indirection, 517 | InParensCExpr <$> nonSelectAExpr <*> maybe indirection, 518 | CaseCExpr <$> caseExpr, 519 | FuncCExpr <$> funcExpr, 520 | SelectWithParensCExpr <$> selectWithParens <*> maybe indirection, 521 | ExistsCExpr <$> selectWithParens, 522 | ArrayCExpr <$> choice [Left <$> selectWithParens, Right <$> arrayExpr], 523 | ExplicitRowCExpr <$> explicitRow, 524 | ImplicitRowCExpr <$> implicitRow, 525 | GroupingCExpr <$> exprList 526 | ] 527 | 528 | caseExpr = CaseExpr <$> maybe aExpr <*> whenClauseList <*> maybe aExpr 529 | 530 | whenClauseList = nonEmpty (Range.exponential 1 7) whenClause 531 | 532 | whenClause = WhenClause <$> small aExpr <*> small aExpr 533 | 534 | inExpr = 535 | choice 536 | [ SelectInExpr <$> NoParensSelectWithParens <$> selectNoParens, 537 | ExprListInExpr <$> exprList 538 | ] 539 | 540 | arrayExpr = 541 | small 542 | $ choice 543 | [ ExprListArrayExpr <$> exprList, 544 | ArrayExprListArrayExpr <$> arrayExprList, 545 | pure EmptyArrayExpr 546 | ] 547 | 548 | arrayExprList = nonEmpty (Range.exponential 1 4) arrayExpr 549 | 550 | row = 551 | choice 552 | [ ExplicitRowRow <$> explicitRow, 553 | ImplicitRowRow <$> implicitRow 554 | ] 555 | 556 | explicitRow = maybe exprList 557 | 558 | implicitRow = ImplicitRow <$> exprList <*> aExpr 559 | 560 | -- ** FuncExpr 561 | 562 | funcExpr = 563 | choice 564 | [ ApplicationFuncExpr <$> funcApplication <*> maybe withinGroupClause <*> maybe filterClause <*> maybe overClause, 565 | SubexprFuncExpr <$> funcExprCommonSubexpr 566 | ] 567 | 568 | funcExprWindowless = 569 | choice 570 | [ ApplicationFuncExprWindowless <$> funcApplication, 571 | CommonSubexprFuncExprWindowless <$> funcExprCommonSubexpr 572 | ] 573 | 574 | funcApplication = FuncApplication <$> funcName <*> maybe funcApplicationParams 575 | 576 | funcApplicationParams = 577 | choice 578 | [ NormalFuncApplicationParams <$> maybe allOrDistinct <*> nonEmpty (Range.exponential 1 8) funcArgExpr <*> maybe sortClause, 579 | VariadicFuncApplicationParams <$> maybe (nonEmpty (Range.exponential 1 8) funcArgExpr) <*> funcArgExpr <*> maybe sortClause, 580 | pure StarFuncApplicationParams 581 | ] 582 | 583 | funcArgExpr = 584 | choice 585 | [ ExprFuncArgExpr <$> small aExpr, 586 | ColonEqualsFuncArgExpr <$> name <*> small aExpr, 587 | EqualsGreaterFuncArgExpr <$> name <*> small aExpr 588 | ] 589 | 590 | withinGroupClause = sortClause 591 | 592 | filterClause = aExpr 593 | 594 | overClause = choice [WindowOverClause <$> windowSpecification, ColIdOverClause <$> colId] 595 | 596 | funcExprCommonSubexpr = 597 | choice 598 | [ CollationForFuncExprCommonSubexpr <$> aExpr, 599 | pure CurrentDateFuncExprCommonSubexpr, 600 | CurrentTimeFuncExprCommonSubexpr <$> maybe iconst, 601 | CurrentTimestampFuncExprCommonSubexpr <$> maybe iconst, 602 | LocalTimeFuncExprCommonSubexpr <$> maybe iconst, 603 | LocalTimestampFuncExprCommonSubexpr <$> maybe iconst, 604 | pure CurrentRoleFuncExprCommonSubexpr, 605 | pure CurrentUserFuncExprCommonSubexpr, 606 | pure SessionUserFuncExprCommonSubexpr, 607 | pure UserFuncExprCommonSubexpr, 608 | pure CurrentCatalogFuncExprCommonSubexpr, 609 | pure CurrentSchemaFuncExprCommonSubexpr, 610 | CastFuncExprCommonSubexpr <$> aExpr <*> typename, 611 | ExtractFuncExprCommonSubexpr <$> maybe extractList, 612 | OverlayFuncExprCommonSubexpr <$> overlayList, 613 | PositionFuncExprCommonSubexpr <$> maybe positionList, 614 | SubstringFuncExprCommonSubexpr <$> maybe substrList, 615 | TreatFuncExprCommonSubexpr <$> aExpr <*> typename, 616 | TrimFuncExprCommonSubexpr <$> maybe trimModifier <*> trimList, 617 | NullIfFuncExprCommonSubexpr <$> aExpr <*> aExpr, 618 | CoalesceFuncExprCommonSubexpr <$> exprList, 619 | GreatestFuncExprCommonSubexpr <$> exprList, 620 | LeastFuncExprCommonSubexpr <$> exprList 621 | ] 622 | 623 | extractList = ExtractList <$> extractArg <*> aExpr 624 | 625 | extractArg = 626 | choice 627 | [ IdentExtractArg <$> ident, 628 | pure YearExtractArg, 629 | pure MonthExtractArg, 630 | pure DayExtractArg, 631 | pure HourExtractArg, 632 | pure MinuteExtractArg, 633 | pure SecondExtractArg, 634 | SconstExtractArg <$> sconst 635 | ] 636 | 637 | overlayList = OverlayList <$> aExpr <*> overlayPlacing <*> substrFrom <*> maybe substrFor 638 | 639 | overlayPlacing = aExpr 640 | 641 | positionList = PositionList <$> bExpr <*> bExpr 642 | 643 | substrList = 644 | choice 645 | [ ExprSubstrList <$> aExpr <*> substrListFromFor, 646 | ExprListSubstrList <$> exprList 647 | ] 648 | 649 | substrListFromFor = 650 | choice 651 | [ FromForSubstrListFromFor <$> substrFrom <*> substrFor, 652 | ForFromSubstrListFromFor <$> substrFor <*> substrFrom, 653 | FromSubstrListFromFor <$> substrFrom, 654 | ForSubstrListFromFor <$> substrFor 655 | ] 656 | 657 | substrFrom = aExpr 658 | 659 | substrFor = aExpr 660 | 661 | trimModifier = enumBounded 662 | 663 | trimList = 664 | choice 665 | [ ExprFromExprListTrimList <$> aExpr <*> exprList, 666 | FromExprListTrimList <$> exprList, 667 | ExprListTrimList <$> exprList 668 | ] 669 | 670 | -- * Operators 671 | 672 | qualOp = choice [OpQualOp <$> op, OperatorQualOp <$> anyOperator] 673 | 674 | qualAllOp = 675 | choice 676 | [ AllQualAllOp <$> allOp, 677 | AnyQualAllOp <$> anyOperator 678 | ] 679 | 680 | op = do 681 | a <- text (Range.exponential 1 7) (listElement "+-*/<>=~!@#%^&|`?") 682 | case Validation.op a of 683 | Nothing -> return a 684 | _ -> discard 685 | 686 | anyOperator = 687 | recursive 688 | choice 689 | [ AllOpAnyOperator <$> allOp 690 | ] 691 | [ QualifiedAnyOperator <$> colId <*> anyOperator 692 | ] 693 | 694 | allOp = choice [OpAllOp <$> op, MathAllOp <$> mathOp] 695 | 696 | mathOp = enumBounded 697 | 698 | symbolicExprBinOp = 699 | choice 700 | [ MathSymbolicExprBinOp <$> mathOp, 701 | QualSymbolicExprBinOp <$> qualOp 702 | ] 703 | 704 | binOp = element (toList KeywordSet.symbolicBinOp <> ["AND", "OR", "IS DISTINCT FROM", "IS NOT DISTINCT FROM"]) 705 | 706 | verbalExprBinOp = enumBounded 707 | 708 | aExprReversableOp = 709 | choice 710 | [ pure NullAExprReversableOp, 711 | pure TrueAExprReversableOp, 712 | pure FalseAExprReversableOp, 713 | pure UnknownAExprReversableOp, 714 | DistinctFromAExprReversableOp <$> aExpr, 715 | OfAExprReversableOp <$> typeList, 716 | BetweenAExprReversableOp <$> bool <*> bExpr <*> aExpr, 717 | BetweenSymmetricAExprReversableOp <$> bExpr <*> aExpr, 718 | InAExprReversableOp <$> inExpr, 719 | pure DocumentAExprReversableOp 720 | ] 721 | 722 | bExprIsOp = 723 | choice 724 | [ DistinctFromBExprIsOp <$> bExpr, 725 | OfBExprIsOp <$> typeList, 726 | pure DocumentBExprIsOp 727 | ] 728 | 729 | subqueryOp = 730 | choice 731 | [ AllSubqueryOp <$> allOp, 732 | AnySubqueryOp <$> anyOperator, 733 | LikeSubqueryOp <$> bool, 734 | IlikeSubqueryOp <$> bool 735 | ] 736 | 737 | -- * Constants 738 | 739 | aexprConst = 740 | choice 741 | [ IAexprConst <$> iconst, 742 | FAexprConst <$> fconst, 743 | SAexprConst <$> sconst, 744 | BAexprConst <$> text (Range.exponential 1 100) (listElement "01"), 745 | XAexprConst <$> text (Range.exponential 1 100) (listElement "0123456789abcdefABCDEF"), 746 | FuncAexprConst <$> funcName <*> maybe funcConstArgs <*> sconst, 747 | ConstTypenameAexprConst <$> constTypename <*> sconst, 748 | StringIntervalAexprConst <$> sconst <*> maybe interval, 749 | IntIntervalAexprConst <$> integral (Range.exponential 0 2309482309483029) <*> sconst, 750 | BoolAexprConst <$> bool, 751 | pure NullAexprConst 752 | ] 753 | 754 | funcConstArgs = FuncConstArgs <$> nonEmpty (Range.exponential 1 7) funcArgExpr <*> maybe sortClause 755 | 756 | constTypename = 757 | choice 758 | [ NumericConstTypename <$> numeric, 759 | ConstBitConstTypename <$> constBit, 760 | ConstCharacterConstTypename <$> constCharacter, 761 | ConstDatetimeConstTypename <$> constDatetime 762 | ] 763 | 764 | numeric = 765 | choice 766 | [ pure IntNumeric, 767 | pure IntegerNumeric, 768 | pure SmallintNumeric, 769 | pure BigintNumeric, 770 | pure RealNumeric, 771 | FloatNumeric <$> maybe iconst, 772 | pure DoublePrecisionNumeric, 773 | DecimalNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)), 774 | DecNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)), 775 | NumericNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)), 776 | pure BooleanNumeric 777 | ] 778 | 779 | bit = Bit <$> bool <*> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)) 780 | 781 | constBit = bit 782 | 783 | constCharacter = ConstCharacter <$> character <*> maybe iconst 784 | 785 | character = 786 | choice 787 | [ CharacterCharacter <$> bool, 788 | CharCharacter <$> bool, 789 | pure VarcharCharacter, 790 | NationalCharacterCharacter <$> bool, 791 | NationalCharCharacter <$> bool, 792 | NcharCharacter <$> bool 793 | ] 794 | 795 | constDatetime = 796 | choice 797 | [ TimestampConstDatetime <$> maybe iconst <*> maybe bool, 798 | TimeConstDatetime <$> maybe iconst <*> maybe bool 799 | ] 800 | 801 | interval = 802 | choice 803 | [ pure YearInterval, 804 | pure MonthInterval, 805 | pure DayInterval, 806 | pure HourInterval, 807 | pure MinuteInterval, 808 | SecondInterval <$> intervalSecond, 809 | pure YearToMonthInterval, 810 | pure DayToHourInterval, 811 | pure DayToMinuteInterval, 812 | DayToSecondInterval <$> intervalSecond, 813 | pure HourToMinuteInterval, 814 | HourToSecondInterval <$> intervalSecond, 815 | MinuteToSecondInterval <$> intervalSecond 816 | ] 817 | 818 | intervalSecond = maybe iconst 819 | 820 | sconst = text (Range.exponential 0 1000) unicode 821 | 822 | iconstOrFconst = choice [Left <$> iconst <|> Right <$> fconst] 823 | 824 | fconst = 825 | filter (\a -> fromIntegral (round a :: Int) /= a) 826 | $ realFrac_ (Range.exponentialFloat 0 309457394857984375983475943) 827 | 828 | iconst = integral (Range.exponential 0 maxBound) 829 | 830 | -- * Types 831 | 832 | nullable = pure False 833 | 834 | arrayDimensionsAmount = int (Range.exponential 0 4) 835 | 836 | -- ** Typename 837 | 838 | typename = Typename <$> bool <*> simpleTypename <*> pure False <*> maybe ((,) <$> typenameArrayDimensions <*> pure False) 839 | 840 | typenameArrayDimensions = 841 | choice 842 | [ BoundsTypenameArrayDimensions <$> arrayBounds, 843 | ExplicitTypenameArrayDimensions <$> maybe iconst 844 | ] 845 | 846 | arrayBounds = nonEmpty (Range.exponential 1 4) (maybe iconst) 847 | 848 | simpleTypename = 849 | choice 850 | [ GenericTypeSimpleTypename <$> genericType, 851 | NumericSimpleTypename <$> numeric, 852 | BitSimpleTypename <$> bit, 853 | CharacterSimpleTypename <$> character, 854 | ConstDatetimeSimpleTypename <$> constDatetime, 855 | ConstIntervalSimpleTypename <$> choice [Left <$> maybe interval, Right <$> iconst] 856 | ] 857 | 858 | genericType = GenericType <$> typeFunctionName <*> maybe attrs <*> maybe typeModifiers 859 | 860 | attrs = nonEmpty (Range.exponential 1 10) attrName 861 | 862 | typeModifiers = exprList 863 | 864 | typeList = nonEmpty (Range.exponential 1 7) typename 865 | 866 | subType = enumBounded 867 | 868 | -- * Names 869 | 870 | columnref = Columnref <$> colId <*> maybe indirection 871 | 872 | keywordNotInSet = \set -> notInSet set $ do 873 | a <- element startList 874 | b <- text (Range.linear 1 29) (element contList) 875 | return (Text.cons a b) 876 | where 877 | startList = "abcdefghijklmnopqrstuvwxyz_" <> List.filter isLower (enumFromTo '\200' '\377') 878 | contList = startList <> "0123456789$" 879 | 880 | ident = identWithSet mempty 881 | 882 | typeName = identWithSet KeywordSet.typeFunctionName 883 | 884 | name = identWithSet KeywordSet.colId 885 | 886 | cursorName = name 887 | 888 | identWithSet set = 889 | frequency 890 | [ (95,) $ UnquotedIdent <$> (keywordNotInSet . HashSet.difference KeywordSet.keyword) set, 891 | (5,) $ QuotedIdent <$> text (Range.linear 1 30) quotedChar 892 | ] 893 | 894 | qualifiedName = 895 | choice 896 | [ SimpleQualifiedName <$> name, 897 | IndirectedQualifiedName <$> name <*> indirection 898 | ] 899 | 900 | indirection = nonEmpty (Range.linear 1 3) indirectionEl 901 | 902 | indirectionEl = 903 | choice 904 | [ AttrNameIndirectionEl <$> name, 905 | pure AllIndirectionEl, 906 | ExprIndirectionEl <$> (small aExpr), 907 | SliceIndirectionEl <$> maybe (small aExpr) <*> maybe (small aExpr) 908 | ] 909 | 910 | quotedChar = filter (not . isControl) unicode 911 | 912 | colId = name 913 | 914 | colLabel = name 915 | 916 | attrName = colLabel 917 | 918 | typeFunctionName = name 919 | 920 | funcName = 921 | choice 922 | [ TypeFuncName <$> typeFunctionName, 923 | IndirectedFuncName <$> colId <*> indirection 924 | ] 925 | 926 | anyName = AnyName <$> colId <*> maybe attrs 927 | 928 | -- * Indexes 929 | 930 | indexParams = nonEmpty (Range.exponential 1 5) indexElem 931 | 932 | indexElem = IndexElem <$> indexElemDef <*> maybe collate <*> maybe class_ <*> maybe ascDesc <*> maybe nullsOrder 933 | 934 | indexElemDef = 935 | choice 936 | [ IdIndexElemDef <$> colId, 937 | FuncIndexElemDef <$> funcExprWindowless, 938 | ExprIndexElemDef <$> aExpr 939 | ] 940 | 941 | collate = anyName 942 | 943 | class_ = anyName 944 | 945 | ascDesc = enumBounded 946 | 947 | nullsOrder = enumBounded 948 | 949 | -- * Helpers 950 | 951 | listElement :: [a] -> Gen a 952 | listElement = element 953 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/CharSet.hs: -------------------------------------------------------------------------------- 1 | module PostgresqlSyntax.CharSet where 2 | 3 | import qualified Data.Text as Text 4 | import qualified PostgresqlSyntax.KeywordSet as KeywordSet 5 | import PostgresqlSyntax.Prelude 6 | 7 | {-# NOINLINE symbolicBinOp #-} 8 | symbolicBinOp :: HashSet Char 9 | symbolicBinOp = KeywordSet.symbolicBinOp & toList & mconcat & Text.unpack & fromList 10 | 11 | {-# NOINLINE hexDigit #-} 12 | hexDigit :: HashSet Char 13 | hexDigit = fromList "0123456789abcdefABCDEF" 14 | 15 | {-# NOINLINE op #-} 16 | op :: HashSet Char 17 | op = fromList "+-*/<>=~!@#%^&|`?" 18 | 19 | {-# NOINLINE prohibitionLiftingOp #-} 20 | prohibitionLiftingOp :: HashSet Char 21 | prohibitionLiftingOp = fromList "~!@#%^&|`?" 22 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Extras/HeadedMegaparsec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-redundant-constraints -Wno-dodgy-imports -Wno-unused-imports #-} 2 | 3 | -- | 4 | -- Generic helpers for HeadedMegaparsec. 5 | module PostgresqlSyntax.Extras.HeadedMegaparsec where 6 | 7 | import Control.Applicative.Combinators hiding (some) 8 | import Control.Applicative.Combinators.NonEmpty 9 | import qualified Data.Text as Text 10 | import HeadedMegaparsec hiding (string) 11 | import PostgresqlSyntax.Prelude hiding (bit, expr, filter, head, many, option, some, sortBy, tail, try) 12 | import Text.Megaparsec (Parsec, Stream, TraversableStream, VisualStream) 13 | import qualified Text.Megaparsec as Megaparsec 14 | import qualified Text.Megaparsec.Char as MegaparsecChar 15 | import qualified Text.Megaparsec.Char.Lexer as MegaparsecLexer 16 | 17 | -- $setup 18 | -- >>> testParser parser = either putStr print . run parser 19 | 20 | -- * Executors 21 | 22 | run :: (Ord err, VisualStream strm, TraversableStream strm, Megaparsec.ShowErrorComponent err) => HeadedParsec err strm a -> strm -> Either String a 23 | run p = first Megaparsec.errorBundlePretty . Megaparsec.runParser (toParsec p <* Megaparsec.eof) "" 24 | 25 | -- * Primitives 26 | 27 | -- | 28 | -- Lifted megaparsec\'s `Megaparsec.eof`. 29 | eof :: (Ord err, Stream strm) => HeadedParsec err strm () 30 | eof = parse Megaparsec.eof 31 | 32 | -- | 33 | -- Lifted megaparsec\'s `Megaparsec.space`. 34 | space :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => HeadedParsec err strm () 35 | space = parse MegaparsecChar.space 36 | 37 | -- | 38 | -- Lifted megaparsec\'s `Megaparsec.space1`. 39 | space1 :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => HeadedParsec err strm () 40 | space1 = parse MegaparsecChar.space1 41 | 42 | -- | 43 | -- Lifted megaparsec\'s `Megaparsec.char`. 44 | char :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => Char -> HeadedParsec err strm Char 45 | char a = parse (MegaparsecChar.char a) 46 | 47 | -- | 48 | -- Lifted megaparsec\'s `Megaparsec.char'`. 49 | char' :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => Char -> HeadedParsec err strm Char 50 | char' a = parse (MegaparsecChar.char' a) 51 | 52 | -- | 53 | -- Lifted megaparsec\'s `Megaparsec.string`. 54 | string :: (Ord err, Stream strm) => Megaparsec.Tokens strm -> HeadedParsec err strm (Megaparsec.Tokens strm) 55 | string = parse . MegaparsecChar.string 56 | 57 | -- | 58 | -- Lifted megaparsec\'s `Megaparsec.string'`. 59 | string' :: (Ord err, Stream strm, FoldCase (Megaparsec.Tokens strm)) => Megaparsec.Tokens strm -> HeadedParsec err strm (Megaparsec.Tokens strm) 60 | string' = parse . MegaparsecChar.string' 61 | 62 | -- | 63 | -- Lifted megaparsec\'s `Megaparsec.takeWhileP`. 64 | takeWhileP :: (Ord err, Stream strm) => Maybe String -> (Megaparsec.Token strm -> Bool) -> HeadedParsec err strm (Megaparsec.Tokens strm) 65 | takeWhileP label predicate = parse (Megaparsec.takeWhileP label predicate) 66 | 67 | -- | 68 | -- Lifted megaparsec\'s `Megaparsec.takeWhile1P`. 69 | takeWhile1P :: (Ord err, Stream strm) => Maybe String -> (Megaparsec.Token strm -> Bool) -> HeadedParsec err strm (Megaparsec.Tokens strm) 70 | takeWhile1P label predicate = parse (Megaparsec.takeWhile1P label predicate) 71 | 72 | satisfy :: (Ord err, Stream strm) => (Megaparsec.Token strm -> Bool) -> HeadedParsec err strm (Megaparsec.Token strm) 73 | satisfy = parse . Megaparsec.satisfy 74 | 75 | decimal :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char, Integral decimal) => HeadedParsec err strm decimal 76 | decimal = parse MegaparsecLexer.decimal 77 | 78 | float :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char, RealFloat float) => HeadedParsec err strm float 79 | float = parse MegaparsecLexer.float 80 | 81 | -- * Combinators 82 | 83 | sep1 :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => HeadedParsec err strm separtor -> HeadedParsec err strm a -> HeadedParsec err strm (NonEmpty a) 84 | sep1 separator parser = do 85 | head <- parser 86 | endHead 87 | tail <- many $ separator *> parser 88 | return (head :| tail) 89 | 90 | sepEnd1 :: (Ord err, Stream strm, Megaparsec.Token strm ~ Char) => HeadedParsec err strm separator -> HeadedParsec err strm end -> HeadedParsec err strm el -> HeadedParsec err strm (NonEmpty el, end) 91 | sepEnd1 sepP endP elP = do 92 | headEl <- elP 93 | let loop !list = do 94 | _ <- sepP 95 | asum 96 | [ do 97 | end <- endP 98 | return (headEl :| reverse list, end), 99 | do 100 | el <- elP 101 | loop (el : list) 102 | ] 103 | in loop [] 104 | 105 | notFollowedBy :: (Ord err, Stream strm) => HeadedParsec err strm a -> HeadedParsec err strm () 106 | notFollowedBy a = parse (Megaparsec.notFollowedBy (toParsec a)) 107 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Extras/NonEmpty.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-dodgy-imports #-} 2 | 3 | module PostgresqlSyntax.Extras.NonEmpty where 4 | 5 | import Data.List.NonEmpty 6 | import PostgresqlSyntax.Prelude hiding (cons, fromList, head, init, last, reverse, tail, uncons) 7 | 8 | -- | 9 | -- >>> intersperseFoldMap ", " id (fromList ["a"]) 10 | -- "a" 11 | -- 12 | -- >>> intersperseFoldMap ", " id (fromList ["a", "b", "c"]) 13 | -- "a, b, c" 14 | intersperseFoldMap :: (Monoid m) => m -> (a -> m) -> NonEmpty a -> m 15 | intersperseFoldMap a b (c :| d) = b c <> foldMap (mappend a . b) d 16 | 17 | unsnoc :: NonEmpty a -> (Maybe (NonEmpty a), a) 18 | unsnoc = 19 | let build1 = \case 20 | a :| b -> build2 a b 21 | build2 a = \case 22 | b : c -> build3 b (a :| []) c 23 | _ -> (Nothing, a) 24 | build3 a b = \case 25 | c : d -> build3 c (cons a b) d 26 | _ -> (Just (reverse b), a) 27 | in build1 28 | 29 | consAndUnsnoc :: a -> NonEmpty a -> (NonEmpty a, a) 30 | consAndUnsnoc a b = case unsnoc b of 31 | (c, d) -> case c of 32 | Just e -> (cons a e, d) 33 | Nothing -> (pure a, d) 34 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Extras/TextBuilder.hs: -------------------------------------------------------------------------------- 1 | module PostgresqlSyntax.Extras.TextBuilder where 2 | 3 | import PostgresqlSyntax.Prelude 4 | import TextBuilder 5 | 6 | char7 :: Char -> TextBuilder 7 | char7 = char 8 | 9 | intDec :: Int -> TextBuilder 10 | intDec = decimal 11 | 12 | int64Dec :: Int64 -> TextBuilder 13 | int64Dec = decimal 14 | 15 | doubleDec :: Double -> TextBuilder 16 | doubleDec = fromString . show 17 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/KeywordSet.hs: -------------------------------------------------------------------------------- 1 | module PostgresqlSyntax.KeywordSet where 2 | 3 | import Data.HashSet 4 | import PostgresqlSyntax.Prelude hiding (fromList, toList) 5 | 6 | {-# NOINLINE keyword #-} 7 | {- 8 | From https://github.com/postgres/postgres/blob/1aac32df89eb19949050f6f27c268122833ad036/src/include/parser/kwlist.h 9 | -} 10 | keyword :: HashSet Text 11 | keyword = fromList ["abort", "absolute", "access", "action", "add", "admin", "after", "aggregate", "all", "also", "alter", "always", "analyse", "analyze", "and", "any", "array", "as", "asc", "assertion", "assignment", "asymmetric", "at", "attach", "attribute", "authorization", "backward", "before", "begin", "between", "bigint", "binary", "bit", "boolean", "both", "by", "cache", "call", "called", "cascade", "cascaded", "case", "cast", "catalog", "chain", "char", "character", "characteristics", "check", "checkpoint", "class", "close", "cluster", "coalesce", "collate", "collation", "column", "columns", "comment", "comments", "commit", "committed", "concurrently", "configuration", "conflict", "connection", "constraint", "constraints", "content", "continue", "conversion", "copy", "cost", "create", "cross", "csv", "cube", "current", "current_catalog", "current_date", "current_role", "current_schema", "current_time", "current_timestamp", "current_user", "cursor", "cycle", "data", "database", "day", "deallocate", "dec", "decimal", "declare", "default", "defaults", "deferrable", "deferred", "definer", "delete", "delimiter", "delimiters", "depends", "desc", "detach", "dictionary", "disable", "discard", "distinct", "do", "document", "domain", "double", "drop", "each", "else", "enable", "encoding", "encrypted", "end", "enum", "escape", "event", "except", "exclude", "excluding", "exclusive", "execute", "exists", "explain", "expression", "extension", "external", "extract", "false", "family", "fetch", "filter", "first", "float", "following", "for", "force", "foreign", "forward", "freeze", "from", "full", "function", "functions", "generated", "global", "grant", "granted", "greatest", "group", "grouping", "groups", "handler", "having", "header", "hold", "hour", "identity", "if", "ilike", "immediate", "immutable", "implicit", "import", "in", "include", "including", "increment", "index", "indexes", "inherit", "inherits", "initially", "inline", "inner", "inout", "input", "insensitive", "insert", "instead", "int", "integer", "intersect", "interval", "into", "invoker", "is", "isnull", "isolation", "join", "key", "label", "language", "large", "last", "lateral", "leading", "leakproof", "least", "left", "level", "like", "limit", "listen", "load", "local", "localtime", "localtimestamp", "location", "lock", "locked", "logged", "mapping", "match", "materialized", "maxvalue", "method", "minute", "minvalue", "mode", "month", "move", "name", "names", "national", "natural", "nchar", "new", "next", "nfc", "nfd", "nfkc", "nfkd", "no", "none", "normalize", "normalized", "not", "nothing", "notify", "notnull", "nowait", "null", "nullif", "nulls", "numeric", "object", "of", "off", "offset", "oids", "old", "on", "only", "operator", "option", "options", "or", "order", "ordinality", "others", "out", "outer", "over", "overlaps", "overlay", "overriding", "owned", "owner", "parallel", "parser", "partial", "partition", "passing", "password", "placing", "plans", "policy", "position", "preceding", "precision", "prepare", "prepared", "preserve", "primary", "prior", "privileges", "procedural", "procedure", "procedures", "program", "publication", "quote", "range", "read", "real", "reassign", "recheck", "recursive", "ref", "references", "referencing", "refresh", "reindex", "relative", "release", "rename", "repeatable", "replace", "replica", "reset", "restart", "restrict", "returning", "returns", "revoke", "right", "role", "rollback", "rollup", "routine", "routines", "row", "rows", "rule", "savepoint", "schema", "schemas", "scroll", "search", "second", "security", "select", "sequence", "sequences", "serializable", "server", "session", "session_user", "set", "setof", "sets", "share", "show", "similar", "simple", "skip", "smallint", "snapshot", "some", "sql", "stable", "standalone", "start", "statement", "statistics", "stdin", "stdout", "storage", "stored", "strict", "strip", "subscription", "substring", "support", "symmetric", "sysid", "system", "table", "tables", "tablesample", "tablespace", "temp", "template", "temporary", "text", "then", "ties", "time", "timestamp", "to", "trailing", "transaction", "transform", "treat", "trigger", "trim", "true", "truncate", "trusted", "type", "types", "uescape", "unbounded", "uncommitted", "unencrypted", "union", "unique", "unknown", "unlisten", "unlogged", "until", "update", "user", "using", "vacuum", "valid", "validate", "validator", "value", "values", "varchar", "variadic", "varying", "verbose", "version", "view", "views", "volatile", "when", "where", "whitespace", "window", "with", "within", "without", "work", "wrapper", "write", "xml", "xmlattributes", "xmlconcat", "xmlelement", "xmlexists", "xmlforest", "xmlnamespaces", "xmlparse", "xmlpi", "xmlroot", "xmlserialize", "xmltable", "year", "yes", "zone"] 12 | 13 | {-# NOINLINE unreservedKeyword #-} 14 | unreservedKeyword :: HashSet Text 15 | unreservedKeyword = fromList ["abort", "absolute", "access", "action", "add", "admin", "after", "aggregate", "also", "alter", "always", "assertion", "assignment", "at", "attach", "attribute", "backward", "before", "begin", "by", "cache", "call", "called", "cascade", "cascaded", "catalog", "chain", "characteristics", "checkpoint", "class", "close", "cluster", "columns", "comment", "comments", "commit", "committed", "configuration", "conflict", "connection", "constraints", "content", "continue", "conversion", "copy", "cost", "csv", "cube", "current", "cursor", "cycle", "data", "database", "day", "deallocate", "declare", "defaults", "deferred", "definer", "delete", "delimiter", "delimiters", "depends", "detach", "dictionary", "disable", "discard", "document", "domain", "double", "drop", "each", "enable", "encoding", "encrypted", "enum", "escape", "event", "exclude", "excluding", "exclusive", "execute", "explain", "extension", "external", "family", "filter", "first", "following", "force", "forward", "function", "functions", "generated", "global", "granted", "groups", "handler", "header", "hold", "hour", "identity", "if", "immediate", "immutable", "implicit", "import", "include", "including", "increment", "index", "indexes", "inherit", "inherits", "inline", "input", "insensitive", "insert", "instead", "invoker", "isolation", "key", "label", "language", "large", "last", "leakproof", "level", "listen", "load", "local", "location", "lock", "locked", "logged", "mapping", "match", "materialized", "maxvalue", "method", "minute", "minvalue", "mode", "month", "move", "name", "names", "new", "next", "no", "nothing", "notify", "nowait", "nulls", "object", "of", "off", "oids", "old", "operator", "option", "options", "ordinality", "others", "over", "overriding", "owned", "owner", "parallel", "parser", "partial", "partition", "passing", "password", "plans", "policy", "preceding", "prepare", "prepared", "preserve", "prior", "privileges", "procedural", "procedure", "procedures", "program", "publication", "quote", "range", "read", "reassign", "recheck", "recursive", "ref", "referencing", "refresh", "reindex", "relative", "release", "rename", "repeatable", "replace", "replica", "reset", "restart", "restrict", "returns", "revoke", "role", "rollback", "rollup", "routine", "routines", "rows", "rule", "savepoint", "schema", "schemas", "scroll", "search", "second", "security", "sequence", "sequences", "serializable", "server", "session", "set", "sets", "share", "show", "simple", "skip", "snapshot", "sql", "stable", "standalone", "start", "statement", "statistics", "stdin", "stdout", "storage", "stored", "strict", "strip", "subscription", "support", "sysid", "system", "tables", "tablespace", "temp", "template", "temporary", "text", "ties", "transaction", "transform", "trigger", "truncate", "trusted", "type", "types", "unbounded", "uncommitted", "unencrypted", "unknown", "unlisten", "unlogged", "until", "update", "vacuum", "valid", "validate", "validator", "value", "varying", "version", "view", "views", "volatile", "whitespace", "within", "without", "work", "wrapper", "write", "xml", "year", "yes", "zone"] 16 | 17 | {-# NOINLINE colNameKeyword #-} 18 | colNameKeyword :: HashSet Text 19 | colNameKeyword = fromList ["between", "bigint", "bit", "boolean", "char", "character", "coalesce", "dec", "decimal", "exists", "extract", "float", "greatest", "grouping", "inout", "int", "integer", "interval", "least", "national", "nchar", "none", "normalize", "nullif", "numeric", "out", "overlay", "position", "precision", "real", "row", "setof", "smallint", "substring", "time", "timestamp", "treat", "trim", "values", "varchar", "xmlattributes", "xmlconcat", "xmlelement", "xmlexists", "xmlforest", "xmlnamespaces", "xmlparse", "xmlpi", "xmlroot", "xmlserialize", "xmltable"] 20 | 21 | {-# NOINLINE typeFuncNameKeyword #-} 22 | typeFuncNameKeyword :: HashSet Text 23 | typeFuncNameKeyword = fromList ["authorization", "binary", "collation", "concurrently", "cross", "current_schema", "freeze", "full", "ilike", "inner", "is", "isnull", "join", "left", "like", "natural", "notnull", "outer", "overlaps", "right", "similar", "tablesample", "verbose"] 24 | 25 | {-# NOINLINE reservedKeyword #-} 26 | reservedKeyword :: HashSet Text 27 | reservedKeyword = fromList ["all", "analyse", "analyze", "and", "any", "array", "as", "asc", "asymmetric", "both", "case", "cast", "check", "collate", "column", "constraint", "create", "current_catalog", "current_date", "current_role", "current_time", "current_timestamp", "current_user", "default", "deferrable", "desc", "distinct", "do", "else", "end", "except", "false", "fetch", "for", "foreign", "from", "grant", "group", "having", "in", "initially", "intersect", "into", "lateral", "leading", "limit", "localtime", "localtimestamp", "not", "null", "offset", "on", "only", "or", "order", "placing", "primary", "references", "returning", "select", "session_user", "some", "symmetric", "table", "then", "to", "trailing", "true", "union", "unique", "user", "using", "variadic", "when", "where", "window", "with"] 28 | 29 | {-# NOINLINE symbolicBinOp #-} 30 | symbolicBinOp :: HashSet Text 31 | symbolicBinOp = fromList ["+", "-", "*", "/", "%", "^", "<", ">", "=", "<=", ">=", "<>", "~~", "~~*", "!~~", "!~~*", "~", "~*", "!~", "!~*"] 32 | 33 | {-# NOINLINE lexicalBinOp #-} 34 | lexicalBinOp :: HashSet Text 35 | lexicalBinOp = fromList ["and", "or"] 36 | 37 | {-# NOINLINE colId #-} 38 | colId :: HashSet Text 39 | colId = unions [unreservedKeyword, colNameKeyword] 40 | 41 | {- 42 | type_function_name: 43 | | IDENT 44 | | unreserved_keyword 45 | | type_func_name_keyword 46 | -} 47 | {-# NOINLINE typeFunctionName #-} 48 | typeFunctionName :: HashSet Text 49 | typeFunctionName = unions [unreservedKeyword, typeFuncNameKeyword] 50 | 51 | -- | 52 | -- As per the following comment from the original scanner definition: 53 | -- 54 | -- /* 55 | -- * Likewise, if what we have left is two chars, and 56 | -- * those match the tokens ">=", "<=", "=>", "<>" or 57 | -- * "!=", then we must return the appropriate token 58 | -- * rather than the generic Op. 59 | -- */ 60 | {-# NOINLINE nonOp #-} 61 | nonOp :: HashSet Text 62 | nonOp = fromList [">=", "<=", "=>", "<>", "!="] <> mathOp 63 | 64 | {-# NOINLINE mathOp #-} 65 | mathOp :: HashSet Text 66 | mathOp = fromList ["<>", ">=", "!=", "<=", "+", "-", "*", "/", "%", "^", "<", ">", "="] 67 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Predicate.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-redundant-constraints #-} 2 | 3 | module PostgresqlSyntax.Predicate where 4 | 5 | import qualified Data.HashSet as HashSet 6 | import qualified PostgresqlSyntax.CharSet as CharSet 7 | import qualified PostgresqlSyntax.KeywordSet as KeywordSet 8 | import PostgresqlSyntax.Prelude 9 | 10 | -- * Generic 11 | 12 | -- | 13 | -- >>> test = oneOf [(==3), (==7), (==3), (==5)] 14 | -- >>> test 1 15 | -- False 16 | -- 17 | -- >>> test 3 18 | -- True 19 | -- 20 | -- >>> test 5 21 | -- True 22 | oneOf :: [a -> Bool] -> a -> Bool 23 | oneOf = foldr (\a b c -> a c || b c) (const False) 24 | 25 | inSet :: (Eq a, Hashable a) => HashSet a -> a -> Bool 26 | inSet = flip HashSet.member 27 | 28 | hexDigit :: Char -> Bool 29 | hexDigit = inSet CharSet.hexDigit 30 | 31 | {- 32 | ident_start [A-Za-z\200-\377_] 33 | -} 34 | firstIdentifierChar :: Char -> Bool 35 | firstIdentifierChar x = isAlpha x || x == '_' || x >= '\200' && x <= '\377' 36 | 37 | {- 38 | ident_cont [A-Za-z\200-\377_0-9\$] 39 | -} 40 | notFirstIdentifierChar :: Char -> Bool 41 | notFirstIdentifierChar x = isAlphaNum x || x == '_' || x == '$' || x >= '\200' && x <= '\377' 42 | 43 | keyword :: Text -> Bool 44 | keyword = inSet KeywordSet.keyword 45 | 46 | unreservedKeyword :: Text -> Bool 47 | unreservedKeyword = inSet KeywordSet.unreservedKeyword 48 | 49 | colNameKeyword :: Text -> Bool 50 | colNameKeyword = inSet KeywordSet.colNameKeyword 51 | 52 | typeFuncNameKeyword :: Text -> Bool 53 | typeFuncNameKeyword = inSet KeywordSet.typeFuncNameKeyword 54 | 55 | reservedKeyword :: Text -> Bool 56 | reservedKeyword = inSet KeywordSet.reservedKeyword 57 | 58 | {-# NOINLINE symbolicBinOpChar #-} 59 | symbolicBinOpChar :: Char -> Bool 60 | symbolicBinOpChar = inSet CharSet.symbolicBinOp 61 | 62 | -- ** Op chars 63 | 64 | opChar :: Char -> Bool 65 | opChar = inSet CharSet.op 66 | 67 | prohibitedOpChar :: Char -> Bool 68 | prohibitedOpChar a = a == '+' || a == '-' 69 | 70 | prohibitionLiftingOpChar :: Char -> Bool 71 | prohibitionLiftingOpChar = inSet CharSet.prohibitionLiftingOp 72 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Prelude.hs: -------------------------------------------------------------------------------- 1 | module PostgresqlSyntax.Prelude 2 | ( module Exports, 3 | showAsText, 4 | suffixRec, 5 | ) 6 | where 7 | 8 | import Control.Applicative as Exports 9 | import Control.Arrow as Exports hiding (first, second) 10 | import Control.Category as Exports 11 | import Control.Concurrent as Exports 12 | import Control.Exception as Exports 13 | import Control.Monad as Exports hiding (fail, forM, forM_, mapM, mapM_, msum, sequence, sequence_) 14 | import Control.Monad.Fail as Exports 15 | import Control.Monad.Fix as Exports hiding (fix) 16 | import Control.Monad.IO.Class as Exports 17 | import Control.Monad.ST as Exports 18 | import Data.Bifunctor as Exports 19 | import Data.Bits as Exports 20 | import Data.Bool as Exports 21 | import Data.ByteString as Exports (ByteString) 22 | import Data.CaseInsensitive as Exports (CI, FoldCase) 23 | import Data.Char as Exports 24 | import Data.Coerce as Exports 25 | import Data.Complex as Exports 26 | import Data.Data as Exports 27 | import Data.Dynamic as Exports 28 | import Data.Either as Exports 29 | import Data.Fixed as Exports 30 | import Data.Foldable as Exports 31 | import Data.Function as Exports hiding (id, (.)) 32 | import Data.Functor as Exports hiding (unzip) 33 | import Data.Functor.Identity as Exports 34 | import Data.HashMap.Strict as Exports (HashMap) 35 | import Data.HashSet as Exports (HashSet) 36 | import Data.Hashable as Exports (Hashable) 37 | import Data.IORef as Exports 38 | import Data.Int as Exports 39 | import Data.Ix as Exports 40 | import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons, unsnoc) 41 | import Data.List.NonEmpty as Exports (NonEmpty (..)) 42 | import Data.Maybe as Exports 43 | import Data.Monoid as Exports hiding (First (..), Last (..), (<>)) 44 | import Data.Ord as Exports 45 | import Data.Proxy as Exports 46 | import Data.Ratio as Exports 47 | import Data.STRef as Exports 48 | import Data.Semigroup as Exports 49 | import Data.String as Exports 50 | import Data.Text as Exports (Text) 51 | import Data.Traversable as Exports 52 | import Data.Tuple as Exports 53 | import Data.Unique as Exports 54 | import Data.Version as Exports 55 | import Data.Void as Exports 56 | import Data.Word as Exports 57 | import Debug.Trace as Exports 58 | import Foreign.ForeignPtr as Exports 59 | import Foreign.Ptr as Exports 60 | import Foreign.StablePtr as Exports 61 | import Foreign.Storable as Exports hiding (alignment, sizeOf) 62 | import GHC.Conc as Exports hiding (orElse, threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar) 63 | import GHC.Exts as Exports (IsList (Item, fromList), groupWith, inline, lazy, sortWith) 64 | import GHC.Generics as Exports (Generic, Generic1) 65 | import GHC.IO.Exception as Exports 66 | import Numeric as Exports 67 | import System.Environment as Exports 68 | import System.Exit as Exports 69 | import System.IO as Exports 70 | import System.IO.Error as Exports 71 | import System.IO.Unsafe as Exports 72 | import System.Mem as Exports 73 | import System.Mem.StableName as Exports 74 | import System.Timeout as Exports 75 | import Text.Printf as Exports (hPrintf, printf) 76 | import Text.Read as Exports (Read (..), readEither, readMaybe) 77 | import Unsafe.Coerce as Exports 78 | import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.)) 79 | 80 | showAsText :: (Show a) => a -> Text 81 | showAsText = show >>> fromString 82 | 83 | -- | 84 | -- Compose a monad, which attempts to extend a value, based on the following input. 85 | -- It does so recursively until the suffix alternative fails. 86 | suffixRec :: (MonadPlus m) => m a -> (a -> m a) -> m a 87 | suffixRec base suffix = base >>= extendMany suffix 88 | 89 | extendMany :: (MonadPlus m) => (a -> m a) -> a -> m a 90 | extendMany attempt = loop 91 | where 92 | loop !state = 93 | optional (attempt state) >>= \case 94 | Nothing -> pure state 95 | Just newState -> loop newState 96 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Rendering.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-missing-signatures -Wno-dodgy-imports #-} 2 | 3 | module PostgresqlSyntax.Rendering 4 | ( toText, 5 | module PostgresqlSyntax.Rendering, 6 | ) 7 | where 8 | 9 | import qualified Data.Text as Text 10 | import qualified Data.Text.Encoding as Text 11 | import PostgresqlSyntax.Ast 12 | import qualified PostgresqlSyntax.Extras.NonEmpty as NonEmpty 13 | import PostgresqlSyntax.Extras.TextBuilder 14 | import PostgresqlSyntax.Prelude hiding (aExpr, bit, fromList, many, option, sortBy, try) 15 | import TextBuilder 16 | 17 | -- * Execution 18 | 19 | toByteString :: TextBuilder -> ByteString 20 | toByteString = Text.encodeUtf8 . toText 21 | 22 | -- * Helpers 23 | 24 | commaNonEmpty :: (a -> TextBuilder) -> NonEmpty a -> TextBuilder 25 | commaNonEmpty = NonEmpty.intersperseFoldMap ", " 26 | 27 | spaceNonEmpty :: (a -> TextBuilder) -> NonEmpty a -> TextBuilder 28 | spaceNonEmpty = NonEmpty.intersperseFoldMap " " 29 | 30 | lexemes :: [TextBuilder] -> TextBuilder 31 | lexemes = mconcat . intersperse " " 32 | 33 | optLexemes :: [Maybe TextBuilder] -> TextBuilder 34 | optLexemes = lexemes . catMaybes 35 | 36 | inParens :: TextBuilder -> TextBuilder 37 | inParens a = "(" <> a <> ")" 38 | 39 | inBrackets :: TextBuilder -> TextBuilder 40 | inBrackets a = "[" <> a <> "]" 41 | 42 | prefixMaybe :: (a -> TextBuilder) -> Maybe a -> TextBuilder 43 | prefixMaybe a = foldMap (flip mappend " " . a) 44 | 45 | suffixMaybe :: (a -> TextBuilder) -> Maybe a -> TextBuilder 46 | suffixMaybe a = foldMap (mappend " " . a) 47 | 48 | -- * Statements 49 | 50 | preparableStmt = \case 51 | SelectPreparableStmt a -> selectStmt a 52 | InsertPreparableStmt a -> insertStmt a 53 | UpdatePreparableStmt a -> updateStmt a 54 | DeletePreparableStmt a -> deleteStmt a 55 | CallPreparableStmt a -> callStmt a 56 | 57 | -- * Call 58 | 59 | callStmt (CallStmt a) = 60 | "CALL " <> funcApplication a 61 | 62 | -- * Insert 63 | 64 | insertStmt (InsertStmt a b c d e) = 65 | prefixMaybe withClause a 66 | <> "INSERT INTO " 67 | <> insertTarget b 68 | <> " " 69 | <> insertRest c 70 | <> suffixMaybe onConflict d 71 | <> suffixMaybe returningClause e 72 | 73 | insertTarget (InsertTarget a b) = 74 | qualifiedName a <> foldMap (mappend " AS " . colId) b 75 | 76 | insertRest = \case 77 | SelectInsertRest a b c -> 78 | optLexemes 79 | [ fmap (inParens . insertColumnList) a, 80 | fmap insertRestOverriding b, 81 | Just (selectStmt c) 82 | ] 83 | DefaultValuesInsertRest -> "DEFAULT VALUES" 84 | 85 | insertRestOverriding a = "OVERRIDING " <> overrideKind a <> " VALUE" 86 | 87 | overrideKind = \case 88 | UserOverrideKind -> "USER" 89 | SystemOverrideKind -> "SYSTEM" 90 | 91 | insertColumnList = commaNonEmpty insertColumnItem 92 | 93 | insertColumnItem (InsertColumnItem a b) = colId a <> suffixMaybe indirection b 94 | 95 | onConflict (OnConflict a b) = "ON CONFLICT" <> suffixMaybe confExpr a <> " DO " <> onConflictDo b 96 | 97 | onConflictDo = \case 98 | UpdateOnConflictDo a b -> "UPDATE SET " <> setClauseList a <> suffixMaybe whereClause b 99 | NothingOnConflictDo -> "NOTHING" 100 | 101 | confExpr = \case 102 | WhereConfExpr a b -> inParens (indexParams a) <> suffixMaybe whereClause b 103 | ConstraintConfExpr a -> "ON CONSTRAINT " <> name a 104 | 105 | returningClause = mappend "RETURNING " . targetList 106 | 107 | -- * Update 108 | 109 | updateStmt (UpdateStmt a b c d e f) = 110 | prefixMaybe withClause a 111 | <> "UPDATE " 112 | <> relationExprOptAlias b 113 | <> " " 114 | <> "SET " 115 | <> setClauseList c 116 | <> suffixMaybe fromClause d 117 | <> suffixMaybe whereOrCurrentClause e 118 | <> suffixMaybe returningClause f 119 | 120 | setClauseList = commaNonEmpty setClause 121 | 122 | setClause = \case 123 | TargetSetClause a b -> setTarget a <> " = " <> aExpr b 124 | TargetListSetClause a b -> inParens (setTargetList a) <> " = " <> aExpr b 125 | 126 | setTarget (SetTarget a b) = colId a <> suffixMaybe indirection b 127 | 128 | setTargetList = commaNonEmpty setTarget 129 | 130 | -- * Delete 131 | 132 | deleteStmt (DeleteStmt a b c d e) = 133 | prefixMaybe withClause a 134 | <> "DELETE FROM " 135 | <> relationExprOptAlias b 136 | <> suffixMaybe usingClause c 137 | <> suffixMaybe whereOrCurrentClause d 138 | <> suffixMaybe returningClause e 139 | 140 | usingClause = mappend "USING " . fromList 141 | 142 | -- * Select 143 | 144 | selectStmt = \case 145 | Left a -> selectNoParens a 146 | Right a -> selectWithParens a 147 | 148 | selectNoParens (SelectNoParens a b c d e) = 149 | optLexemes 150 | [ fmap withClause a, 151 | Just (selectClause b), 152 | fmap sortClause c, 153 | fmap selectLimit d, 154 | fmap forLockingClause e 155 | ] 156 | 157 | selectWithParens = 158 | inParens . \case 159 | NoParensSelectWithParens a -> selectNoParens a 160 | WithParensSelectWithParens a -> selectWithParens a 161 | 162 | withClause (WithClause a b) = 163 | "WITH " <> bool "" "RECURSIVE " a <> commaNonEmpty commonTableExpr b 164 | 165 | commonTableExpr (CommonTableExpr a b c d) = 166 | optLexemes 167 | [ Just (ident a), 168 | fmap (inParens . commaNonEmpty ident) b, 169 | Just "AS", 170 | fmap materialization c, 171 | Just (inParens (preparableStmt d)) 172 | ] 173 | 174 | materialization = bool "NOT MATERIALIZED" "MATERIALIZED" 175 | 176 | selectLimit = \case 177 | LimitOffsetSelectLimit a b -> lexemes [limitClause a, offsetClause b] 178 | OffsetLimitSelectLimit a b -> lexemes [offsetClause a, limitClause b] 179 | LimitSelectLimit a -> limitClause a 180 | OffsetSelectLimit a -> offsetClause a 181 | 182 | limitClause = \case 183 | LimitLimitClause a b -> "LIMIT " <> selectLimitValue a <> foldMap (mappend ", " . aExpr) b 184 | FetchOnlyLimitClause a b c -> 185 | optLexemes 186 | [ Just "FETCH", 187 | Just (firstOrNext a), 188 | fmap selectFetchFirstValue b, 189 | Just (rowOrRows c), 190 | Just "ONLY" 191 | ] 192 | 193 | firstOrNext = bool "FIRST" "NEXT" 194 | 195 | rowOrRows = bool "ROW" "ROWS" 196 | 197 | selectFetchFirstValue = \case 198 | ExprSelectFetchFirstValue a -> cExpr a 199 | NumSelectFetchFirstValue a b -> bool "+" "-" a <> intOrFloat b 200 | 201 | intOrFloat = either int64Dec doubleDec 202 | 203 | selectLimitValue = \case 204 | ExprSelectLimitValue a -> aExpr a 205 | AllSelectLimitValue -> "ALL" 206 | 207 | offsetClause = \case 208 | ExprOffsetClause a -> "OFFSET " <> aExpr a 209 | FetchFirstOffsetClause a b -> "OFFSET " <> selectFetchFirstValue a <> " " <> rowOrRows b 210 | 211 | forLockingClause = \case 212 | ItemsForLockingClause a -> spaceNonEmpty forLockingItem a 213 | ReadOnlyForLockingClause -> "FOR READ ONLY" 214 | 215 | forLockingItem (ForLockingItem a b c) = 216 | optLexemes 217 | [ Just (forLockingStrength a), 218 | fmap lockedRelsList b, 219 | fmap nowaitOrSkip c 220 | ] 221 | 222 | forLockingStrength = \case 223 | UpdateForLockingStrength -> "FOR UPDATE" 224 | NoKeyUpdateForLockingStrength -> "FOR NO KEY UPDATE" 225 | ShareForLockingStrength -> "FOR SHARE" 226 | KeyForLockingStrength -> "FOR KEY SHARE" 227 | 228 | lockedRelsList a = "OF " <> commaNonEmpty qualifiedName a 229 | 230 | nowaitOrSkip = bool "NOWAIT" "SKIP LOCKED" 231 | 232 | selectClause = either simpleSelect selectWithParens 233 | 234 | simpleSelect = \case 235 | NormalSimpleSelect a b c d e f g -> 236 | optLexemes 237 | [ Just "SELECT", 238 | fmap targeting a, 239 | fmap intoClause b, 240 | fmap fromClause c, 241 | fmap whereClause d, 242 | fmap groupClause e, 243 | fmap havingClause f, 244 | fmap windowClause g 245 | ] 246 | ValuesSimpleSelect a -> valuesClause a 247 | TableSimpleSelect a -> "TABLE " <> relationExpr a 248 | BinSimpleSelect a b c d -> selectClause b <> " " <> selectBinOp a <> foldMap (mappend " " . allOrDistinct) c <> " " <> selectClause d 249 | 250 | selectBinOp = \case 251 | UnionSelectBinOp -> "UNION" 252 | IntersectSelectBinOp -> "INTERSECT" 253 | ExceptSelectBinOp -> "EXCEPT" 254 | 255 | targeting = \case 256 | NormalTargeting a -> targetList a 257 | AllTargeting a -> "ALL" <> suffixMaybe targetList a 258 | DistinctTargeting a b -> "DISTINCT" <> suffixMaybe onExpressionsClause a <> " " <> commaNonEmpty targetEl b 259 | 260 | targetList = commaNonEmpty targetEl 261 | 262 | onExpressionsClause a = "ON (" <> commaNonEmpty aExpr a <> ")" 263 | 264 | targetEl = \case 265 | AliasedExprTargetEl a b -> aExpr a <> " AS " <> ident b 266 | ImplicitlyAliasedExprTargetEl a b -> aExpr a <> " " <> ident b 267 | ExprTargetEl a -> aExpr a 268 | AsteriskTargetEl -> "*" 269 | 270 | -- * Select Into 271 | 272 | intoClause a = "INTO " <> optTempTableName a 273 | 274 | optTempTableName = \case 275 | TemporaryOptTempTableName a b -> optLexemes [Just "TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 276 | TempOptTempTableName a b -> optLexemes [Just "TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 277 | LocalTemporaryOptTempTableName a b -> optLexemes [Just "LOCAL TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 278 | LocalTempOptTempTableName a b -> optLexemes [Just "LOCAL TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 279 | GlobalTemporaryOptTempTableName a b -> optLexemes [Just "GLOBAL TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 280 | GlobalTempOptTempTableName a b -> optLexemes [Just "GLOBAL TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 281 | UnloggedOptTempTableName a b -> optLexemes [Just "UNLOGGED", bool Nothing (Just "TABLE") a, Just (qualifiedName b)] 282 | TableOptTempTableName a -> "TABLE " <> qualifiedName a 283 | QualifedOptTempTableName a -> qualifiedName a 284 | 285 | -- * From 286 | 287 | fromClause a = "FROM " <> fromList a 288 | 289 | fromList = commaNonEmpty tableRef 290 | 291 | tableRef = \case 292 | RelationExprTableRef a b c -> 293 | optLexemes 294 | [ Just (relationExpr a), 295 | fmap aliasClause b, 296 | fmap tablesampleClause c 297 | ] 298 | FuncTableRef a b c -> 299 | optLexemes 300 | [ if a then Just "LATERAL" else Nothing, 301 | Just (funcTable b), 302 | fmap funcAliasClause c 303 | ] 304 | SelectTableRef a b c -> 305 | optLexemes 306 | [ if a then Just "LATERAL" else Nothing, 307 | Just (selectWithParens b), 308 | fmap aliasClause c 309 | ] 310 | JoinTableRef a b -> case b of 311 | Just c -> inParens (joinedTable a) <> " " <> aliasClause c 312 | Nothing -> joinedTable a 313 | 314 | relationExpr = \case 315 | SimpleRelationExpr a b -> qualifiedName a <> bool "" " *" b 316 | OnlyRelationExpr a b -> "ONLY " <> bool qualifiedName (inParens . qualifiedName) b a 317 | 318 | relationExprOptAlias (RelationExprOptAlias a b) = relationExpr a <> suffixMaybe optAlias b 319 | 320 | optAlias (a, b) = bool "" "AS " a <> colId b 321 | 322 | tablesampleClause (TablesampleClause a b c) = 323 | "TABLESAMPLE " <> funcName a <> " (" <> exprList b <> ")" <> suffixMaybe repeatableClause c 324 | 325 | repeatableClause a = "REPEATABLE (" <> aExpr a <> ")" 326 | 327 | funcTable = \case 328 | FuncExprFuncTable a b -> funcExprWindownless a <> bool "" " WITH ORDINALITY" b 329 | RowsFromFuncTable a b -> "ROWS FROM (" <> rowsfromList a <> ")" <> bool "" " WITH ORDINALITY" b 330 | 331 | rowsfromItem (RowsfromItem a b) = funcExprWindownless a <> suffixMaybe colDefList b 332 | 333 | rowsfromList = commaNonEmpty rowsfromItem 334 | 335 | colDefList a = "AS (" <> tableFuncElementList a <> ")" 336 | 337 | tableFuncElementList = commaNonEmpty tableFuncElement 338 | 339 | tableFuncElement (TableFuncElement a b c) = colId a <> " " <> typename b <> suffixMaybe collateClause c 340 | 341 | collateClause a = "COLLATE " <> anyName a 342 | 343 | aliasClause (AliasClause a b c) = 344 | optLexemes 345 | [ if a then Just "AS" else Nothing, 346 | Just (ident b), 347 | fmap (inParens . commaNonEmpty ident) c 348 | ] 349 | 350 | funcAliasClause = \case 351 | AliasFuncAliasClause a -> aliasClause a 352 | AsFuncAliasClause a -> "AS (" <> tableFuncElementList a <> ")" 353 | AsColIdFuncAliasClause a b -> "AS " <> colId a <> " (" <> tableFuncElementList b <> ")" 354 | ColIdFuncAliasClause a b -> colId a <> " (" <> tableFuncElementList b <> ")" 355 | 356 | joinedTable = \case 357 | InParensJoinedTable a -> inParens (joinedTable a) 358 | MethJoinedTable a b c -> case a of 359 | CrossJoinMeth -> tableRef b <> " CROSS JOIN " <> tableRef c 360 | QualJoinMeth d e -> tableRef b <> suffixMaybe joinType d <> " JOIN " <> tableRef c <> " " <> joinQual e 361 | NaturalJoinMeth d -> tableRef b <> " NATURAL" <> suffixMaybe joinType d <> " JOIN " <> tableRef c 362 | 363 | joinType = \case 364 | FullJoinType a -> "FULL" <> if a then " OUTER" else "" 365 | LeftJoinType a -> "LEFT" <> if a then " OUTER" else "" 366 | RightJoinType a -> "RIGHT" <> if a then " OUTER" else "" 367 | InnerJoinType -> "INNER" 368 | 369 | joinQual = \case 370 | UsingJoinQual a -> "USING (" <> commaNonEmpty ident a <> ")" 371 | OnJoinQual a -> "ON " <> aExpr a 372 | 373 | -- * Where 374 | 375 | whereClause a = "WHERE " <> aExpr a 376 | 377 | whereOrCurrentClause = \case 378 | ExprWhereOrCurrentClause a -> "WHERE " <> aExpr a 379 | CursorWhereOrCurrentClause a -> "WHERE CURRENT OF " <> cursorName a 380 | 381 | -- * Group By 382 | 383 | groupClause a = "GROUP BY " <> commaNonEmpty groupByItem a 384 | 385 | groupByItem = \case 386 | ExprGroupByItem a -> aExpr a 387 | EmptyGroupingSetGroupByItem -> "()" 388 | RollupGroupByItem a -> "ROLLUP (" <> commaNonEmpty aExpr a <> ")" 389 | CubeGroupByItem a -> "CUBE (" <> commaNonEmpty aExpr a <> ")" 390 | GroupingSetsGroupByItem a -> "GROUPING SETS (" <> commaNonEmpty groupByItem a <> ")" 391 | 392 | -- * Having 393 | 394 | havingClause a = "HAVING " <> aExpr a 395 | 396 | -- * Window 397 | 398 | windowClause a = "WINDOW " <> commaNonEmpty windowDefinition a 399 | 400 | windowDefinition (WindowDefinition a b) = ident a <> " AS " <> windowSpecification b 401 | 402 | windowSpecification (WindowSpecification a b c d) = 403 | inParens 404 | $ optLexemes 405 | [ fmap ident a, 406 | fmap partitionClause b, 407 | fmap sortClause c, 408 | fmap frameClause d 409 | ] 410 | 411 | partitionClause a = "PARTITION BY " <> commaNonEmpty aExpr a 412 | 413 | frameClause (FrameClause a b c) = 414 | optLexemes 415 | [ Just (frameClauseMode a), 416 | Just (frameExtent b), 417 | fmap windowExclusionCause c 418 | ] 419 | 420 | frameClauseMode = \case 421 | RangeFrameClauseMode -> "RANGE" 422 | RowsFrameClauseMode -> "ROWS" 423 | GroupsFrameClauseMode -> "GROUPS" 424 | 425 | frameExtent = \case 426 | SingularFrameExtent a -> frameBound a 427 | BetweenFrameExtent a b -> "BETWEEN " <> frameBound a <> " AND " <> frameBound b 428 | 429 | frameBound = \case 430 | UnboundedPrecedingFrameBound -> "UNBOUNDED PRECEDING" 431 | UnboundedFollowingFrameBound -> "UNBOUNDED FOLLOWING" 432 | CurrentRowFrameBound -> "CURRENT ROW" 433 | PrecedingFrameBound a -> aExpr a <> " PRECEDING" 434 | FollowingFrameBound a -> aExpr a <> " FOLLOWING" 435 | 436 | windowExclusionCause = \case 437 | CurrentRowWindowExclusionClause -> "EXCLUDE CURRENT ROW" 438 | GroupWindowExclusionClause -> "EXCLUDE GROUP" 439 | TiesWindowExclusionClause -> "EXCLUDE TIES" 440 | NoOthersWindowExclusionClause -> "EXCLUDE NO OTHERS" 441 | 442 | -- * Order By 443 | 444 | sortClause a = "ORDER BY " <> commaNonEmpty sortBy a 445 | 446 | sortBy = \case 447 | UsingSortBy a b c -> aExpr a <> " USING " <> qualAllOp b <> suffixMaybe nullsOrder c 448 | AscDescSortBy a b c -> aExpr a <> suffixMaybe ascDesc b <> suffixMaybe nullsOrder c 449 | 450 | -- * Values 451 | 452 | valuesClause a = "VALUES " <> commaNonEmpty (inParens . commaNonEmpty aExpr) a 453 | 454 | -- * Exprs 455 | 456 | exprList = commaNonEmpty aExpr 457 | 458 | aExpr = \case 459 | CExprAExpr a -> cExpr a 460 | TypecastAExpr a b -> aExpr a <> " :: " <> typename b 461 | CollateAExpr a b -> aExpr a <> " COLLATE " <> anyName b 462 | AtTimeZoneAExpr a b -> aExpr a <> " AT TIME ZONE " <> aExpr b 463 | PlusAExpr a -> "+ " <> aExpr a 464 | MinusAExpr a -> "- " <> aExpr a 465 | SymbolicBinOpAExpr a b c -> aExpr a <> " " <> symbolicExprBinOp b <> " " <> aExpr c 466 | PrefixQualOpAExpr a b -> qualOp a <> " " <> aExpr b 467 | SuffixQualOpAExpr a b -> aExpr a <> " " <> qualOp b 468 | AndAExpr a b -> aExpr a <> " AND " <> aExpr b 469 | OrAExpr a b -> aExpr a <> " OR " <> aExpr b 470 | NotAExpr a -> "NOT " <> aExpr a 471 | VerbalExprBinOpAExpr a b c d e -> aExpr a <> " " <> verbalExprBinOp b c <> " " <> aExpr d <> foldMap (mappend " ESCAPE " . aExpr) e 472 | ReversableOpAExpr a b c -> aExpr a <> " " <> aExprReversableOp b c 473 | IsnullAExpr a -> aExpr a <> " ISNULL" 474 | NotnullAExpr a -> aExpr a <> " NOTNULL" 475 | OverlapsAExpr a b -> row a <> " OVERLAPS " <> row b 476 | SubqueryAExpr a b c d -> aExpr a <> " " <> subqueryOp b <> " " <> subType c <> " " <> either selectWithParens (inParens . aExpr) d 477 | UniqueAExpr a -> "UNIQUE " <> selectWithParens a 478 | DefaultAExpr -> "DEFAULT" 479 | 480 | bExpr = \case 481 | CExprBExpr a -> cExpr a 482 | TypecastBExpr a b -> bExpr a <> " :: " <> typename b 483 | PlusBExpr a -> "+ " <> bExpr a 484 | MinusBExpr a -> "- " <> bExpr a 485 | SymbolicBinOpBExpr a b c -> bExpr a <> " " <> symbolicExprBinOp b <> " " <> bExpr c 486 | QualOpBExpr a b -> qualOp a <> " " <> bExpr b 487 | IsOpBExpr a b c -> bExpr a <> " " <> bExprIsOp b c 488 | 489 | cExpr = \case 490 | ColumnrefCExpr a -> columnref a 491 | AexprConstCExpr a -> aexprConst a 492 | ParamCExpr a b -> "$" <> intDec a <> foldMap indirection b 493 | InParensCExpr a b -> inParens (aExpr a) <> foldMap indirection b 494 | CaseCExpr a -> caseExpr a 495 | FuncCExpr a -> funcExpr a 496 | SelectWithParensCExpr a b -> selectWithParens a <> foldMap indirection b 497 | ExistsCExpr a -> "EXISTS " <> selectWithParens a 498 | ArrayCExpr a -> "ARRAY " <> either selectWithParens arrayExpr a 499 | ExplicitRowCExpr a -> explicitRow a 500 | ImplicitRowCExpr a -> implicitRow a 501 | GroupingCExpr a -> "GROUPING " <> inParens (exprList a) 502 | 503 | -- * Ops 504 | 505 | aExprReversableOp a = \case 506 | NullAExprReversableOp -> bool "IS " "IS NOT " a <> "NULL" 507 | TrueAExprReversableOp -> bool "IS " "IS NOT " a <> "TRUE" 508 | FalseAExprReversableOp -> bool "IS " "IS NOT " a <> "FALSE" 509 | UnknownAExprReversableOp -> bool "IS " "IS NOT " a <> "UNKNOWN" 510 | DistinctFromAExprReversableOp b -> bool "IS " "IS NOT " a <> "DISTINCT FROM " <> aExpr b 511 | OfAExprReversableOp b -> bool "IS " "IS NOT " a <> "OF " <> inParens (typeList b) 512 | BetweenAExprReversableOp b c d -> bool "" "NOT " a <> bool "BETWEEN " "BETWEEN ASYMMETRIC " b <> bExpr c <> " AND " <> aExpr d 513 | BetweenSymmetricAExprReversableOp b c -> bool "" "NOT " a <> "BETWEEN SYMMETRIC " <> bExpr b <> " AND " <> aExpr c 514 | InAExprReversableOp b -> bool "" "NOT " a <> "IN " <> inExpr b 515 | DocumentAExprReversableOp -> bool "IS " "IS NOT " a <> "DOCUMENT" 516 | 517 | verbalExprBinOp a = 518 | mappend (bool "" "NOT " a) . \case 519 | LikeVerbalExprBinOp -> "LIKE" 520 | IlikeVerbalExprBinOp -> "ILIKE" 521 | SimilarToVerbalExprBinOp -> "SIMILAR TO" 522 | 523 | subqueryOp = \case 524 | AllSubqueryOp a -> allOp a 525 | AnySubqueryOp a -> "OPERATOR " <> inParens (anyOperator a) 526 | LikeSubqueryOp a -> bool "" "NOT " a <> "LIKE" 527 | IlikeSubqueryOp a -> bool "" "NOT " a <> "ILIKE" 528 | 529 | bExprIsOp a = 530 | mappend (bool "IS " "IS NOT " a) . \case 531 | DistinctFromBExprIsOp b -> "DISTINCT FROM " <> bExpr b 532 | OfBExprIsOp a -> "OF " <> inParens (typeList a) 533 | DocumentBExprIsOp -> "DOCUMENT" 534 | 535 | symbolicExprBinOp = \case 536 | MathSymbolicExprBinOp a -> mathOp a 537 | QualSymbolicExprBinOp a -> qualOp a 538 | 539 | qualOp = \case 540 | OpQualOp a -> op a 541 | OperatorQualOp a -> "OPERATOR (" <> anyOperator a <> ")" 542 | 543 | qualAllOp = \case 544 | AllQualAllOp a -> allOp a 545 | AnyQualAllOp a -> "OPERATOR (" <> anyOperator a <> ")" 546 | 547 | op = text 548 | 549 | anyOperator = \case 550 | AllOpAnyOperator a -> allOp a 551 | QualifiedAnyOperator a b -> colId a <> "." <> anyOperator b 552 | 553 | allOp = \case 554 | OpAllOp a -> op a 555 | MathAllOp a -> mathOp a 556 | 557 | mathOp = \case 558 | PlusMathOp -> char7 '+' 559 | MinusMathOp -> char7 '-' 560 | AsteriskMathOp -> char7 '*' 561 | SlashMathOp -> char7 '/' 562 | PercentMathOp -> char7 '%' 563 | ArrowUpMathOp -> char7 '^' 564 | ArrowLeftMathOp -> char7 '<' 565 | ArrowRightMathOp -> char7 '>' 566 | EqualsMathOp -> char7 '=' 567 | LessEqualsMathOp -> "<=" 568 | GreaterEqualsMathOp -> ">=" 569 | ArrowLeftArrowRightMathOp -> "<>" 570 | ExclamationEqualsMathOp -> "!=" 571 | 572 | inExpr = \case 573 | SelectInExpr a -> selectWithParens a 574 | ExprListInExpr a -> inParens (exprList a) 575 | 576 | caseExpr (CaseExpr a b c) = 577 | optLexemes 578 | [ Just "CASE", 579 | fmap aExpr a, 580 | Just (spaceNonEmpty whenClause b), 581 | fmap caseDefault c, 582 | Just "END" 583 | ] 584 | 585 | whenClause (WhenClause a b) = "WHEN " <> aExpr a <> " THEN " <> aExpr b 586 | 587 | caseDefault a = "ELSE " <> aExpr a 588 | 589 | arrayExpr = 590 | inBrackets . \case 591 | ExprListArrayExpr a -> exprList a 592 | ArrayExprListArrayExpr a -> arrayExprList a 593 | EmptyArrayExpr -> mempty 594 | 595 | arrayExprList = commaNonEmpty arrayExpr 596 | 597 | row = \case 598 | ExplicitRowRow a -> explicitRow a 599 | ImplicitRowRow a -> implicitRow a 600 | 601 | explicitRow a = "ROW " <> inParens (foldMap exprList a) 602 | 603 | implicitRow (ImplicitRow a b) = inParens (exprList a <> ", " <> aExpr b) 604 | 605 | funcApplication (FuncApplication a b) = 606 | funcName a <> "(" <> foldMap funcApplicationParams b <> ")" 607 | 608 | funcApplicationParams = \case 609 | NormalFuncApplicationParams a b c -> 610 | optLexemes 611 | [ fmap allOrDistinct a, 612 | Just (commaNonEmpty funcArgExpr b), 613 | fmap sortClause c 614 | ] 615 | VariadicFuncApplicationParams a b c -> 616 | optLexemes 617 | [ fmap (flip mappend "," . commaNonEmpty funcArgExpr) a, 618 | Just "VARIADIC", 619 | Just (funcArgExpr b), 620 | fmap sortClause c 621 | ] 622 | StarFuncApplicationParams -> "*" 623 | 624 | allOrDistinct = \case 625 | False -> "ALL" 626 | True -> "DISTINCT" 627 | 628 | funcArgExpr = \case 629 | ExprFuncArgExpr a -> aExpr a 630 | ColonEqualsFuncArgExpr a b -> ident a <> " := " <> aExpr b 631 | EqualsGreaterFuncArgExpr a b -> ident a <> " => " <> aExpr b 632 | 633 | -- ** Func Expr 634 | 635 | funcExpr = \case 636 | ApplicationFuncExpr a b c d -> 637 | optLexemes 638 | [ Just (funcApplication a), 639 | fmap withinGroupClause b, 640 | fmap filterClause c, 641 | fmap overClause d 642 | ] 643 | SubexprFuncExpr a -> funcExprCommonSubexpr a 644 | 645 | funcExprWindownless = \case 646 | ApplicationFuncExprWindowless a -> funcApplication a 647 | CommonSubexprFuncExprWindowless a -> funcExprCommonSubexpr a 648 | 649 | withinGroupClause a = "WITHIN GROUP (" <> sortClause a <> ")" 650 | 651 | filterClause a = "FILTER (WHERE " <> aExpr a <> ")" 652 | 653 | overClause = \case 654 | WindowOverClause a -> "OVER " <> windowSpecification a 655 | ColIdOverClause a -> "OVER " <> colId a 656 | 657 | funcExprCommonSubexpr = \case 658 | CollationForFuncExprCommonSubexpr a -> "COLLATION FOR (" <> aExpr a <> ")" 659 | CurrentDateFuncExprCommonSubexpr -> "CURRENT_DATE" 660 | CurrentTimeFuncExprCommonSubexpr a -> "CURRENT_TIME" <> suffixMaybe (inParens . iconst) a 661 | CurrentTimestampFuncExprCommonSubexpr a -> "CURRENT_TIMESTAMP" <> suffixMaybe (inParens . iconst) a 662 | LocalTimeFuncExprCommonSubexpr a -> "LOCALTIME" <> suffixMaybe (inParens . iconst) a 663 | LocalTimestampFuncExprCommonSubexpr a -> "LOCALTIMESTAMP" <> suffixMaybe (inParens . iconst) a 664 | CurrentRoleFuncExprCommonSubexpr -> "CURRENT_ROLE" 665 | CurrentUserFuncExprCommonSubexpr -> "CURRENT_USER" 666 | SessionUserFuncExprCommonSubexpr -> "SESSION_USER" 667 | UserFuncExprCommonSubexpr -> "USER" 668 | CurrentCatalogFuncExprCommonSubexpr -> "CURRENT_CATALOG" 669 | CurrentSchemaFuncExprCommonSubexpr -> "CURRENT_SCHEMA" 670 | CastFuncExprCommonSubexpr a b -> "CAST (" <> aExpr a <> " AS " <> typename b <> ")" 671 | ExtractFuncExprCommonSubexpr a -> "EXTRACT (" <> foldMap extractList a <> ")" 672 | OverlayFuncExprCommonSubexpr a -> "OVERLAY (" <> overlayList a <> ")" 673 | PositionFuncExprCommonSubexpr a -> "POSITION (" <> foldMap positionList a <> ")" 674 | SubstringFuncExprCommonSubexpr a -> "SUBSTRING (" <> foldMap substrList a <> ")" 675 | TreatFuncExprCommonSubexpr a b -> "TREAT (" <> aExpr a <> " AS " <> typename b <> ")" 676 | TrimFuncExprCommonSubexpr a b -> "TRIM (" <> prefixMaybe trimModifier a <> trimList b <> ")" 677 | NullIfFuncExprCommonSubexpr a b -> "NULLIF (" <> aExpr a <> ", " <> aExpr b <> ")" 678 | CoalesceFuncExprCommonSubexpr a -> "COALESCE (" <> exprList a <> ")" 679 | GreatestFuncExprCommonSubexpr a -> "GREATEST (" <> exprList a <> ")" 680 | LeastFuncExprCommonSubexpr a -> "LEAST (" <> exprList a <> ")" 681 | 682 | extractList (ExtractList a b) = extractArg a <> " FROM " <> aExpr b 683 | 684 | extractArg = \case 685 | IdentExtractArg a -> ident a 686 | YearExtractArg -> "YEAR" 687 | MonthExtractArg -> "MONTH" 688 | DayExtractArg -> "DAY" 689 | HourExtractArg -> "HOUR" 690 | MinuteExtractArg -> "MINUTE" 691 | SecondExtractArg -> "SECOND" 692 | SconstExtractArg a -> sconst a 693 | 694 | overlayList (OverlayList a b c d) = aExpr a <> " " <> overlayPlacing b <> " " <> substrFrom c <> suffixMaybe substrFor d 695 | 696 | overlayPlacing a = "PLACING " <> aExpr a 697 | 698 | positionList (PositionList a b) = bExpr a <> " IN " <> bExpr b 699 | 700 | substrList = \case 701 | ExprSubstrList a b -> aExpr a <> " " <> substrListFromFor b 702 | ExprListSubstrList a -> exprList a 703 | 704 | substrListFromFor = \case 705 | FromForSubstrListFromFor a b -> substrFrom a <> " " <> substrFor b 706 | ForFromSubstrListFromFor a b -> substrFor a <> " " <> substrFrom b 707 | FromSubstrListFromFor a -> substrFrom a 708 | ForSubstrListFromFor a -> substrFor a 709 | 710 | substrFrom a = "FROM " <> aExpr a 711 | 712 | substrFor a = "FOR " <> aExpr a 713 | 714 | trimModifier = \case 715 | BothTrimModifier -> "BOTH" 716 | LeadingTrimModifier -> "LEADING" 717 | TrailingTrimModifier -> "TRAILING" 718 | 719 | trimList = \case 720 | ExprFromExprListTrimList a b -> aExpr a <> " FROM " <> exprList b 721 | FromExprListTrimList a -> "FROM " <> exprList a 722 | ExprListTrimList a -> exprList a 723 | 724 | -- * AexprConsts 725 | 726 | aexprConst = \case 727 | IAexprConst a -> iconst a 728 | FAexprConst a -> fconst a 729 | SAexprConst a -> sconst a 730 | BAexprConst a -> "B'" <> text a <> "'" 731 | XAexprConst a -> "X'" <> text a <> "'" 732 | FuncAexprConst a b c -> funcName a <> foldMap (inParens . funcAexprConstArgList) b <> " " <> sconst c 733 | ConstTypenameAexprConst a b -> constTypename a <> " " <> sconst b 734 | StringIntervalAexprConst a b -> "INTERVAL " <> sconst a <> suffixMaybe interval b 735 | IntIntervalAexprConst a b -> "INTERVAL " <> inParens (int64Dec a) <> " " <> sconst b 736 | BoolAexprConst a -> if a then "TRUE" else "FALSE" 737 | NullAexprConst -> "NULL" 738 | 739 | iconst = int64Dec 740 | 741 | fconst = doubleDec 742 | 743 | sconst a = "'" <> text (Text.replace "'" "''" a) <> "'" 744 | 745 | funcAexprConstArgList (FuncConstArgs a b) = commaNonEmpty funcArgExpr a <> suffixMaybe sortClause b 746 | 747 | constTypename = \case 748 | NumericConstTypename a -> numeric a 749 | ConstBitConstTypename a -> constBit a 750 | ConstCharacterConstTypename a -> constCharacter a 751 | ConstDatetimeConstTypename a -> constDatetime a 752 | 753 | numeric = \case 754 | IntNumeric -> "INT" 755 | IntegerNumeric -> "INTEGER" 756 | SmallintNumeric -> "SMALLINT" 757 | BigintNumeric -> "BIGINT" 758 | RealNumeric -> "REAL" 759 | FloatNumeric a -> "FLOAT" <> suffixMaybe (inParens . int64Dec) a 760 | DoublePrecisionNumeric -> "DOUBLE PRECISION" 761 | DecimalNumeric a -> "DECIMAL" <> suffixMaybe (inParens . commaNonEmpty aExpr) a 762 | DecNumeric a -> "DEC" <> suffixMaybe (inParens . commaNonEmpty aExpr) a 763 | NumericNumeric a -> "NUMERIC" <> suffixMaybe (inParens . commaNonEmpty aExpr) a 764 | BooleanNumeric -> "BOOLEAN" 765 | 766 | bit (Bit a b) = 767 | optLexemes 768 | [ Just "BIT", 769 | bool Nothing (Just "VARYING") a, 770 | fmap (inParens . commaNonEmpty aExpr) b 771 | ] 772 | 773 | constBit = bit 774 | 775 | constCharacter (ConstCharacter a b) = character a <> suffixMaybe (inParens . int64Dec) b 776 | 777 | character = \case 778 | CharacterCharacter a -> "CHARACTER" <> bool "" " VARYING" a 779 | CharCharacter a -> "CHAR" <> bool "" " VARYING" a 780 | VarcharCharacter -> "VARCHAR" 781 | NationalCharacterCharacter a -> "NATIONAL CHARACTER" <> bool "" " VARYING" a 782 | NationalCharCharacter a -> "NATIONAL CHAR" <> bool "" " VARYING" a 783 | NcharCharacter a -> "NCHAR" <> bool "" " VARYING" a 784 | 785 | constDatetime = \case 786 | TimestampConstDatetime a b -> 787 | optLexemes 788 | [ Just "TIMESTAMP", 789 | fmap (inParens . int64Dec) a, 790 | fmap timezone b 791 | ] 792 | TimeConstDatetime a b -> 793 | optLexemes 794 | [ Just "TIME", 795 | fmap (inParens . int64Dec) a, 796 | fmap timezone b 797 | ] 798 | 799 | timezone = \case 800 | False -> "WITH TIME ZONE" 801 | True -> "WITHOUT TIME ZONE" 802 | 803 | interval = \case 804 | YearInterval -> "YEAR" 805 | MonthInterval -> "MONTH" 806 | DayInterval -> "DAY" 807 | HourInterval -> "HOUR" 808 | MinuteInterval -> "MINUTE" 809 | SecondInterval a -> intervalSecond a 810 | YearToMonthInterval -> "YEAR TO MONTH" 811 | DayToHourInterval -> "DAY TO HOUR" 812 | DayToMinuteInterval -> "DAY TO MINUTE" 813 | DayToSecondInterval a -> "DAY TO " <> intervalSecond a 814 | HourToMinuteInterval -> "HOUR TO MINUTE" 815 | HourToSecondInterval a -> "HOUR TO " <> intervalSecond a 816 | MinuteToSecondInterval a -> "MINUTE TO " <> intervalSecond a 817 | 818 | intervalSecond = \case 819 | Nothing -> "SECOND" 820 | Just a -> "SECOND " <> inParens (int64Dec a) 821 | 822 | -- * Names and refs 823 | 824 | columnref (Columnref a b) = colId a <> foldMap indirection b 825 | 826 | ident = \case 827 | QuotedIdent a -> char7 '"' <> text (Text.replace "\"" "\"\"" a) <> char7 '"' 828 | UnquotedIdent a -> text a 829 | 830 | qualifiedName = \case 831 | SimpleQualifiedName a -> ident a 832 | IndirectedQualifiedName a b -> ident a <> indirection b 833 | 834 | indirection = foldMap indirectionEl 835 | 836 | indirectionEl = \case 837 | AttrNameIndirectionEl a -> "." <> ident a 838 | AllIndirectionEl -> ".*" 839 | ExprIndirectionEl a -> "[" <> aExpr a <> "]" 840 | SliceIndirectionEl a b -> "[" <> foldMap aExpr a <> ":" <> foldMap aExpr b <> "]" 841 | 842 | colId = ident 843 | 844 | name = colId 845 | 846 | cursorName = name 847 | 848 | colLabel = ident 849 | 850 | attrName = colLabel 851 | 852 | typeFunctionName = ident 853 | 854 | funcName = \case 855 | TypeFuncName a -> typeFunctionName a 856 | IndirectedFuncName a b -> colId a <> indirection b 857 | 858 | anyName (AnyName a b) = colId a <> foldMap attrs b 859 | 860 | -- * Types 861 | 862 | typename (Typename a b _ d) = 863 | bool "" "SETOF " a <> simpleTypename b <> foldMap typenameArrayDimensionsWithQuestionMark d 864 | 865 | typenameArrayDimensionsWithQuestionMark (a, _) = 866 | typenameArrayDimensions a 867 | 868 | typenameArrayDimensions = \case 869 | BoundsTypenameArrayDimensions a -> arrayBounds a 870 | ExplicitTypenameArrayDimensions a -> " ARRAY" <> foldMap (inBrackets . iconst) a 871 | 872 | arrayBounds = spaceNonEmpty (inBrackets . foldMap iconst) 873 | 874 | simpleTypename = \case 875 | GenericTypeSimpleTypename a -> genericType a 876 | NumericSimpleTypename a -> numeric a 877 | BitSimpleTypename a -> bit a 878 | CharacterSimpleTypename a -> character a 879 | ConstDatetimeSimpleTypename a -> constDatetime a 880 | ConstIntervalSimpleTypename a -> "INTERVAL" <> either (suffixMaybe interval) (mappend " " . inParens . iconst) a 881 | 882 | genericType (GenericType a b c) = typeFunctionName a <> foldMap attrs b <> suffixMaybe typeModifiers c 883 | 884 | attrs = foldMap (mappend "." . attrName) 885 | 886 | typeModifiers = inParens . exprList 887 | 888 | typeList = commaNonEmpty typename 889 | 890 | subType = \case 891 | AnySubType -> "ANY" 892 | SomeSubType -> "SOME" 893 | AllSubType -> "ALL" 894 | 895 | -- * Indexes 896 | 897 | indexParams = commaNonEmpty indexElem 898 | 899 | indexElem (IndexElem a b c d e) = 900 | indexElemDef a 901 | <> suffixMaybe collate b 902 | <> suffixMaybe class_ c 903 | <> suffixMaybe ascDesc d 904 | <> suffixMaybe nullsOrder e 905 | 906 | indexElemDef = \case 907 | IdIndexElemDef a -> colId a 908 | FuncIndexElemDef a -> funcExprWindownless a 909 | ExprIndexElemDef a -> inParens (aExpr a) 910 | 911 | collate = mappend "COLLATE " . anyName 912 | 913 | class_ = anyName 914 | 915 | ascDesc = \case 916 | AscAscDesc -> "ASC" 917 | DescAscDesc -> "DESC" 918 | 919 | nullsOrder = \case 920 | FirstNullsOrder -> "NULLS FIRST" 921 | LastNullsOrder -> "NULLS LAST" 922 | -------------------------------------------------------------------------------- /library/PostgresqlSyntax/Validation.hs: -------------------------------------------------------------------------------- 1 | module PostgresqlSyntax.Validation where 2 | 3 | import qualified Data.Text as Text 4 | import qualified PostgresqlSyntax.KeywordSet as KeywordSet 5 | import qualified PostgresqlSyntax.Predicate as Predicate 6 | import PostgresqlSyntax.Prelude 7 | 8 | {- 9 | The operator name is a sequence of up to NAMEDATALEN-1 (63 by default) 10 | characters from the following list: 11 | 12 | + - * / < > = ~ ! @ # % ^ & | ` ? 13 | 14 | There are a few restrictions on your choice of name: 15 | -- and /* cannot appear anywhere in an operator name, 16 | since they will be taken as the start of a comment. 17 | 18 | A multicharacter operator name cannot end in + or -, 19 | unless the name also contains at least one of these characters: 20 | 21 | ~ ! @ # % ^ & | ` ? 22 | 23 | For example, @- is an allowed operator name, but *- is not. 24 | This restriction allows PostgreSQL to parse SQL-compliant 25 | commands without requiring spaces between tokens. 26 | The use of => as an operator name is deprecated. 27 | It may be disallowed altogether in a future release. 28 | 29 | The operator != is mapped to <> on input, 30 | so these two names are always equivalent. 31 | -} 32 | op :: Text -> Maybe Text 33 | op a = 34 | if Text.null a 35 | then Just ("Operator is empty") 36 | else 37 | if Text.isInfixOf "--" a 38 | then Just ("Operator contains a prohibited \"--\" sequence: " <> a) 39 | else 40 | if Text.isInfixOf "/*" a 41 | then Just ("Operator contains a prohibited \"/*\" sequence: " <> a) 42 | else 43 | if Predicate.inSet KeywordSet.nonOp a 44 | then Just ("Operator is not generic: " <> a) 45 | else 46 | if Text.find Predicate.prohibitionLiftingOpChar a & isJust 47 | then Nothing 48 | else 49 | if Predicate.prohibitedOpChar (Text.last a) 50 | then Just ("Operator ends with a prohibited char: " <> a) 51 | else Nothing 52 | -------------------------------------------------------------------------------- /postgresql-syntax.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: postgresql-syntax 3 | version: 0.4.1.3 4 | category: Database, PostgreSQL, Parsing 5 | synopsis: PostgreSQL AST parsing and rendering 6 | description: 7 | Postgres syntax tree and related utils extracted from the \"hasql-th\" package. 8 | The API is in a rather raw \"guts out\" state with most documentation lacking, 9 | but the codebase is well tested. 10 | 11 | homepage: https://github.com/nikita-volkov/postgresql-syntax 12 | bug-reports: https://github.com/nikita-volkov/postgresql-syntax/issues 13 | author: Nikita Volkov 14 | maintainer: Nikita Volkov 15 | copyright: (c) 2020, Nikita Volkov 16 | license: MIT 17 | license-file: LICENSE 18 | 19 | source-repository head 20 | type: git 21 | location: https://github.com/nikita-volkov/postgresql-syntax 22 | 23 | common base-settings 24 | default-language: Haskell2010 25 | default-extensions: 26 | ApplicativeDo 27 | Arrows 28 | BangPatterns 29 | ConstraintKinds 30 | DataKinds 31 | DefaultSignatures 32 | DeriveDataTypeable 33 | DeriveFoldable 34 | DeriveFunctor 35 | DeriveGeneric 36 | DeriveTraversable 37 | DuplicateRecordFields 38 | EmptyDataDecls 39 | FlexibleContexts 40 | FlexibleInstances 41 | FunctionalDependencies 42 | GADTs 43 | GeneralizedNewtypeDeriving 44 | LambdaCase 45 | LiberalTypeSynonyms 46 | MagicHash 47 | MultiParamTypeClasses 48 | MultiWayIf 49 | NoImplicitPrelude 50 | NoMonomorphismRestriction 51 | OverloadedStrings 52 | ParallelListComp 53 | PatternGuards 54 | QuasiQuotes 55 | RankNTypes 56 | RecordWildCards 57 | ScopedTypeVariables 58 | StandaloneDeriving 59 | TemplateHaskell 60 | TupleSections 61 | TypeApplications 62 | TypeFamilies 63 | TypeOperators 64 | UnboxedTuples 65 | 66 | library 67 | import: base-settings 68 | hs-source-dirs: library 69 | exposed-modules: 70 | PostgresqlSyntax.Ast 71 | PostgresqlSyntax.KeywordSet 72 | PostgresqlSyntax.Parsing 73 | PostgresqlSyntax.Rendering 74 | PostgresqlSyntax.Validation 75 | 76 | other-modules: 77 | PostgresqlSyntax.CharSet 78 | PostgresqlSyntax.Extras.HeadedMegaparsec 79 | PostgresqlSyntax.Extras.NonEmpty 80 | PostgresqlSyntax.Extras.TextBuilder 81 | PostgresqlSyntax.Predicate 82 | PostgresqlSyntax.Prelude 83 | 84 | build-depends: 85 | base >=4.12 && <5, 86 | bytestring >=0.10 && <0.13, 87 | case-insensitive >=1.2.1 && <2, 88 | hashable >=1.3.5 && <2, 89 | headed-megaparsec >=0.2.0.1 && <0.3, 90 | megaparsec >=9.2 && <10, 91 | parser-combinators >=1.3 && <1.4, 92 | text >=1 && <3, 93 | text-builder >=1 && <1.1, 94 | unordered-containers >=0.2.16 && <0.3, 95 | 96 | test-suite tasty-test 97 | import: base-settings 98 | type: exitcode-stdio-1.0 99 | hs-source-dirs: tasty-test 100 | main-is: Main.hs 101 | ghc-options: -threaded 102 | build-depends: 103 | postgresql-syntax, 104 | rerebase <2, 105 | tasty >=1.2.3 && <2, 106 | tasty-hunit >=0.10 && <0.11, 107 | 108 | test-suite hedgehog-test 109 | import: base-settings 110 | type: exitcode-stdio-1.0 111 | hs-source-dirs: hedgehog-test 112 | main-is: Main.hs 113 | ghc-options: -threaded 114 | other-modules: Main.Gen 115 | build-depends: 116 | hedgehog >=1.0.1 && <2, 117 | postgresql-syntax, 118 | rerebase >=1.6.1 && <2, 119 | -------------------------------------------------------------------------------- /references/kwlist.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * kwlist.h 4 | * 5 | * The keyword lists are kept in their own source files for use by 6 | * automatic tools. The exact representation of a keyword is determined 7 | * by the PG_KEYWORD macro, which is not defined in this file; it can 8 | * be defined by the caller for special purposes. 9 | * 10 | * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group 11 | * Portions Copyright (c) 1994, Regents of the University of California 12 | * 13 | * IDENTIFICATION 14 | * src/include/parser/kwlist.h 15 | * 16 | *------------------------------------------------------------------------- 17 | */ 18 | 19 | /* there is deliberately not an #ifndef KWLIST_H here */ 20 | 21 | /* 22 | * List of keyword (name, token-value, category) entries. 23 | * 24 | * Note: gen_keywordlist.pl requires the entries to appear in ASCII order. 25 | */ 26 | 27 | /* name, value, category */ 28 | PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD) 29 | PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD) 30 | PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD) 31 | PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD) 32 | PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD) 33 | PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD) 34 | PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD) 35 | PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD) 36 | PG_KEYWORD("all", ALL, RESERVED_KEYWORD) 37 | PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD) 38 | PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD) 39 | PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD) 40 | PG_KEYWORD("analyse", ANALYSE, RESERVED_KEYWORD) /* British spelling */ 41 | PG_KEYWORD("analyze", ANALYZE, RESERVED_KEYWORD) 42 | PG_KEYWORD("and", AND, RESERVED_KEYWORD) 43 | PG_KEYWORD("any", ANY, RESERVED_KEYWORD) 44 | PG_KEYWORD("array", ARRAY, RESERVED_KEYWORD) 45 | PG_KEYWORD("as", AS, RESERVED_KEYWORD) 46 | PG_KEYWORD("asc", ASC, RESERVED_KEYWORD) 47 | PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD) 48 | PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) 49 | PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) 50 | PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) 51 | PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD) 52 | PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD) 53 | PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) 54 | PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) 55 | PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD) 56 | PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD) 57 | PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD) 58 | PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD) 59 | PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD) 60 | PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD) 61 | PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD) 62 | PG_KEYWORD("both", BOTH, RESERVED_KEYWORD) 63 | PG_KEYWORD("by", BY, UNRESERVED_KEYWORD) 64 | PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD) 65 | PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD) 66 | PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD) 67 | PG_KEYWORD("cascade", CASCADE, UNRESERVED_KEYWORD) 68 | PG_KEYWORD("cascaded", CASCADED, UNRESERVED_KEYWORD) 69 | PG_KEYWORD("case", CASE, RESERVED_KEYWORD) 70 | PG_KEYWORD("cast", CAST, RESERVED_KEYWORD) 71 | PG_KEYWORD("catalog", CATALOG_P, UNRESERVED_KEYWORD) 72 | PG_KEYWORD("chain", CHAIN, UNRESERVED_KEYWORD) 73 | PG_KEYWORD("char", CHAR_P, COL_NAME_KEYWORD) 74 | PG_KEYWORD("character", CHARACTER, COL_NAME_KEYWORD) 75 | PG_KEYWORD("characteristics", CHARACTERISTICS, UNRESERVED_KEYWORD) 76 | PG_KEYWORD("check", CHECK, RESERVED_KEYWORD) 77 | PG_KEYWORD("checkpoint", CHECKPOINT, UNRESERVED_KEYWORD) 78 | PG_KEYWORD("class", CLASS, UNRESERVED_KEYWORD) 79 | PG_KEYWORD("close", CLOSE, UNRESERVED_KEYWORD) 80 | PG_KEYWORD("cluster", CLUSTER, UNRESERVED_KEYWORD) 81 | PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD) 82 | PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD) 83 | PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD) 84 | PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD) 85 | PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD) 86 | PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD) 87 | PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) 88 | PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) 89 | PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD) 90 | PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD) 91 | PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD) 92 | PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD) 93 | PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD) 94 | PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD) 95 | PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD) 96 | PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD) 97 | PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD) 98 | PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD) 99 | PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD) 100 | PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD) 101 | PG_KEYWORD("create", CREATE, RESERVED_KEYWORD) 102 | PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD) 103 | PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD) 104 | PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD) 105 | PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD) 106 | PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD) 107 | PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD) 108 | PG_KEYWORD("current_role", CURRENT_ROLE, RESERVED_KEYWORD) 109 | PG_KEYWORD("current_schema", CURRENT_SCHEMA, TYPE_FUNC_NAME_KEYWORD) 110 | PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD) 111 | PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD) 112 | PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD) 113 | PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD) 114 | PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD) 115 | PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD) 116 | PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD) 117 | PG_KEYWORD("day", DAY_P, UNRESERVED_KEYWORD) 118 | PG_KEYWORD("deallocate", DEALLOCATE, UNRESERVED_KEYWORD) 119 | PG_KEYWORD("dec", DEC, COL_NAME_KEYWORD) 120 | PG_KEYWORD("decimal", DECIMAL_P, COL_NAME_KEYWORD) 121 | PG_KEYWORD("declare", DECLARE, UNRESERVED_KEYWORD) 122 | PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD) 123 | PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD) 124 | PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD) 125 | PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD) 126 | PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD) 127 | PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD) 128 | PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) 129 | PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) 130 | PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD) 131 | PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) 132 | PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD) 133 | PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) 134 | PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) 135 | PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) 136 | PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD) 137 | PG_KEYWORD("do", DO, RESERVED_KEYWORD) 138 | PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD) 139 | PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD) 140 | PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD) 141 | PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD) 142 | PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD) 143 | PG_KEYWORD("else", ELSE, RESERVED_KEYWORD) 144 | PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD) 145 | PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD) 146 | PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD) 147 | PG_KEYWORD("end", END_P, RESERVED_KEYWORD) 148 | PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD) 149 | PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD) 150 | PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD) 151 | PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD) 152 | PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD) 153 | PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD) 154 | PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD) 155 | PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD) 156 | PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD) 157 | PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD) 158 | PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD) 159 | PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD) 160 | PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD) 161 | PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD) 162 | PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) 163 | PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD) 164 | PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD) 165 | PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD) 166 | PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD) 167 | PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD) 168 | PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD) 169 | PG_KEYWORD("for", FOR, RESERVED_KEYWORD) 170 | PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD) 171 | PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD) 172 | PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD) 173 | PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD) 174 | PG_KEYWORD("from", FROM, RESERVED_KEYWORD) 175 | PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD) 176 | PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD) 177 | PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD) 178 | PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD) 179 | PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD) 180 | PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD) 181 | PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD) 182 | PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) 183 | PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) 184 | PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) 185 | PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD) 186 | PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) 187 | PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) 188 | PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) 189 | PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) 190 | PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD) 191 | PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD) 192 | PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD) 193 | PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD) 194 | PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD) 195 | PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD) 196 | PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD) 197 | PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD) 198 | PG_KEYWORD("in", IN_P, RESERVED_KEYWORD) 199 | PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD) 200 | PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD) 201 | PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD) 202 | PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD) 203 | PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD) 204 | PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD) 205 | PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD) 206 | PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD) 207 | PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD) 208 | PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD) 209 | PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD) 210 | PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD) 211 | PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD) 212 | PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD) 213 | PG_KEYWORD("instead", INSTEAD, UNRESERVED_KEYWORD) 214 | PG_KEYWORD("int", INT_P, COL_NAME_KEYWORD) 215 | PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD) 216 | PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD) 217 | PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD) 218 | PG_KEYWORD("into", INTO, RESERVED_KEYWORD) 219 | PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD) 220 | PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) 221 | PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) 222 | PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) 223 | PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) 224 | PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) 225 | PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) 226 | PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) 227 | PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD) 228 | PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD) 229 | PG_KEYWORD("lateral", LATERAL_P, RESERVED_KEYWORD) 230 | PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD) 231 | PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD) 232 | PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD) 233 | PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD) 234 | PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD) 235 | PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD) 236 | PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) 237 | PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD) 238 | PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD) 239 | PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD) 240 | PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD) 241 | PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD) 242 | PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD) 243 | PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD) 244 | PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD) 245 | PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD) 246 | PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD) 247 | PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD) 248 | PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD) 249 | PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD) 250 | PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD) 251 | PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD) 252 | PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD) 253 | PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD) 254 | PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD) 255 | PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD) 256 | PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD) 257 | PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD) 258 | PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD) 259 | PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD) 260 | PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD) 261 | PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD) 262 | PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD) 263 | PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD) 264 | PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD) 265 | PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD) 266 | PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD) 267 | PG_KEYWORD("no", NO, UNRESERVED_KEYWORD) 268 | PG_KEYWORD("none", NONE, COL_NAME_KEYWORD) 269 | PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD) 270 | PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD) 271 | PG_KEYWORD("not", NOT, RESERVED_KEYWORD) 272 | PG_KEYWORD("nothing", NOTHING, UNRESERVED_KEYWORD) 273 | PG_KEYWORD("notify", NOTIFY, UNRESERVED_KEYWORD) 274 | PG_KEYWORD("notnull", NOTNULL, TYPE_FUNC_NAME_KEYWORD) 275 | PG_KEYWORD("nowait", NOWAIT, UNRESERVED_KEYWORD) 276 | PG_KEYWORD("null", NULL_P, RESERVED_KEYWORD) 277 | PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD) 278 | PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD) 279 | PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD) 280 | PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD) 281 | PG_KEYWORD("of", OF, UNRESERVED_KEYWORD) 282 | PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD) 283 | PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD) 284 | PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD) 285 | PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD) 286 | PG_KEYWORD("on", ON, RESERVED_KEYWORD) 287 | PG_KEYWORD("only", ONLY, RESERVED_KEYWORD) 288 | PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD) 289 | PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD) 290 | PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD) 291 | PG_KEYWORD("or", OR, RESERVED_KEYWORD) 292 | PG_KEYWORD("order", ORDER, RESERVED_KEYWORD) 293 | PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD) 294 | PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD) 295 | PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD) 296 | PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD) 297 | PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD) 298 | PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD) 299 | PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD) 300 | PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD) 301 | PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD) 302 | PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD) 303 | PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) 304 | PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) 305 | PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) 306 | PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) 307 | PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) 308 | PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) 309 | PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) 310 | PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD) 311 | PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD) 312 | PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD) 313 | PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD) 314 | PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD) 315 | PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD) 316 | PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD) 317 | PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD) 318 | PG_KEYWORD("primary", PRIMARY, RESERVED_KEYWORD) 319 | PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD) 320 | PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD) 321 | PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD) 322 | PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD) 323 | PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD) 324 | PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD) 325 | PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD) 326 | PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD) 327 | PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD) 328 | PG_KEYWORD("read", READ, UNRESERVED_KEYWORD) 329 | PG_KEYWORD("real", REAL, COL_NAME_KEYWORD) 330 | PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD) 331 | PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD) 332 | PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD) 333 | PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD) 334 | PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD) 335 | PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD) 336 | PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD) 337 | PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD) 338 | PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD) 339 | PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD) 340 | PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD) 341 | PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD) 342 | PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD) 343 | PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD) 344 | PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD) 345 | PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD) 346 | PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD) 347 | PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD) 348 | PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD) 349 | PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD) 350 | PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD) 351 | PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD) 352 | PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD) 353 | PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD) 354 | PG_KEYWORD("routine", ROUTINE, UNRESERVED_KEYWORD) 355 | PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD) 356 | PG_KEYWORD("row", ROW, COL_NAME_KEYWORD) 357 | PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD) 358 | PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD) 359 | PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD) 360 | PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD) 361 | PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD) 362 | PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD) 363 | PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD) 364 | PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD) 365 | PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD) 366 | PG_KEYWORD("select", SELECT, RESERVED_KEYWORD) 367 | PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD) 368 | PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD) 369 | PG_KEYWORD("serializable", SERIALIZABLE, UNRESERVED_KEYWORD) 370 | PG_KEYWORD("server", SERVER, UNRESERVED_KEYWORD) 371 | PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD) 372 | PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD) 373 | PG_KEYWORD("set", SET, UNRESERVED_KEYWORD) 374 | PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD) 375 | PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD) 376 | PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD) 377 | PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD) 378 | PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD) 379 | PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD) 380 | PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD) 381 | PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD) 382 | PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD) 383 | PG_KEYWORD("some", SOME, RESERVED_KEYWORD) 384 | PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD) 385 | PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD) 386 | PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD) 387 | PG_KEYWORD("start", START, UNRESERVED_KEYWORD) 388 | PG_KEYWORD("statement", STATEMENT, UNRESERVED_KEYWORD) 389 | PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD) 390 | PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD) 391 | PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) 392 | PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) 393 | PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD) 394 | PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) 395 | PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) 396 | PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) 397 | PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) 398 | PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD) 399 | PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD) 400 | PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD) 401 | PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD) 402 | PG_KEYWORD("table", TABLE, RESERVED_KEYWORD) 403 | PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD) 404 | PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD) 405 | PG_KEYWORD("tablespace", TABLESPACE, UNRESERVED_KEYWORD) 406 | PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD) 407 | PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD) 408 | PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD) 409 | PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD) 410 | PG_KEYWORD("then", THEN, RESERVED_KEYWORD) 411 | PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD) 412 | PG_KEYWORD("time", TIME, COL_NAME_KEYWORD) 413 | PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) 414 | PG_KEYWORD("to", TO, RESERVED_KEYWORD) 415 | PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD) 416 | PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD) 417 | PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD) 418 | PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD) 419 | PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD) 420 | PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD) 421 | PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD) 422 | PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD) 423 | PG_KEYWORD("trusted", TRUSTED, UNRESERVED_KEYWORD) 424 | PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD) 425 | PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD) 426 | PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD) 427 | PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD) 428 | PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD) 429 | PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD) 430 | PG_KEYWORD("union", UNION, RESERVED_KEYWORD) 431 | PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD) 432 | PG_KEYWORD("unknown", UNKNOWN, UNRESERVED_KEYWORD) 433 | PG_KEYWORD("unlisten", UNLISTEN, UNRESERVED_KEYWORD) 434 | PG_KEYWORD("unlogged", UNLOGGED, UNRESERVED_KEYWORD) 435 | PG_KEYWORD("until", UNTIL, UNRESERVED_KEYWORD) 436 | PG_KEYWORD("update", UPDATE, UNRESERVED_KEYWORD) 437 | PG_KEYWORD("user", USER, RESERVED_KEYWORD) 438 | PG_KEYWORD("using", USING, RESERVED_KEYWORD) 439 | PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD) 440 | PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD) 441 | PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD) 442 | PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD) 443 | PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD) 444 | PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD) 445 | PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD) 446 | PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD) 447 | PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD) 448 | PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD) 449 | PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD) 450 | PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD) 451 | PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD) 452 | PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD) 453 | PG_KEYWORD("when", WHEN, RESERVED_KEYWORD) 454 | PG_KEYWORD("where", WHERE, RESERVED_KEYWORD) 455 | PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD) 456 | PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD) 457 | PG_KEYWORD("with", WITH, RESERVED_KEYWORD) 458 | PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD) 459 | PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD) 460 | PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD) 461 | PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD) 462 | PG_KEYWORD("write", WRITE, UNRESERVED_KEYWORD) 463 | PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD) 464 | PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD) 465 | PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD) 466 | PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD) 467 | PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD) 468 | PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD) 469 | PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD) 470 | PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD) 471 | PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD) 472 | PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD) 473 | PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD) 474 | PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD) 475 | PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD) 476 | PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD) 477 | PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD) -------------------------------------------------------------------------------- /references/operator-token-scanner: -------------------------------------------------------------------------------- 1 | 2 | {typecast} { 3 | SET_YYLLOC(); 4 | return TYPECAST; 5 | } 6 | 7 | {dot_dot} { 8 | SET_YYLLOC(); 9 | return DOT_DOT; 10 | } 11 | 12 | {colon_equals} { 13 | SET_YYLLOC(); 14 | return COLON_EQUALS; 15 | } 16 | 17 | {equals_greater} { 18 | SET_YYLLOC(); 19 | return EQUALS_GREATER; 20 | } 21 | 22 | {less_equals} { 23 | SET_YYLLOC(); 24 | return LESS_EQUALS; 25 | } 26 | 27 | {greater_equals} { 28 | SET_YYLLOC(); 29 | return GREATER_EQUALS; 30 | } 31 | 32 | {less_greater} { 33 | /* We accept both "<>" and "!=" as meaning NOT_EQUALS */ 34 | SET_YYLLOC(); 35 | return NOT_EQUALS; 36 | } 37 | 38 | {not_equals} { 39 | /* We accept both "<>" and "!=" as meaning NOT_EQUALS */ 40 | SET_YYLLOC(); 41 | return NOT_EQUALS; 42 | } 43 | 44 | {self} { 45 | SET_YYLLOC(); 46 | return yytext[0]; 47 | } 48 | 49 | {operator} { 50 | /* 51 | * Check for embedded slash-star or dash-dash; those 52 | * are comment starts, so operator must stop there. 53 | * Note that slash-star or dash-dash at the first 54 | * character will match a prior rule, not this one. 55 | */ 56 | int nchars = yyleng; 57 | char *slashstar = strstr(yytext, "/*"); 58 | char *dashdash = strstr(yytext, "--"); 59 | 60 | if (slashstar && dashdash) 61 | { 62 | /* if both appear, take the first one */ 63 | if (slashstar > dashdash) 64 | slashstar = dashdash; 65 | } 66 | else if (!slashstar) 67 | slashstar = dashdash; 68 | if (slashstar) 69 | nchars = slashstar - yytext; 70 | 71 | /* 72 | * For SQL compatibility, '+' and '-' cannot be the 73 | * last char of a multi-char operator unless the operator 74 | * contains chars that are not in SQL operators. 75 | * The idea is to lex '=-' as two operators, but not 76 | * to forbid operator names like '?-' that could not be 77 | * sequences of SQL operators. 78 | */ 79 | if (nchars > 1 && 80 | (yytext[nchars - 1] == '+' || 81 | yytext[nchars - 1] == '-')) 82 | { 83 | int ic; 84 | 85 | for (ic = nchars - 2; ic >= 0; ic--) 86 | { 87 | char c = yytext[ic]; 88 | if (c == '~' || c == '!' || c == '@' || 89 | c == '#' || c == '^' || c == '&' || 90 | c == '|' || c == '`' || c == '?' || 91 | c == '%') 92 | break; 93 | } 94 | if (ic < 0) 95 | { 96 | /* 97 | * didn't find a qualifying character, so remove 98 | * all trailing [+-] 99 | */ 100 | do { 101 | nchars--; 102 | } while (nchars > 1 && 103 | (yytext[nchars - 1] == '+' || 104 | yytext[nchars - 1] == '-')); 105 | } 106 | } 107 | 108 | SET_YYLLOC(); 109 | 110 | if (nchars < yyleng) 111 | { 112 | /* Strip the unwanted chars from the token */ 113 | yyless(nchars); 114 | /* 115 | * If what we have left is only one char, and it's 116 | * one of the characters matching "self", then 117 | * return it as a character token the same way 118 | * that the "self" rule would have. 119 | */ 120 | if (nchars == 1 && 121 | strchr(",()[].;:+-*/%^<>=", yytext[0])) 122 | return yytext[0]; 123 | /* 124 | * Likewise, if what we have left is two chars, and 125 | * those match the tokens ">=", "<=", "=>", "<>" or 126 | * "!=", then we must return the appropriate token 127 | * rather than the generic Op. 128 | */ 129 | if (nchars == 2) 130 | { 131 | if (yytext[0] == '=' && yytext[1] == '>') 132 | return EQUALS_GREATER; 133 | if (yytext[0] == '>' && yytext[1] == '=') 134 | return GREATER_EQUALS; 135 | if (yytext[0] == '<' && yytext[1] == '=') 136 | return LESS_EQUALS; 137 | if (yytext[0] == '<' && yytext[1] == '>') 138 | return NOT_EQUALS; 139 | if (yytext[0] == '!' && yytext[1] == '=') 140 | return NOT_EQUALS; 141 | } 142 | } 143 | 144 | /* 145 | * Complain if operator is too long. Unlike the case 146 | * for identifiers, we make this an error not a notice- 147 | * and-truncate, because the odds are we are looking at 148 | * a syntactic mistake anyway. 149 | */ 150 | if (nchars >= NAMEDATALEN) 151 | yyerror("operator too long"); 152 | 153 | yylval->str = pstrdup(yytext); 154 | return Op; 155 | } 156 | -------------------------------------------------------------------------------- /references/scan.l: -------------------------------------------------------------------------------- 1 | %top{ 2 | /*------------------------------------------------------------------------- 3 | * 4 | * scan.l 5 | * lexical scanner for PostgreSQL 6 | * 7 | * NOTE NOTE NOTE: 8 | * 9 | * The rules in this file must be kept in sync with src/fe_utils/psqlscan.l 10 | * and src/interfaces/ecpg/preproc/pgc.l! 11 | * 12 | * The rules are designed so that the scanner never has to backtrack, 13 | * in the sense that there is always a rule that can match the input 14 | * consumed so far (the rule action may internally throw back some input 15 | * with yyless(), however). As explained in the flex manual, this makes 16 | * for a useful speed increase --- several percent faster when measuring 17 | * raw parsing (Flex + Bison). The extra complexity is mostly in the rules 18 | * for handling float numbers and continued string literals. If you change 19 | * the lexical rules, verify that you haven't broken the no-backtrack 20 | * property by running flex with the "-b" option and checking that the 21 | * resulting "lex.backup" file says that no backing up is needed. (As of 22 | * Postgres 9.2, this check is made automatically by the Makefile.) 23 | * 24 | * 25 | * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group 26 | * Portions Copyright (c) 1994, Regents of the University of California 27 | * 28 | * IDENTIFICATION 29 | * src/backend/parser/scan.l 30 | * 31 | *------------------------------------------------------------------------- 32 | */ 33 | #include "postgres.h" 34 | 35 | #include 36 | #include 37 | 38 | #include "common/string.h" 39 | #include "parser/gramparse.h" 40 | #include "parser/parser.h" /* only needed for GUC variables */ 41 | #include "parser/scansup.h" 42 | #include "mb/pg_wchar.h" 43 | } 44 | 45 | %{ 46 | 47 | /* LCOV_EXCL_START */ 48 | 49 | /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ 50 | #undef fprintf 51 | #define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) 52 | 53 | static void 54 | fprintf_to_ereport(const char *fmt, const char *msg) 55 | { 56 | ereport(ERROR, (errmsg_internal("%s", msg))); 57 | } 58 | 59 | /* 60 | * GUC variables. This is a DIRECT violation of the warning given at the 61 | * head of gram.y, ie flex/bison code must not depend on any GUC variables; 62 | * as such, changing their values can induce very unintuitive behavior. 63 | * But we shall have to live with it until we can remove these variables. 64 | */ 65 | int backslash_quote = BACKSLASH_QUOTE_SAFE_ENCODING; 66 | bool escape_string_warning = true; 67 | bool standard_conforming_strings = true; 68 | 69 | /* 70 | * Constant data exported from this file. This array maps from the 71 | * zero-based keyword numbers returned by ScanKeywordLookup to the 72 | * Bison token numbers needed by gram.y. This is exported because 73 | * callers need to pass it to scanner_init, if they are using the 74 | * standard keyword list ScanKeywords. 75 | */ 76 | #define PG_KEYWORD(kwname, value, category) value, 77 | 78 | const uint16 ScanKeywordTokens[] = { 79 | #include "parser/kwlist.h" 80 | }; 81 | 82 | #undef PG_KEYWORD 83 | 84 | /* 85 | * Set the type of YYSTYPE. 86 | */ 87 | #define YYSTYPE core_YYSTYPE 88 | 89 | /* 90 | * Set the type of yyextra. All state variables used by the scanner should 91 | * be in yyextra, *not* statically allocated. 92 | */ 93 | #define YY_EXTRA_TYPE core_yy_extra_type * 94 | 95 | /* 96 | * Each call to yylex must set yylloc to the location of the found token 97 | * (expressed as a byte offset from the start of the input text). 98 | * When we parse a token that requires multiple lexer rules to process, 99 | * this should be done in the first such rule, else yylloc will point 100 | * into the middle of the token. 101 | */ 102 | #define SET_YYLLOC() (*(yylloc) = yytext - yyextra->scanbuf) 103 | 104 | /* 105 | * Advance yylloc by the given number of bytes. 106 | */ 107 | #define ADVANCE_YYLLOC(delta) ( *(yylloc) += (delta) ) 108 | 109 | /* 110 | * Sometimes, we do want yylloc to point into the middle of a token; this is 111 | * useful for instance to throw an error about an escape sequence within a 112 | * string literal. But if we find no error there, we want to revert yylloc 113 | * to the token start, so that that's the location reported to the parser. 114 | * Use PUSH_YYLLOC/POP_YYLLOC to save/restore yylloc around such code. 115 | * (Currently the implied "stack" is just one location, but someday we might 116 | * need to nest these.) 117 | */ 118 | #define PUSH_YYLLOC() (yyextra->save_yylloc = *(yylloc)) 119 | #define POP_YYLLOC() (*(yylloc) = yyextra->save_yylloc) 120 | 121 | #define startlit() ( yyextra->literallen = 0 ) 122 | static void addlit(char *ytext, int yleng, core_yyscan_t yyscanner); 123 | static void addlitchar(unsigned char ychar, core_yyscan_t yyscanner); 124 | static char *litbufdup(core_yyscan_t yyscanner); 125 | static unsigned char unescape_single_char(unsigned char c, core_yyscan_t yyscanner); 126 | static int process_integer_literal(const char *token, YYSTYPE *lval); 127 | static void addunicode(pg_wchar c, yyscan_t yyscanner); 128 | 129 | #define yyerror(msg) scanner_yyerror(msg, yyscanner) 130 | 131 | #define lexer_errposition() scanner_errposition(*(yylloc), yyscanner) 132 | 133 | static void check_string_escape_warning(unsigned char ychar, core_yyscan_t yyscanner); 134 | static void check_escape_warning(core_yyscan_t yyscanner); 135 | 136 | /* 137 | * Work around a bug in flex 2.5.35: it emits a couple of functions that 138 | * it forgets to emit declarations for. Since we use -Wmissing-prototypes, 139 | * this would cause warnings. Providing our own declarations should be 140 | * harmless even when the bug gets fixed. 141 | */ 142 | extern int core_yyget_column(yyscan_t yyscanner); 143 | extern void core_yyset_column(int column_no, yyscan_t yyscanner); 144 | 145 | %} 146 | 147 | %option reentrant 148 | %option bison-bridge 149 | %option bison-locations 150 | %option 8bit 151 | %option never-interactive 152 | %option nodefault 153 | %option noinput 154 | %option nounput 155 | %option noyywrap 156 | %option noyyalloc 157 | %option noyyrealloc 158 | %option noyyfree 159 | %option warn 160 | %option prefix="core_yy" 161 | 162 | /* 163 | * OK, here is a short description of lex/flex rules behavior. 164 | * The longest pattern which matches an input string is always chosen. 165 | * For equal-length patterns, the first occurring in the rules list is chosen. 166 | * INITIAL is the starting state, to which all non-conditional rules apply. 167 | * Exclusive states change parsing rules while the state is active. When in 168 | * an exclusive state, only those rules defined for that state apply. 169 | * 170 | * We use exclusive states for quoted strings, extended comments, 171 | * and to eliminate parsing troubles for numeric strings. 172 | * Exclusive states: 173 | * bit string literal 174 | * extended C-style comments 175 | * delimited identifiers (double-quoted identifiers) 176 | * hexadecimal numeric string 177 | * standard quoted strings 178 | * quote stop (detect continued strings) 179 | * extended quoted strings (support backslash escape sequences) 180 | * $foo$ quoted strings 181 | * quoted identifier with Unicode escapes 182 | * quoted string with Unicode escapes 183 | * Unicode surrogate pair in extended quoted string 184 | * 185 | * Remember to add an <> case whenever you add a new exclusive state! 186 | * The default one is probably not the right thing. 187 | */ 188 | 189 | %x xb 190 | %x xc 191 | %x xd 192 | %x xh 193 | %x xq 194 | %x xqs 195 | %x xe 196 | %x xdolq 197 | %x xui 198 | %x xus 199 | %x xeu 200 | 201 | /* 202 | * In order to make the world safe for Windows and Mac clients as well as 203 | * Unix ones, we accept either \n or \r as a newline. A DOS-style \r\n 204 | * sequence will be seen as two successive newlines, but that doesn't cause 205 | * any problems. Comments that start with -- and extend to the next 206 | * newline are treated as equivalent to a single whitespace character. 207 | * 208 | * NOTE a fine point: if there is no newline following --, we will absorb 209 | * everything to the end of the input as a comment. This is correct. Older 210 | * versions of Postgres failed to recognize -- as a comment if the input 211 | * did not end with a newline. 212 | * 213 | * XXX perhaps \f (formfeed) should be treated as a newline as well? 214 | * 215 | * XXX if you change the set of whitespace characters, fix scanner_isspace() 216 | * to agree. 217 | */ 218 | 219 | space [ \t\n\r\f] 220 | horiz_space [ \t\f] 221 | newline [\n\r] 222 | non_newline [^\n\r] 223 | 224 | comment ("--"{non_newline}*) 225 | 226 | whitespace ({space}+|{comment}) 227 | 228 | /* 229 | * SQL requires at least one newline in the whitespace separating 230 | * string literals that are to be concatenated. Silly, but who are we 231 | * to argue? Note that {whitespace_with_newline} should not have * after 232 | * it, whereas {whitespace} should generally have a * after it... 233 | */ 234 | 235 | special_whitespace ({space}+|{comment}{newline}) 236 | horiz_whitespace ({horiz_space}|{comment}) 237 | whitespace_with_newline ({horiz_whitespace}*{newline}{special_whitespace}*) 238 | 239 | quote ' 240 | /* If we see {quote} then {quotecontinue}, the quoted string continues */ 241 | quotecontinue {whitespace_with_newline}{quote} 242 | 243 | /* 244 | * {quotecontinuefail} is needed to avoid lexer backup when we fail to match 245 | * {quotecontinue}. It might seem that this could just be {whitespace}*, 246 | * but if there's a dash after {whitespace_with_newline}, it must be consumed 247 | * to see if there's another dash --- which would start a {comment} and thus 248 | * allow continuation of the {quotecontinue} token. 249 | */ 250 | quotecontinuefail {whitespace}*"-"? 251 | 252 | /* Bit string 253 | * It is tempting to scan the string for only those characters 254 | * which are allowed. However, this leads to silently swallowed 255 | * characters if illegal characters are included in the string. 256 | * For example, if xbinside is [01] then B'ABCD' is interpreted 257 | * as a zero-length string, and the ABCD' is lost! 258 | * Better to pass the string forward and let the input routines 259 | * validate the contents. 260 | */ 261 | xbstart [bB]{quote} 262 | xbinside [^']* 263 | 264 | /* Hexadecimal number */ 265 | xhstart [xX]{quote} 266 | xhinside [^']* 267 | 268 | /* National character */ 269 | xnstart [nN]{quote} 270 | 271 | /* Quoted string that allows backslash escapes */ 272 | xestart [eE]{quote} 273 | xeinside [^\\']+ 274 | xeescape [\\][^0-7] 275 | xeoctesc [\\][0-7]{1,3} 276 | xehexesc [\\]x[0-9A-Fa-f]{1,2} 277 | xeunicode [\\](u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}) 278 | xeunicodefail [\\](u[0-9A-Fa-f]{0,3}|U[0-9A-Fa-f]{0,7}) 279 | 280 | /* Extended quote 281 | * xqdouble implements embedded quote, '''' 282 | */ 283 | xqstart {quote} 284 | xqdouble {quote}{quote} 285 | xqinside [^']+ 286 | 287 | /* $foo$ style quotes ("dollar quoting") 288 | * The quoted string starts with $foo$ where "foo" is an optional string 289 | * in the form of an identifier, except that it may not contain "$", 290 | * and extends to the first occurrence of an identical string. 291 | * There is *no* processing of the quoted text. 292 | * 293 | * {dolqfailed} is an error rule to avoid scanner backup when {dolqdelim} 294 | * fails to match its trailing "$". 295 | */ 296 | dolq_start [A-Za-z\200-\377_] 297 | dolq_cont [A-Za-z\200-\377_0-9] 298 | dolqdelim \$({dolq_start}{dolq_cont}*)?\$ 299 | dolqfailed \${dolq_start}{dolq_cont}* 300 | dolqinside [^$]+ 301 | 302 | /* Double quote 303 | * Allows embedded spaces and other special characters into identifiers. 304 | */ 305 | dquote \" 306 | xdstart {dquote} 307 | xdstop {dquote} 308 | xddouble {dquote}{dquote} 309 | xdinside [^"]+ 310 | 311 | /* Quoted identifier with Unicode escapes */ 312 | xuistart [uU]&{dquote} 313 | 314 | /* Quoted string with Unicode escapes */ 315 | xusstart [uU]&{quote} 316 | 317 | /* error rule to avoid backup */ 318 | xufailed [uU]& 319 | 320 | 321 | /* C-style comments 322 | * 323 | * The "extended comment" syntax closely resembles allowable operator syntax. 324 | * The tricky part here is to get lex to recognize a string starting with 325 | * slash-star as a comment, when interpreting it as an operator would produce 326 | * a longer match --- remember lex will prefer a longer match! Also, if we 327 | * have something like plus-slash-star, lex will think this is a 3-character 328 | * operator whereas we want to see it as a + operator and a comment start. 329 | * The solution is two-fold: 330 | * 1. append {op_chars}* to xcstart so that it matches as much text as 331 | * {operator} would. Then the tie-breaker (first matching rule of same 332 | * length) ensures xcstart wins. We put back the extra stuff with yyless() 333 | * in case it contains a star-slash that should terminate the comment. 334 | * 2. In the operator rule, check for slash-star within the operator, and 335 | * if found throw it back with yyless(). This handles the plus-slash-star 336 | * problem. 337 | * Dash-dash comments have similar interactions with the operator rule. 338 | */ 339 | xcstart \/\*{op_chars}* 340 | xcstop \*+\/ 341 | xcinside [^*/]+ 342 | 343 | digit [0-9] 344 | ident_start [A-Za-z\200-\377_] 345 | ident_cont [A-Za-z\200-\377_0-9\$] 346 | 347 | identifier {ident_start}{ident_cont}* 348 | 349 | /* Assorted special-case operators and operator-like tokens */ 350 | typecast "::" 351 | dot_dot \.\. 352 | colon_equals ":=" 353 | 354 | /* 355 | * These operator-like tokens (unlike the above ones) also match the {operator} 356 | * rule, which means that they might be overridden by a longer match if they 357 | * are followed by a comment start or a + or - character. Accordingly, if you 358 | * add to this list, you must also add corresponding code to the {operator} 359 | * block to return the correct token in such cases. (This is not needed in 360 | * psqlscan.l since the token value is ignored there.) 361 | */ 362 | equals_greater "=>" 363 | less_equals "<=" 364 | greater_equals ">=" 365 | less_greater "<>" 366 | not_equals "!=" 367 | 368 | /* 369 | * "self" is the set of chars that should be returned as single-character 370 | * tokens. "op_chars" is the set of chars that can make up "Op" tokens, 371 | * which can be one or more characters long (but if a single-char token 372 | * appears in the "self" set, it is not to be returned as an Op). Note 373 | * that the sets overlap, but each has some chars that are not in the other. 374 | * 375 | * If you change either set, adjust the character lists appearing in the 376 | * rule for "operator"! 377 | */ 378 | self [,()\[\].;\:\+\-\*\/\%\^\<\>\=] 379 | op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=] 380 | operator {op_chars}+ 381 | 382 | /* we no longer allow unary minus in numbers. 383 | * instead we pass it separately to parser. there it gets 384 | * coerced via doNegate() -- Leon aug 20 1999 385 | * 386 | * {decimalfail} is used because we would like "1..10" to lex as 1, dot_dot, 10. 387 | * 388 | * {realfail1} and {realfail2} are added to prevent the need for scanner 389 | * backup when the {real} rule fails to match completely. 390 | */ 391 | 392 | integer {digit}+ 393 | decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*)) 394 | decimalfail {digit}+\.\. 395 | real ({integer}|{decimal})[Ee][-+]?{digit}+ 396 | realfail1 ({integer}|{decimal})[Ee] 397 | realfail2 ({integer}|{decimal})[Ee][-+] 398 | 399 | param \${integer} 400 | 401 | other . 402 | 403 | /* 404 | * Dollar quoted strings are totally opaque, and no escaping is done on them. 405 | * Other quoted strings must allow some special characters such as single-quote 406 | * and newline. 407 | * Embedded single-quotes are implemented both in the SQL standard 408 | * style of two adjacent single quotes "''" and in the Postgres/Java style 409 | * of escaped-quote "\'". 410 | * Other embedded escaped characters are matched explicitly and the leading 411 | * backslash is dropped from the string. 412 | * Note that xcstart must appear before operator, as explained above! 413 | * Also whitespace (comment) must appear before operator. 414 | */ 415 | 416 | %% 417 | 418 | {whitespace} { 419 | /* ignore */ 420 | } 421 | 422 | {xcstart} { 423 | /* Set location in case of syntax error in comment */ 424 | SET_YYLLOC(); 425 | yyextra->xcdepth = 0; 426 | BEGIN(xc); 427 | /* Put back any characters past slash-star; see above */ 428 | yyless(2); 429 | } 430 | 431 | { 432 | {xcstart} { 433 | (yyextra->xcdepth)++; 434 | /* Put back any characters past slash-star; see above */ 435 | yyless(2); 436 | } 437 | 438 | {xcstop} { 439 | if (yyextra->xcdepth <= 0) 440 | BEGIN(INITIAL); 441 | else 442 | (yyextra->xcdepth)--; 443 | } 444 | 445 | {xcinside} { 446 | /* ignore */ 447 | } 448 | 449 | {op_chars} { 450 | /* ignore */ 451 | } 452 | 453 | \*+ { 454 | /* ignore */ 455 | } 456 | 457 | <> { 458 | yyerror("unterminated /* comment"); 459 | } 460 | } /* */ 461 | 462 | {xbstart} { 463 | /* Binary bit type. 464 | * At some point we should simply pass the string 465 | * forward to the parser and label it there. 466 | * In the meantime, place a leading "b" on the string 467 | * to mark it for the input routine as a binary string. 468 | */ 469 | SET_YYLLOC(); 470 | BEGIN(xb); 471 | startlit(); 472 | addlitchar('b', yyscanner); 473 | } 474 | {xhinside} | 475 | {xbinside} { 476 | addlit(yytext, yyleng, yyscanner); 477 | } 478 | <> { yyerror("unterminated bit string literal"); } 479 | 480 | {xhstart} { 481 | /* Hexadecimal bit type. 482 | * At some point we should simply pass the string 483 | * forward to the parser and label it there. 484 | * In the meantime, place a leading "x" on the string 485 | * to mark it for the input routine as a hex string. 486 | */ 487 | SET_YYLLOC(); 488 | BEGIN(xh); 489 | startlit(); 490 | addlitchar('x', yyscanner); 491 | } 492 | <> { yyerror("unterminated hexadecimal string literal"); } 493 | 494 | {xnstart} { 495 | /* National character. 496 | * We will pass this along as a normal character string, 497 | * but preceded with an internally-generated "NCHAR". 498 | */ 499 | int kwnum; 500 | 501 | SET_YYLLOC(); 502 | yyless(1); /* eat only 'n' this time */ 503 | 504 | kwnum = ScanKeywordLookup("nchar", 505 | yyextra->keywordlist); 506 | if (kwnum >= 0) 507 | { 508 | yylval->keyword = GetScanKeyword(kwnum, 509 | yyextra->keywordlist); 510 | return yyextra->keyword_tokens[kwnum]; 511 | } 512 | else 513 | { 514 | /* If NCHAR isn't a keyword, just return "n" */ 515 | yylval->str = pstrdup("n"); 516 | return IDENT; 517 | } 518 | } 519 | 520 | {xqstart} { 521 | yyextra->warn_on_first_escape = true; 522 | yyextra->saw_non_ascii = false; 523 | SET_YYLLOC(); 524 | if (yyextra->standard_conforming_strings) 525 | BEGIN(xq); 526 | else 527 | BEGIN(xe); 528 | startlit(); 529 | } 530 | {xestart} { 531 | yyextra->warn_on_first_escape = false; 532 | yyextra->saw_non_ascii = false; 533 | SET_YYLLOC(); 534 | BEGIN(xe); 535 | startlit(); 536 | } 537 | {xusstart} { 538 | SET_YYLLOC(); 539 | if (!yyextra->standard_conforming_strings) 540 | ereport(ERROR, 541 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 542 | errmsg("unsafe use of string constant with Unicode escapes"), 543 | errdetail("String constants with Unicode escapes cannot be used when standard_conforming_strings is off."), 544 | lexer_errposition())); 545 | BEGIN(xus); 546 | startlit(); 547 | } 548 | 549 | {quote} { 550 | /* 551 | * When we are scanning a quoted string and see an end 552 | * quote, we must look ahead for a possible continuation. 553 | * If we don't see one, we know the end quote was in fact 554 | * the end of the string. To reduce the lexer table size, 555 | * we use a single "xqs" state to do the lookahead for all 556 | * types of strings. 557 | */ 558 | yyextra->state_before_str_stop = YYSTATE; 559 | BEGIN(xqs); 560 | } 561 | {quotecontinue} { 562 | /* 563 | * Found a quote continuation, so return to the in-quote 564 | * state and continue scanning the literal. Nothing is 565 | * added to the literal's contents. 566 | */ 567 | BEGIN(yyextra->state_before_str_stop); 568 | } 569 | {quotecontinuefail} | 570 | {other} | 571 | <> { 572 | /* 573 | * Failed to see a quote continuation. Throw back 574 | * everything after the end quote, and handle the string 575 | * according to the state we were in previously. 576 | */ 577 | yyless(0); 578 | BEGIN(INITIAL); 579 | 580 | switch (yyextra->state_before_str_stop) 581 | { 582 | case xb: 583 | yylval->str = litbufdup(yyscanner); 584 | return BCONST; 585 | case xh: 586 | yylval->str = litbufdup(yyscanner); 587 | return XCONST; 588 | case xq: 589 | case xe: 590 | /* 591 | * Check that the data remains valid, if it might 592 | * have been made invalid by unescaping any chars. 593 | */ 594 | if (yyextra->saw_non_ascii) 595 | pg_verifymbstr(yyextra->literalbuf, 596 | yyextra->literallen, 597 | false); 598 | yylval->str = litbufdup(yyscanner); 599 | return SCONST; 600 | case xus: 601 | yylval->str = litbufdup(yyscanner); 602 | return USCONST; 603 | default: 604 | yyerror("unhandled previous state in xqs"); 605 | } 606 | } 607 | 608 | {xqdouble} { 609 | addlitchar('\'', yyscanner); 610 | } 611 | {xqinside} { 612 | addlit(yytext, yyleng, yyscanner); 613 | } 614 | {xeinside} { 615 | addlit(yytext, yyleng, yyscanner); 616 | } 617 | {xeunicode} { 618 | pg_wchar c = strtoul(yytext + 2, NULL, 16); 619 | 620 | /* 621 | * For consistency with other productions, issue any 622 | * escape warning with cursor pointing to start of string. 623 | * We might want to change that, someday. 624 | */ 625 | check_escape_warning(yyscanner); 626 | 627 | /* Remember start of overall string token ... */ 628 | PUSH_YYLLOC(); 629 | /* ... and set the error cursor to point at this esc seq */ 630 | SET_YYLLOC(); 631 | 632 | if (is_utf16_surrogate_first(c)) 633 | { 634 | yyextra->utf16_first_part = c; 635 | BEGIN(xeu); 636 | } 637 | else if (is_utf16_surrogate_second(c)) 638 | yyerror("invalid Unicode surrogate pair"); 639 | else 640 | addunicode(c, yyscanner); 641 | 642 | /* Restore yylloc to be start of string token */ 643 | POP_YYLLOC(); 644 | } 645 | {xeunicode} { 646 | pg_wchar c = strtoul(yytext + 2, NULL, 16); 647 | 648 | /* Remember start of overall string token ... */ 649 | PUSH_YYLLOC(); 650 | /* ... and set the error cursor to point at this esc seq */ 651 | SET_YYLLOC(); 652 | 653 | if (!is_utf16_surrogate_second(c)) 654 | yyerror("invalid Unicode surrogate pair"); 655 | 656 | c = surrogate_pair_to_codepoint(yyextra->utf16_first_part, c); 657 | 658 | addunicode(c, yyscanner); 659 | 660 | /* Restore yylloc to be start of string token */ 661 | POP_YYLLOC(); 662 | 663 | BEGIN(xe); 664 | } 665 | . | 666 | \n | 667 | <> { 668 | /* Set the error cursor to point at missing esc seq */ 669 | SET_YYLLOC(); 670 | yyerror("invalid Unicode surrogate pair"); 671 | } 672 | {xeunicodefail} { 673 | /* Set the error cursor to point at malformed esc seq */ 674 | SET_YYLLOC(); 675 | ereport(ERROR, 676 | (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), 677 | errmsg("invalid Unicode escape"), 678 | errhint("Unicode escapes must be \\uXXXX or \\UXXXXXXXX."), 679 | lexer_errposition())); 680 | } 681 | {xeescape} { 682 | if (yytext[1] == '\'') 683 | { 684 | if (yyextra->backslash_quote == BACKSLASH_QUOTE_OFF || 685 | (yyextra->backslash_quote == BACKSLASH_QUOTE_SAFE_ENCODING && 686 | PG_ENCODING_IS_CLIENT_ONLY(pg_get_client_encoding()))) 687 | ereport(ERROR, 688 | (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 689 | errmsg("unsafe use of \\' in a string literal"), 690 | errhint("Use '' to write quotes in strings. \\' is insecure in client-only encodings."), 691 | lexer_errposition())); 692 | } 693 | check_string_escape_warning(yytext[1], yyscanner); 694 | addlitchar(unescape_single_char(yytext[1], yyscanner), 695 | yyscanner); 696 | } 697 | {xeoctesc} { 698 | unsigned char c = strtoul(yytext + 1, NULL, 8); 699 | 700 | check_escape_warning(yyscanner); 701 | addlitchar(c, yyscanner); 702 | if (c == '\0' || IS_HIGHBIT_SET(c)) 703 | yyextra->saw_non_ascii = true; 704 | } 705 | {xehexesc} { 706 | unsigned char c = strtoul(yytext + 2, NULL, 16); 707 | 708 | check_escape_warning(yyscanner); 709 | addlitchar(c, yyscanner); 710 | if (c == '\0' || IS_HIGHBIT_SET(c)) 711 | yyextra->saw_non_ascii = true; 712 | } 713 | . { 714 | /* This is only needed for \ just before EOF */ 715 | addlitchar(yytext[0], yyscanner); 716 | } 717 | <> { yyerror("unterminated quoted string"); } 718 | 719 | {dolqdelim} { 720 | SET_YYLLOC(); 721 | yyextra->dolqstart = pstrdup(yytext); 722 | BEGIN(xdolq); 723 | startlit(); 724 | } 725 | {dolqfailed} { 726 | SET_YYLLOC(); 727 | /* throw back all but the initial "$" */ 728 | yyless(1); 729 | /* and treat it as {other} */ 730 | return yytext[0]; 731 | } 732 | {dolqdelim} { 733 | if (strcmp(yytext, yyextra->dolqstart) == 0) 734 | { 735 | pfree(yyextra->dolqstart); 736 | yyextra->dolqstart = NULL; 737 | BEGIN(INITIAL); 738 | yylval->str = litbufdup(yyscanner); 739 | return SCONST; 740 | } 741 | else 742 | { 743 | /* 744 | * When we fail to match $...$ to dolqstart, transfer 745 | * the $... part to the output, but put back the final 746 | * $ for rescanning. Consider $delim$...$junk$delim$ 747 | */ 748 | addlit(yytext, yyleng - 1, yyscanner); 749 | yyless(yyleng - 1); 750 | } 751 | } 752 | {dolqinside} { 753 | addlit(yytext, yyleng, yyscanner); 754 | } 755 | {dolqfailed} { 756 | addlit(yytext, yyleng, yyscanner); 757 | } 758 | . { 759 | /* This is only needed for $ inside the quoted text */ 760 | addlitchar(yytext[0], yyscanner); 761 | } 762 | <> { yyerror("unterminated dollar-quoted string"); } 763 | 764 | {xdstart} { 765 | SET_YYLLOC(); 766 | BEGIN(xd); 767 | startlit(); 768 | } 769 | {xuistart} { 770 | SET_YYLLOC(); 771 | BEGIN(xui); 772 | startlit(); 773 | } 774 | {xdstop} { 775 | char *ident; 776 | 777 | BEGIN(INITIAL); 778 | if (yyextra->literallen == 0) 779 | yyerror("zero-length delimited identifier"); 780 | ident = litbufdup(yyscanner); 781 | if (yyextra->literallen >= NAMEDATALEN) 782 | truncate_identifier(ident, yyextra->literallen, true); 783 | yylval->str = ident; 784 | return IDENT; 785 | } 786 | {dquote} { 787 | BEGIN(INITIAL); 788 | if (yyextra->literallen == 0) 789 | yyerror("zero-length delimited identifier"); 790 | /* can't truncate till after we de-escape the ident */ 791 | yylval->str = litbufdup(yyscanner); 792 | return UIDENT; 793 | } 794 | {xddouble} { 795 | addlitchar('"', yyscanner); 796 | } 797 | {xdinside} { 798 | addlit(yytext, yyleng, yyscanner); 799 | } 800 | <> { yyerror("unterminated quoted identifier"); } 801 | 802 | {xufailed} { 803 | char *ident; 804 | 805 | SET_YYLLOC(); 806 | /* throw back all but the initial u/U */ 807 | yyless(1); 808 | /* and treat it as {identifier} */ 809 | ident = downcase_truncate_identifier(yytext, yyleng, true); 810 | yylval->str = ident; 811 | return IDENT; 812 | } 813 | 814 | {typecast} { 815 | SET_YYLLOC(); 816 | return TYPECAST; 817 | } 818 | 819 | {dot_dot} { 820 | SET_YYLLOC(); 821 | return DOT_DOT; 822 | } 823 | 824 | {colon_equals} { 825 | SET_YYLLOC(); 826 | return COLON_EQUALS; 827 | } 828 | 829 | {equals_greater} { 830 | SET_YYLLOC(); 831 | return EQUALS_GREATER; 832 | } 833 | 834 | {less_equals} { 835 | SET_YYLLOC(); 836 | return LESS_EQUALS; 837 | } 838 | 839 | {greater_equals} { 840 | SET_YYLLOC(); 841 | return GREATER_EQUALS; 842 | } 843 | 844 | {less_greater} { 845 | /* We accept both "<>" and "!=" as meaning NOT_EQUALS */ 846 | SET_YYLLOC(); 847 | return NOT_EQUALS; 848 | } 849 | 850 | {not_equals} { 851 | /* We accept both "<>" and "!=" as meaning NOT_EQUALS */ 852 | SET_YYLLOC(); 853 | return NOT_EQUALS; 854 | } 855 | 856 | {self} { 857 | SET_YYLLOC(); 858 | return yytext[0]; 859 | } 860 | 861 | {operator} { 862 | /* 863 | * Check for embedded slash-star or dash-dash; those 864 | * are comment starts, so operator must stop there. 865 | * Note that slash-star or dash-dash at the first 866 | * character will match a prior rule, not this one. 867 | */ 868 | int nchars = yyleng; 869 | char *slashstar = strstr(yytext, "/*"); 870 | char *dashdash = strstr(yytext, "--"); 871 | 872 | if (slashstar && dashdash) 873 | { 874 | /* if both appear, take the first one */ 875 | if (slashstar > dashdash) 876 | slashstar = dashdash; 877 | } 878 | else if (!slashstar) 879 | slashstar = dashdash; 880 | if (slashstar) 881 | nchars = slashstar - yytext; 882 | 883 | /* 884 | * For SQL compatibility, '+' and '-' cannot be the 885 | * last char of a multi-char operator unless the operator 886 | * contains chars that are not in SQL operators. 887 | * The idea is to lex '=-' as two operators, but not 888 | * to forbid operator names like '?-' that could not be 889 | * sequences of SQL operators. 890 | */ 891 | if (nchars > 1 && 892 | (yytext[nchars - 1] == '+' || 893 | yytext[nchars - 1] == '-')) 894 | { 895 | int ic; 896 | 897 | for (ic = nchars - 2; ic >= 0; ic--) 898 | { 899 | char c = yytext[ic]; 900 | if (c == '~' || c == '!' || c == '@' || 901 | c == '#' || c == '^' || c == '&' || 902 | c == '|' || c == '`' || c == '?' || 903 | c == '%') 904 | break; 905 | } 906 | if (ic < 0) 907 | { 908 | /* 909 | * didn't find a qualifying character, so remove 910 | * all trailing [+-] 911 | */ 912 | do { 913 | nchars--; 914 | } while (nchars > 1 && 915 | (yytext[nchars - 1] == '+' || 916 | yytext[nchars - 1] == '-')); 917 | } 918 | } 919 | 920 | SET_YYLLOC(); 921 | 922 | if (nchars < yyleng) 923 | { 924 | /* Strip the unwanted chars from the token */ 925 | yyless(nchars); 926 | /* 927 | * If what we have left is only one char, and it's 928 | * one of the characters matching "self", then 929 | * return it as a character token the same way 930 | * that the "self" rule would have. 931 | */ 932 | if (nchars == 1 && 933 | strchr(",()[].;:+-*/%^<>=", yytext[0])) 934 | return yytext[0]; 935 | /* 936 | * Likewise, if what we have left is two chars, and 937 | * those match the tokens ">=", "<=", "=>", "<>" or 938 | * "!=", then we must return the appropriate token 939 | * rather than the generic Op. 940 | */ 941 | if (nchars == 2) 942 | { 943 | if (yytext[0] == '=' && yytext[1] == '>') 944 | return EQUALS_GREATER; 945 | if (yytext[0] == '>' && yytext[1] == '=') 946 | return GREATER_EQUALS; 947 | if (yytext[0] == '<' && yytext[1] == '=') 948 | return LESS_EQUALS; 949 | if (yytext[0] == '<' && yytext[1] == '>') 950 | return NOT_EQUALS; 951 | if (yytext[0] == '!' && yytext[1] == '=') 952 | return NOT_EQUALS; 953 | } 954 | } 955 | 956 | /* 957 | * Complain if operator is too long. Unlike the case 958 | * for identifiers, we make this an error not a notice- 959 | * and-truncate, because the odds are we are looking at 960 | * a syntactic mistake anyway. 961 | */ 962 | if (nchars >= NAMEDATALEN) 963 | yyerror("operator too long"); 964 | 965 | yylval->str = pstrdup(yytext); 966 | return Op; 967 | } 968 | 969 | {param} { 970 | SET_YYLLOC(); 971 | yylval->ival = atol(yytext + 1); 972 | return PARAM; 973 | } 974 | 975 | {integer} { 976 | SET_YYLLOC(); 977 | return process_integer_literal(yytext, yylval); 978 | } 979 | {decimal} { 980 | SET_YYLLOC(); 981 | yylval->str = pstrdup(yytext); 982 | return FCONST; 983 | } 984 | {decimalfail} { 985 | /* throw back the .., and treat as integer */ 986 | yyless(yyleng - 2); 987 | SET_YYLLOC(); 988 | return process_integer_literal(yytext, yylval); 989 | } 990 | {real} { 991 | SET_YYLLOC(); 992 | yylval->str = pstrdup(yytext); 993 | return FCONST; 994 | } 995 | {realfail1} { 996 | /* 997 | * throw back the [Ee], and figure out whether what 998 | * remains is an {integer} or {decimal}. 999 | */ 1000 | yyless(yyleng - 1); 1001 | SET_YYLLOC(); 1002 | return process_integer_literal(yytext, yylval); 1003 | } 1004 | {realfail2} { 1005 | /* throw back the [Ee][+-], and proceed as above */ 1006 | yyless(yyleng - 2); 1007 | SET_YYLLOC(); 1008 | return process_integer_literal(yytext, yylval); 1009 | } 1010 | 1011 | 1012 | {identifier} { 1013 | int kwnum; 1014 | char *ident; 1015 | 1016 | SET_YYLLOC(); 1017 | 1018 | /* Is it a keyword? */ 1019 | kwnum = ScanKeywordLookup(yytext, 1020 | yyextra->keywordlist); 1021 | if (kwnum >= 0) 1022 | { 1023 | yylval->keyword = GetScanKeyword(kwnum, 1024 | yyextra->keywordlist); 1025 | return yyextra->keyword_tokens[kwnum]; 1026 | } 1027 | 1028 | /* 1029 | * No. Convert the identifier to lower case, and truncate 1030 | * if necessary. 1031 | */ 1032 | ident = downcase_truncate_identifier(yytext, yyleng, true); 1033 | yylval->str = ident; 1034 | return IDENT; 1035 | } 1036 | 1037 | {other} { 1038 | SET_YYLLOC(); 1039 | return yytext[0]; 1040 | } 1041 | 1042 | <> { 1043 | SET_YYLLOC(); 1044 | yyterminate(); 1045 | } 1046 | 1047 | %% 1048 | 1049 | /* LCOV_EXCL_STOP */ 1050 | 1051 | /* 1052 | * Arrange access to yyextra for subroutines of the main yylex() function. 1053 | * We expect each subroutine to have a yyscanner parameter. Rather than 1054 | * use the yyget_xxx functions, which might or might not get inlined by the 1055 | * compiler, we cheat just a bit and cast yyscanner to the right type. 1056 | */ 1057 | #undef yyextra 1058 | #define yyextra (((struct yyguts_t *) yyscanner)->yyextra_r) 1059 | 1060 | /* Likewise for a couple of other things we need. */ 1061 | #undef yylloc 1062 | #define yylloc (((struct yyguts_t *) yyscanner)->yylloc_r) 1063 | #undef yyleng 1064 | #define yyleng (((struct yyguts_t *) yyscanner)->yyleng_r) 1065 | 1066 | 1067 | /* 1068 | * scanner_errposition 1069 | * Report a lexer or grammar error cursor position, if possible. 1070 | * 1071 | * This is expected to be used within an ereport() call, or via an error 1072 | * callback such as setup_scanner_errposition_callback(). The return value 1073 | * is a dummy (always 0, in fact). 1074 | * 1075 | * Note that this can only be used for messages emitted during raw parsing 1076 | * (essentially, scan.l, parser.c, and gram.y), since it requires the 1077 | * yyscanner struct to still be available. 1078 | */ 1079 | int 1080 | scanner_errposition(int location, core_yyscan_t yyscanner) 1081 | { 1082 | int pos; 1083 | 1084 | if (location < 0) 1085 | return 0; /* no-op if location is unknown */ 1086 | 1087 | /* Convert byte offset to character number */ 1088 | pos = pg_mbstrlen_with_len(yyextra->scanbuf, location) + 1; 1089 | /* And pass it to the ereport mechanism */ 1090 | return errposition(pos); 1091 | } 1092 | 1093 | /* 1094 | * Error context callback for inserting scanner error location. 1095 | * 1096 | * Note that this will be called for *any* error occurring while the 1097 | * callback is installed. We avoid inserting an irrelevant error location 1098 | * if the error is a query cancel --- are there any other important cases? 1099 | */ 1100 | static void 1101 | scb_error_callback(void *arg) 1102 | { 1103 | ScannerCallbackState *scbstate = (ScannerCallbackState *) arg; 1104 | 1105 | if (geterrcode() != ERRCODE_QUERY_CANCELED) 1106 | (void) scanner_errposition(scbstate->location, scbstate->yyscanner); 1107 | } 1108 | 1109 | /* 1110 | * setup_scanner_errposition_callback 1111 | * Arrange for non-scanner errors to report an error position 1112 | * 1113 | * Sometimes the scanner calls functions that aren't part of the scanner 1114 | * subsystem and can't reasonably be passed the yyscanner pointer; yet 1115 | * we would like any errors thrown in those functions to be tagged with an 1116 | * error location. Use this function to set up an error context stack 1117 | * entry that will accomplish that. Usage pattern: 1118 | * 1119 | * declare a local variable "ScannerCallbackState scbstate" 1120 | * ... 1121 | * setup_scanner_errposition_callback(&scbstate, yyscanner, location); 1122 | * call function that might throw error; 1123 | * cancel_scanner_errposition_callback(&scbstate); 1124 | */ 1125 | void 1126 | setup_scanner_errposition_callback(ScannerCallbackState *scbstate, 1127 | core_yyscan_t yyscanner, 1128 | int location) 1129 | { 1130 | /* Setup error traceback support for ereport() */ 1131 | scbstate->yyscanner = yyscanner; 1132 | scbstate->location = location; 1133 | scbstate->errcallback.callback = scb_error_callback; 1134 | scbstate->errcallback.arg = (void *) scbstate; 1135 | scbstate->errcallback.previous = error_context_stack; 1136 | error_context_stack = &scbstate->errcallback; 1137 | } 1138 | 1139 | /* 1140 | * Cancel a previously-set-up errposition callback. 1141 | */ 1142 | void 1143 | cancel_scanner_errposition_callback(ScannerCallbackState *scbstate) 1144 | { 1145 | /* Pop the error context stack */ 1146 | error_context_stack = scbstate->errcallback.previous; 1147 | } 1148 | 1149 | /* 1150 | * scanner_yyerror 1151 | * Report a lexer or grammar error. 1152 | * 1153 | * The message's cursor position is whatever YYLLOC was last set to, 1154 | * ie, the start of the current token if called within yylex(), or the 1155 | * most recently lexed token if called from the grammar. 1156 | * This is OK for syntax error messages from the Bison parser, because Bison 1157 | * parsers report error as soon as the first unparsable token is reached. 1158 | * Beware of using yyerror for other purposes, as the cursor position might 1159 | * be misleading! 1160 | */ 1161 | void 1162 | scanner_yyerror(const char *message, core_yyscan_t yyscanner) 1163 | { 1164 | const char *loc = yyextra->scanbuf + *yylloc; 1165 | 1166 | if (*loc == YY_END_OF_BUFFER_CHAR) 1167 | { 1168 | ereport(ERROR, 1169 | (errcode(ERRCODE_SYNTAX_ERROR), 1170 | /* translator: %s is typically the translation of "syntax error" */ 1171 | errmsg("%s at end of input", _(message)), 1172 | lexer_errposition())); 1173 | } 1174 | else 1175 | { 1176 | ereport(ERROR, 1177 | (errcode(ERRCODE_SYNTAX_ERROR), 1178 | /* translator: first %s is typically the translation of "syntax error" */ 1179 | errmsg("%s at or near \"%s\"", _(message), loc), 1180 | lexer_errposition())); 1181 | } 1182 | } 1183 | 1184 | 1185 | /* 1186 | * Called before any actual parsing is done 1187 | */ 1188 | core_yyscan_t 1189 | scanner_init(const char *str, 1190 | core_yy_extra_type *yyext, 1191 | const ScanKeywordList *keywordlist, 1192 | const uint16 *keyword_tokens) 1193 | { 1194 | Size slen = strlen(str); 1195 | yyscan_t scanner; 1196 | 1197 | if (yylex_init(&scanner) != 0) 1198 | elog(ERROR, "yylex_init() failed: %m"); 1199 | 1200 | core_yyset_extra(yyext, scanner); 1201 | 1202 | yyext->keywordlist = keywordlist; 1203 | yyext->keyword_tokens = keyword_tokens; 1204 | 1205 | yyext->backslash_quote = backslash_quote; 1206 | yyext->escape_string_warning = escape_string_warning; 1207 | yyext->standard_conforming_strings = standard_conforming_strings; 1208 | 1209 | /* 1210 | * Make a scan buffer with special termination needed by flex. 1211 | */ 1212 | yyext->scanbuf = (char *) palloc(slen + 2); 1213 | yyext->scanbuflen = slen; 1214 | memcpy(yyext->scanbuf, str, slen); 1215 | yyext->scanbuf[slen] = yyext->scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; 1216 | yy_scan_buffer(yyext->scanbuf, slen + 2, scanner); 1217 | 1218 | /* initialize literal buffer to a reasonable but expansible size */ 1219 | yyext->literalalloc = 1024; 1220 | yyext->literalbuf = (char *) palloc(yyext->literalalloc); 1221 | yyext->literallen = 0; 1222 | 1223 | return scanner; 1224 | } 1225 | 1226 | 1227 | /* 1228 | * Called after parsing is done to clean up after scanner_init() 1229 | */ 1230 | void 1231 | scanner_finish(core_yyscan_t yyscanner) 1232 | { 1233 | /* 1234 | * We don't bother to call yylex_destroy(), because all it would do is 1235 | * pfree a small amount of control storage. It's cheaper to leak the 1236 | * storage until the parsing context is destroyed. The amount of space 1237 | * involved is usually negligible compared to the output parse tree 1238 | * anyway. 1239 | * 1240 | * We do bother to pfree the scanbuf and literal buffer, but only if they 1241 | * represent a nontrivial amount of space. The 8K cutoff is arbitrary. 1242 | */ 1243 | if (yyextra->scanbuflen >= 8192) 1244 | pfree(yyextra->scanbuf); 1245 | if (yyextra->literalalloc >= 8192) 1246 | pfree(yyextra->literalbuf); 1247 | } 1248 | 1249 | 1250 | static void 1251 | addlit(char *ytext, int yleng, core_yyscan_t yyscanner) 1252 | { 1253 | /* enlarge buffer if needed */ 1254 | if ((yyextra->literallen + yleng) >= yyextra->literalalloc) 1255 | { 1256 | do 1257 | { 1258 | yyextra->literalalloc *= 2; 1259 | } while ((yyextra->literallen + yleng) >= yyextra->literalalloc); 1260 | yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf, 1261 | yyextra->literalalloc); 1262 | } 1263 | /* append new data */ 1264 | memcpy(yyextra->literalbuf + yyextra->literallen, ytext, yleng); 1265 | yyextra->literallen += yleng; 1266 | } 1267 | 1268 | 1269 | static void 1270 | addlitchar(unsigned char ychar, core_yyscan_t yyscanner) 1271 | { 1272 | /* enlarge buffer if needed */ 1273 | if ((yyextra->literallen + 1) >= yyextra->literalalloc) 1274 | { 1275 | yyextra->literalalloc *= 2; 1276 | yyextra->literalbuf = (char *) repalloc(yyextra->literalbuf, 1277 | yyextra->literalalloc); 1278 | } 1279 | /* append new data */ 1280 | yyextra->literalbuf[yyextra->literallen] = ychar; 1281 | yyextra->literallen += 1; 1282 | } 1283 | 1284 | 1285 | /* 1286 | * Create a palloc'd copy of literalbuf, adding a trailing null. 1287 | */ 1288 | static char * 1289 | litbufdup(core_yyscan_t yyscanner) 1290 | { 1291 | int llen = yyextra->literallen; 1292 | char *new; 1293 | 1294 | new = palloc(llen + 1); 1295 | memcpy(new, yyextra->literalbuf, llen); 1296 | new[llen] = '\0'; 1297 | return new; 1298 | } 1299 | 1300 | /* 1301 | * Process {integer}. Note this will also do the right thing with {decimal}, 1302 | * ie digits and a decimal point. 1303 | */ 1304 | static int 1305 | process_integer_literal(const char *token, YYSTYPE *lval) 1306 | { 1307 | int val; 1308 | char *endptr; 1309 | 1310 | errno = 0; 1311 | val = strtoint(token, &endptr, 10); 1312 | if (*endptr != '\0' || errno == ERANGE) 1313 | { 1314 | /* integer too large (or contains decimal pt), treat it as a float */ 1315 | lval->str = pstrdup(token); 1316 | return FCONST; 1317 | } 1318 | lval->ival = val; 1319 | return ICONST; 1320 | } 1321 | 1322 | static void 1323 | addunicode(pg_wchar c, core_yyscan_t yyscanner) 1324 | { 1325 | ScannerCallbackState scbstate; 1326 | char buf[MAX_UNICODE_EQUIVALENT_STRING + 1]; 1327 | 1328 | if (!is_valid_unicode_codepoint(c)) 1329 | yyerror("invalid Unicode escape value"); 1330 | 1331 | /* 1332 | * We expect that pg_unicode_to_server() will complain about any 1333 | * unconvertible code point, so we don't have to set saw_non_ascii. 1334 | */ 1335 | setup_scanner_errposition_callback(&scbstate, yyscanner, *(yylloc)); 1336 | pg_unicode_to_server(c, (unsigned char *) buf); 1337 | cancel_scanner_errposition_callback(&scbstate); 1338 | addlit(buf, strlen(buf), yyscanner); 1339 | } 1340 | 1341 | static unsigned char 1342 | unescape_single_char(unsigned char c, core_yyscan_t yyscanner) 1343 | { 1344 | switch (c) 1345 | { 1346 | case 'b': 1347 | return '\b'; 1348 | case 'f': 1349 | return '\f'; 1350 | case 'n': 1351 | return '\n'; 1352 | case 'r': 1353 | return '\r'; 1354 | case 't': 1355 | return '\t'; 1356 | default: 1357 | /* check for backslash followed by non-7-bit-ASCII */ 1358 | if (c == '\0' || IS_HIGHBIT_SET(c)) 1359 | yyextra->saw_non_ascii = true; 1360 | 1361 | return c; 1362 | } 1363 | } 1364 | 1365 | static void 1366 | check_string_escape_warning(unsigned char ychar, core_yyscan_t yyscanner) 1367 | { 1368 | if (ychar == '\'') 1369 | { 1370 | if (yyextra->warn_on_first_escape && yyextra->escape_string_warning) 1371 | ereport(WARNING, 1372 | (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 1373 | errmsg("nonstandard use of \\' in a string literal"), 1374 | errhint("Use '' to write quotes in strings, or use the escape string syntax (E'...')."), 1375 | lexer_errposition())); 1376 | yyextra->warn_on_first_escape = false; /* warn only once per string */ 1377 | } 1378 | else if (ychar == '\\') 1379 | { 1380 | if (yyextra->warn_on_first_escape && yyextra->escape_string_warning) 1381 | ereport(WARNING, 1382 | (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 1383 | errmsg("nonstandard use of \\\\ in a string literal"), 1384 | errhint("Use the escape string syntax for backslashes, e.g., E'\\\\'."), 1385 | lexer_errposition())); 1386 | yyextra->warn_on_first_escape = false; /* warn only once per string */ 1387 | } 1388 | else 1389 | check_escape_warning(yyscanner); 1390 | } 1391 | 1392 | static void 1393 | check_escape_warning(core_yyscan_t yyscanner) 1394 | { 1395 | if (yyextra->warn_on_first_escape && yyextra->escape_string_warning) 1396 | ereport(WARNING, 1397 | (errcode(ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER), 1398 | errmsg("nonstandard use of escape in a string literal"), 1399 | errhint("Use the escape string syntax for escapes, e.g., E'\\r\\n'."), 1400 | lexer_errposition())); 1401 | yyextra->warn_on_first_escape = false; /* warn only once per string */ 1402 | } 1403 | 1404 | /* 1405 | * Interface functions to make flex use palloc() instead of malloc(). 1406 | * It'd be better to make these static, but flex insists otherwise. 1407 | */ 1408 | 1409 | void * 1410 | core_yyalloc(yy_size_t bytes, core_yyscan_t yyscanner) 1411 | { 1412 | return palloc(bytes); 1413 | } 1414 | 1415 | void * 1416 | core_yyrealloc(void *ptr, yy_size_t bytes, core_yyscan_t yyscanner) 1417 | { 1418 | if (ptr) 1419 | return repalloc(ptr, bytes); 1420 | else 1421 | return palloc(bytes); 1422 | } 1423 | 1424 | void 1425 | core_yyfree(void *ptr, core_yyscan_t yyscanner) 1426 | { 1427 | if (ptr) 1428 | pfree(ptr); 1429 | } -------------------------------------------------------------------------------- /tasty-test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified Data.Text as Text 4 | import qualified PostgresqlSyntax.Parsing as Parsing 5 | import Test.Tasty 6 | import Test.Tasty.HUnit 7 | import Prelude hiding (assert) 8 | 9 | main :: IO () 10 | main = 11 | defaultMain 12 | $ testGroup 13 | "" 14 | [ testGroup "Parsers" 15 | $ let testParserOnAllInputs parserName parser inputs = 16 | testCase parserName 17 | $ forM_ inputs 18 | $ \input -> case Parsing.run parser input of 19 | Left err -> assertFailure (err <> "\ninput: " <> Text.unpack input) 20 | Right _ -> return () 21 | in [ testParserOnAllInputs 22 | "preparableStmt" 23 | Parsing.preparableStmt 24 | [ "select i :: int8 from auth.user as u\n\ 25 | \inner join edgenode.usere_provider as p\n\ 26 | \on u.id = p.user_id\n\ 27 | \inner join edgenode.provider_branch as b\n\ 28 | \on b.provider_fk = p.provider_id" 29 | ], 30 | testParserOnAllInputs 31 | "typename" 32 | Parsing.typename 33 | [ "int4[]", 34 | "int4[][]", 35 | "int4?[]", 36 | "int4?[]?", 37 | "aa array", 38 | "DOUBLE PRECISION", 39 | "bool", 40 | "int2", 41 | "int4", 42 | "int8", 43 | "float4", 44 | "float8", 45 | "numeric", 46 | "char", 47 | "text", 48 | "bytea", 49 | "date", 50 | "timestamp", 51 | "timestamptz", 52 | "time", 53 | "timetz", 54 | "interval", 55 | "uuid", 56 | "inet", 57 | "json", 58 | "jsonb" 59 | ] 60 | ] 61 | ] 62 | --------------------------------------------------------------------------------