├── .coveragerc ├── .github └── workflows │ └── pytest.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── docs ├── development.md └── examples.md ├── generate.py ├── pyproject.toml └── src └── queryparser ├── __init__.py ├── adql ├── ADQLLexer.g4 ├── ADQLParser.g4 ├── __init__.py └── adqltranslator.py ├── common ├── __init__.py └── common.py ├── exceptions └── __init__.py ├── mysql ├── MySQLLexer.g4 ├── MySQLParser.g4 ├── __init__.py ├── mysqllisteners.py └── mysqlprocessor.py ├── postgresql ├── PostgreSQLLexer.g4 ├── PostgreSQLParser.g4 ├── __init__.py ├── postgresqllisteners.py └── postgresqlprocessor.py └── testing ├── __init__.py ├── test_adql.py ├── test_mysql.py ├── test_postgresql.py ├── tests.yaml └── utils.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = \ 3 | */MySQL* 4 | */PostgreSQL* 5 | */ADQL* 6 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | 2 | name: pytest 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Set up Python 3.11 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: 3.11 17 | 18 | - name: Install prerequisites 19 | run: | 20 | sudo apt update 21 | sudo apt install -y default-jre 22 | python -m pip install --upgrade pip 23 | 24 | - name: Build queryparser 25 | run: | 26 | wget http://www.antlr.org/download/antlr-4.13.1-complete.jar 27 | make 28 | pip install -I -e .[test] 29 | pip install pytest-cov 30 | pip install coveralls 31 | 32 | - name: Run Tests 33 | run: | 34 | pytest lib/ --cov=queryparser 35 | coveralls --service=github 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | coveralls: 40 | name: Indicate completion to coveralls 41 | needs: build 42 | runs-on: ubuntu-latest 43 | container: python:3-slim 44 | steps: 45 | - name: Run Coveralls finish 46 | run: | 47 | pip install coveralls 48 | coveralls --service=github --finish 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.swp 3 | *.~ 4 | .DS_Store 5 | 6 | .idea 7 | 8 | *.pyc 9 | *.egg-info 10 | 11 | .cache 12 | 13 | .coverage 14 | .pytest_cache 15 | htmlcov 16 | 17 | env 18 | env3 19 | 20 | attic 21 | 22 | lib 23 | build 24 | dist 25 | src/queryparser/mysql/gen/ 26 | src/queryparser/postgresql/gen/ 27 | src/queryparser/adql/gen/ 28 | MANIFEST 29 | 30 | antlr-* 31 | *.interp 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.7.3 (2025-05-30) 4 | 5 | - Add tests for failure 6 | - Add psql native functions 'point' and 'polygon' 7 | 8 | ## 0.7.2 (2025-05-19) 9 | 10 | - Feature: accept custom udf names for query processing 11 | 12 | ## 0.7.1 (2025-05-14) 13 | 14 | - Fix adql translation for 'contains' and 'intersects' 15 | 16 | ## 0.7.0 (2024-05-21) 17 | 18 | major overhaul for ADQL 2.1 recommendation 2023-12-15 19 | 20 | - COOSYS is not required for the geometry constructors anymore, since it's deprecated 21 | - the geometry constructors return the correct datatype (double precision[]) 22 | and correct units (degrees) 23 | - droped the maintenance/support for the translation from ADQL to MySQL. 24 | - bumped the version of `antlr4-python3-runtime` to 4.13.1 25 | - fixed `BOX` constructor, although it's deprecated in ADQL 2.1 26 | - fixed `CONTAINS` for the case `0=CONTAINS()` 27 | - fixed `INTERSECTS` for the case `0=INTERSECTS()` 28 | - new requirements for the `pg_sphere` extension 29 | ([pg_sphere](https://github.com/kimakan/pgsphere/tree/aiprdbms16)) 30 | - removed not supported optional ADQL functions, such as `CENTROID`, `REGION`, etc. 31 | - replaced `setup.py` by `pyproject.toml` since `python setup.py install` is deprecated 32 | 33 | ## 0.6.1 (2022-11-17) 34 | 35 | - fixed the `ORDER BY` clause for the dot-separated column references 36 | 37 | ## 0.6.0 (2022-11-04) 38 | 39 | - bump the version of `antlr4-python3-runtime` to 4.11.1 40 | - added support for two custom functions - `gaia_healpix_index`, `pdist` 41 | - added support for `DISTINCT ON` , [Issue#11](https://github.com/aipescience/queryparser/issues/11) 42 | - added the `ILIKE` operator 43 | - fix installation of requirements.txt (e.g., the required version of 44 | antlr4-python3-runtime was overwritten by the most current one) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | 205 | APACHE HTTP SERVER SUBCOMPONENTS: 206 | 207 | The Apache HTTP Server includes a number of subcomponents with 208 | separate copyright notices and license terms. Your use of the source 209 | code for the these subcomponents is subject to the terms and 210 | conditions of the following licenses. 211 | 212 | For the mod_mime_magic component: 213 | 214 | /* 215 | * mod_mime_magic: MIME type lookup via file magic numbers 216 | * Copyright (c) 1996-1997 Cisco Systems, Inc. 217 | * 218 | * This software was submitted by Cisco Systems to the Apache Group in July 219 | * 1997. Future revisions and derivatives of this source code must 220 | * acknowledge Cisco Systems as the original contributor of this module. 221 | * All other licensing and usage conditions are those of the Apache Group. 222 | * 223 | * Some of this code is derived from the free version of the file command 224 | * originally posted to comp.sources.unix. Copyright info for that program 225 | * is included below as required. 226 | * --------------------------------------------------------------------------- 227 | * - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin. 228 | * 229 | * This software is not subject to any license of the American Telephone and 230 | * Telegraph Company or of the Regents of the University of California. 231 | * 232 | * Permission is granted to anyone to use this software for any purpose on any 233 | * computer system, and to alter it and redistribute it freely, subject to 234 | * the following restrictions: 235 | * 236 | * 1. The author is not responsible for the consequences of use of this 237 | * software, no matter how awful, even if they arise from flaws in it. 238 | * 239 | * 2. The origin of this software must not be misrepresented, either by 240 | * explicit claim or by omission. Since few users ever read sources, credits 241 | * must appear in the documentation. 242 | * 243 | * 3. Altered versions must be plainly marked as such, and must not be 244 | * misrepresented as being the original software. Since few users ever read 245 | * sources, credits must appear in the documentation. 246 | * 247 | * 4. This notice may not be removed or altered. 248 | * ------------------------------------------------------------------------- 249 | * 250 | */ 251 | 252 | 253 | For the modules\mappers\mod_imagemap.c component: 254 | 255 | "macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com 256 | 257 | For the server\util_md5.c component: 258 | 259 | /************************************************************************ 260 | * NCSA HTTPd Server 261 | * Software Development Group 262 | * National Center for Supercomputing Applications 263 | * University of Illinois at Urbana-Champaign 264 | * 605 E. Springfield, Champaign, IL 61820 265 | * httpd@ncsa.uiuc.edu 266 | * 267 | * Copyright (C) 1995, Board of Trustees of the University of Illinois 268 | * 269 | ************************************************************************ 270 | * 271 | * md5.c: NCSA HTTPd code which uses the md5c.c RSA Code 272 | * 273 | * Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc. 274 | * Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon 275 | * University (see Copyright below). 276 | * Portions of Content-MD5 code Copyright (C) 1991 Bell Communications 277 | * Research, Inc. (Bellcore) (see Copyright below). 278 | * Portions extracted from mpack, John G. Myers - jgm+@cmu.edu 279 | * Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk) 280 | * 281 | */ 282 | 283 | 284 | /* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */ 285 | /* (C) Copyright 1993,1994 by Carnegie Mellon University 286 | * All Rights Reserved. 287 | * 288 | * Permission to use, copy, modify, distribute, and sell this software 289 | * and its documentation for any purpose is hereby granted without 290 | * fee, provided that the above copyright notice appear in all copies 291 | * and that both that copyright notice and this permission notice 292 | * appear in supporting documentation, and that the name of Carnegie 293 | * Mellon University not be used in advertising or publicity 294 | * pertaining to distribution of the software without specific, 295 | * written prior permission. Carnegie Mellon University makes no 296 | * representations about the suitability of this software for any 297 | * purpose. It is provided "as is" without express or implied 298 | * warranty. 299 | * 300 | * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO 301 | * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 302 | * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE 303 | * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 304 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 305 | * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 306 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 307 | * SOFTWARE. 308 | */ 309 | 310 | /* 311 | * Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) 312 | * 313 | * Permission to use, copy, modify, and distribute this material 314 | * for any purpose and without fee is hereby granted, provided 315 | * that the above copyright notice and this permission notice 316 | * appear in all copies, and that the name of Bellcore not be 317 | * used in advertising or publicity pertaining to this 318 | * material without the specific, prior written permission 319 | * of an authorized representative of Bellcore. BELLCORE 320 | * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY 321 | * OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", 322 | * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. 323 | */ 324 | 325 | For the srclib\apr\include\apr_md5.h component: 326 | /* 327 | * This is work is derived from material Copyright RSA Data Security, Inc. 328 | * 329 | * The RSA copyright statement and Licence for that original material is 330 | * included below. This is followed by the Apache copyright statement and 331 | * licence for the modifications made to that material. 332 | */ 333 | 334 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 335 | rights reserved. 336 | 337 | License to copy and use this software is granted provided that it 338 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 339 | Algorithm" in all material mentioning or referencing this software 340 | or this function. 341 | 342 | License is also granted to make and use derivative works provided 343 | that such works are identified as "derived from the RSA Data 344 | Security, Inc. MD5 Message-Digest Algorithm" in all material 345 | mentioning or referencing the derived work. 346 | 347 | RSA Data Security, Inc. makes no representations concerning either 348 | the merchantability of this software or the suitability of this 349 | software for any particular purpose. It is provided "as is" 350 | without express or implied warranty of any kind. 351 | 352 | These notices must be retained in any copies of any part of this 353 | documentation and/or software. 354 | */ 355 | 356 | For the srclib\apr\passwd\apr_md5.c component: 357 | 358 | /* 359 | * This is work is derived from material Copyright RSA Data Security, Inc. 360 | * 361 | * The RSA copyright statement and Licence for that original material is 362 | * included below. This is followed by the Apache copyright statement and 363 | * licence for the modifications made to that material. 364 | */ 365 | 366 | /* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm 367 | */ 368 | 369 | /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 370 | rights reserved. 371 | 372 | License to copy and use this software is granted provided that it 373 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 374 | Algorithm" in all material mentioning or referencing this software 375 | or this function. 376 | 377 | License is also granted to make and use derivative works provided 378 | that such works are identified as "derived from the RSA Data 379 | Security, Inc. MD5 Message-Digest Algorithm" in all material 380 | mentioning or referencing the derived work. 381 | 382 | RSA Data Security, Inc. makes no representations concerning either 383 | the merchantability of this software or the suitability of this 384 | software for any particular purpose. It is provided "as is" 385 | without express or implied warranty of any kind. 386 | 387 | These notices must be retained in any copies of any part of this 388 | documentation and/or software. 389 | */ 390 | /* 391 | * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0 392 | * MD5 crypt() function, which is licenced as follows: 393 | * ---------------------------------------------------------------------------- 394 | * "THE BEER-WARE LICENSE" (Revision 42): 395 | * wrote this file. As long as you retain this notice you 396 | * can do whatever you want with this stuff. If we meet some day, and you think 397 | * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 398 | * ---------------------------------------------------------------------------- 399 | */ 400 | 401 | For the srclib\apr-util\crypto\apr_md4.c component: 402 | 403 | * This is derived from material copyright RSA Data Security, Inc. 404 | * Their notice is reproduced below in its entirety. 405 | * 406 | * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 407 | * rights reserved. 408 | * 409 | * License to copy and use this software is granted provided that it 410 | * is identified as the "RSA Data Security, Inc. MD4 Message-Digest 411 | * Algorithm" in all material mentioning or referencing this software 412 | * or this function. 413 | * 414 | * License is also granted to make and use derivative works provided 415 | * that such works are identified as "derived from the RSA Data 416 | * Security, Inc. MD4 Message-Digest Algorithm" in all material 417 | * mentioning or referencing the derived work. 418 | * 419 | * RSA Data Security, Inc. makes no representations concerning either 420 | * the merchantability of this software or the suitability of this 421 | * software for any particular purpose. It is provided "as is" 422 | * without express or implied warranty of any kind. 423 | * 424 | * These notices must be retained in any copies of any part of this 425 | * documentation and/or software. 426 | */ 427 | 428 | For the srclib\apr-util\include\apr_md4.h component: 429 | 430 | * 431 | * This is derived from material copyright RSA Data Security, Inc. 432 | * Their notice is reproduced below in its entirety. 433 | * 434 | * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 435 | * rights reserved. 436 | * 437 | * License to copy and use this software is granted provided that it 438 | * is identified as the "RSA Data Security, Inc. MD4 Message-Digest 439 | * Algorithm" in all material mentioning or referencing this software 440 | * or this function. 441 | * 442 | * License is also granted to make and use derivative works provided 443 | * that such works are identified as "derived from the RSA Data 444 | * Security, Inc. MD4 Message-Digest Algorithm" in all material 445 | * mentioning or referencing the derived work. 446 | * 447 | * RSA Data Security, Inc. makes no representations concerning either 448 | * the merchantability of this software or the suitability of this 449 | * software for any particular purpose. It is provided "as is" 450 | * without express or implied warranty of any kind. 451 | * 452 | * These notices must be retained in any copies of any part of this 453 | * documentation and/or software. 454 | */ 455 | 456 | 457 | For the srclib\apr-util\test\testmd4.c component: 458 | 459 | * 460 | * This is derived from material copyright RSA Data Security, Inc. 461 | * Their notice is reproduced below in its entirety. 462 | * 463 | * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All 464 | * rights reserved. 465 | * 466 | * RSA Data Security, Inc. makes no representations concerning either 467 | * the merchantability of this software or the suitability of this 468 | * software for any particular purpose. It is provided "as is" 469 | * without express or implied warranty of any kind. 470 | * 471 | * These notices must be retained in any copies of any part of this 472 | * documentation and/or software. 473 | */ 474 | 475 | For the srclib\apr-util\xml\expat\conftools\install-sh component: 476 | 477 | # 478 | # install - install a program, script, or datafile 479 | # This comes from X11R5 (mit/util/scripts/install.sh). 480 | # 481 | # Copyright 1991 by the Massachusetts Institute of Technology 482 | # 483 | # Permission to use, copy, modify, distribute, and sell this software and its 484 | # documentation for any purpose is hereby granted without fee, provided that 485 | # the above copyright notice appear in all copies and that both that 486 | # copyright notice and this permission notice appear in supporting 487 | # documentation, and that the name of M.I.T. not be used in advertising or 488 | # publicity pertaining to distribution of the software without specific, 489 | # written prior permission. M.I.T. makes no representations about the 490 | # suitability of this software for any purpose. It is provided "as is" 491 | # without express or implied warranty. 492 | # 493 | 494 | For the test\zb.c component: 495 | 496 | /* ZeusBench V1.01 497 | =============== 498 | 499 | This program is Copyright (C) Zeus Technology Limited 1996. 500 | 501 | This program may be used and copied freely providing this copyright notice 502 | is not removed. 503 | 504 | This software is provided "as is" and any express or implied waranties, 505 | including but not limited to, the implied warranties of merchantability and 506 | fitness for a particular purpose are disclaimed. In no event shall 507 | Zeus Technology Ltd. be liable for any direct, indirect, incidental, special, 508 | exemplary, or consequential damaged (including, but not limited to, 509 | procurement of substitute good or services; loss of use, data, or profits; 510 | or business interruption) however caused and on theory of liability. Whether 511 | in contract, strict liability or tort (including negligence or otherwise) 512 | arising in any way out of the use of this software, even if advised of the 513 | possibility of such damage. 514 | 515 | Written by Adam Twiss (adam@zeus.co.uk). March 1996 516 | 517 | Thanks to the following people for their input: 518 | Mike Belshe (mbelshe@netscape.com) 519 | Michael Campanella (campanella@stevms.enet.dec.com) 520 | 521 | */ 522 | 523 | For the expat xml parser component: 524 | 525 | Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd 526 | and Clark Cooper 527 | 528 | Permission is hereby granted, free of charge, to any person obtaining 529 | a copy of this software and associated documentation files (the 530 | "Software"), to deal in the Software without restriction, including 531 | without limitation the rights to use, copy, modify, merge, publish, 532 | distribute, sublicense, and/or sell copies of the Software, and to 533 | permit persons to whom the Software is furnished to do so, subject to 534 | the following conditions: 535 | 536 | The above copyright notice and this permission notice shall be included 537 | in all copies or substantial portions of the Software. 538 | 539 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 540 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 541 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 542 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 543 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 544 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 545 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 546 | 547 | 548 | ==================================================================== 549 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | 3 | all: lib/queryparser 4 | 5 | lib/queryparser: \ 6 | lib/queryparser/__init__.py \ 7 | lib/queryparser/adql \ 8 | lib/queryparser/common \ 9 | lib/queryparser/exceptions \ 10 | lib/queryparser/mysql \ 11 | lib/queryparser/postgresql \ 12 | lib/queryparser/testing 13 | 14 | lib/queryparser/%.py: src/queryparser/%.py 15 | mkdir -p `dirname $@` 16 | cp $< $@ 17 | 18 | lib/queryparser/common: \ 19 | lib/queryparser/common/common.py \ 20 | lib/queryparser/common/__init__.py 21 | 22 | lib/queryparser/common/%.py: src/queryparser/common/%.py 23 | mkdir -p `dirname $@` 24 | cp $< $@ 25 | 26 | lib/queryparser/adql: \ 27 | lib/queryparser/adql/ADQLParser.py \ 28 | lib/queryparser/adql/adqltranslator.py \ 29 | lib/queryparser/adql/__init__.py 30 | 31 | lib/queryparser/adql/ADQLParser.py: src/queryparser/adql/*.g4 32 | python generate.py -l adql 33 | 34 | lib/queryparser/adql/%.py: src/queryparser/adql/%.py 35 | mkdir -p `dirname $@` 36 | cp $< $@ 37 | 38 | lib/queryparser/exceptions: \ 39 | lib/queryparser/exceptions/__init__.py 40 | 41 | lib/queryparser/exceptions/%.py: src/queryparser/exceptions/%.py 42 | mkdir -p `dirname $@` 43 | cp $< $@ 44 | 45 | lib/queryparser/mysql: \ 46 | lib/queryparser/mysql/MySQLParser.py \ 47 | lib/queryparser/mysql/mysqlprocessor.py \ 48 | lib/queryparser/mysql/mysqllisteners.py \ 49 | lib/queryparser/mysql/__init__.py 50 | 51 | lib/queryparser/mysql/MySQLParser.py: src/queryparser/mysql/*.g4 52 | python generate.py -l mysql 53 | 54 | lib/queryparser/mysql/%.py: src/queryparser/mysql/%.py 55 | mkdir -p `dirname $@` 56 | cp $< $@ 57 | 58 | lib/queryparser/postgresql: \ 59 | lib/queryparser/postgresql/PostgreSQLParser.py \ 60 | lib/queryparser/postgresql/postgresqlprocessor.py \ 61 | lib/queryparser/postgresql/postgresqllisteners.py \ 62 | lib/queryparser/postgresql/__init__.py 63 | 64 | lib/queryparser/postgresql/PostgreSQLParser.py: src/queryparser/postgresql/*.g4 65 | python generate.py -l postgresql 66 | 67 | lib/queryparser/postgresql/%.py: src/queryparser/postgresql/%.py 68 | mkdir -p `dirname $@` 69 | cp $< $@ 70 | 71 | lib/queryparser/testing: \ 72 | lib/queryparser/testing/__init__.py \ 73 | lib/queryparser/testing/utils.py \ 74 | lib/queryparser/testing/tests.yaml \ 75 | lib/queryparser/testing/test_adql.py \ 76 | lib/queryparser/testing/test_mysql.py \ 77 | lib/queryparser/testing/test_postgresql.py 78 | 79 | lib/queryparser/testing/%.py: src/queryparser/testing/%.py 80 | mkdir -p `dirname $@` 81 | cp $< $@ 82 | 83 | lib/queryparser/testing/%.yaml: src/queryparser/testing/%.yaml 84 | mkdir -p `dirname $@` 85 | cp $< $@ 86 | 87 | ############################################################################### 88 | 89 | clean: 90 | rm -fr lib 91 | 92 | .PHONY: all clean 93 | 94 | ############################################################################### 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | queryparser 2 | =========== 3 | 4 | **Tool for parsing and processing of (MySQL\*)/PostgreSQL and translation of 5 | ADQL SELECT-like queries** 6 | 7 | Designed to be used in conjunction with [django-daiquri](https://github.com/django-daiquiri/daiquiri) 8 | as a query processing backend but it can be easily used as a stand-alone tool 9 | or integrated into another project. 10 | 11 | **\*NOTE: Since version 0.7.0 MySQL is not supported (maintained) anymore.** 12 | 13 | 14 | [![pytest Workflow Status](https://github.com/aipescience/queryparser/actions/workflows/pytest.yml/badge.svg)](https://github.com/aipescience/queryparser/actions/workflows/pytest.yml) 15 | [![Coverage Status](https://coveralls.io/repos/aipescience/queryparser/badge.svg?branch=master&service=github)](https://coveralls.io/github/aipescience/queryparser?branch=master) 16 | [![License](http://img.shields.io/badge/license-APACHE-blue.svg?style=flat)](https://github.com/aipescience/queryparser/blob/master/LICENSE) 17 | [![Latest Version](https://img.shields.io/pypi/v/queryparser-python3.svg?style=flat)](https://pypi.org/project/queryparser-python3/) 18 | 19 | 20 | 21 | Installation 22 | ------------ 23 | 24 | The easiest way to install the package is by using the pip tool: 25 | 26 | ```bash 27 | python -m pip install queryparser-python3 28 | ``` 29 | 30 | Alternatively, you can clone the repository and install it from there. 31 | However, this step also requires generating the parser which is a slightly 32 | more elaborate process (see below). 33 | 34 | 35 | Generating the parser from the git repository 36 | --------------------------------------------- 37 | 38 | To generate the parsers you need `python3` , `java` above version 39 | 7, and `antlr4` (`antlr-4.*-complete.jar` has to be installed inside the 40 | `/usr/local/lib/`, `/usr/local/bin/` or root directory of the project). 41 | 42 | The current version of `antlr-4.*-complete.jar` can be downloaded via 43 | 44 | ```bash 45 | wget http://www.antlr.org/download/antlr-4.13.1-complete.jar 46 | ``` 47 | 48 | After cloning the project run 49 | 50 | ```bash 51 | make 52 | ``` 53 | 54 | and a `lib` directory will be created. After that, run 55 | 56 | ```bash 57 | python -m pip install . 58 | ``` 59 | 60 | to install the generated parser in your virtual environment. 61 | 62 | 63 | Additional requirements 64 | ----------------------- 65 | The queryparser assumes that the PostgreSQL database has the extension 66 | [pg_sphere](https://github.com/kimakan/pgsphere/tree/aiprdbms16) installed. 67 | Although the `pg_sphere` is not required for the python module, the PostgreSQL 68 | **queries will not run** without this extension installed on the database. 69 | 70 | 71 | Parsing MySQL and PostgreSQL 72 | ---------------------------- 73 | 74 | **Since version 0.7, MySQL part of the parser is not maintained anymore. 75 | Thus, the MySQL related functionality cannot be guaranteed!** 76 | 77 | Parsing and processing of MySQL queries can be done by creating an instance 78 | of the `MySQLQueryProcessor` class 79 | 80 | ```python 81 | from queryparser.mysql import MySQLQueryProcessor 82 | qp = MySQLQueryProcessor() 83 | ``` 84 | 85 | feeding it a MySQL query 86 | 87 | ```python 88 | sql = "SELECT a FROM db.tab;" 89 | qp.set_query(sql) 90 | ``` 91 | 92 | and running it with 93 | 94 | ```python 95 | qp.process_query() 96 | ``` 97 | 98 | After the processing is completed, the processor object `qp` will include 99 | tables, columns, functions, and keywords used in the query or will raise a 100 | `QuerySyntaxError` if there are any syntax errors in the query. 101 | 102 | Alternatively, passing the query at initialization automatically processes it. 103 | 104 | PostgreSQL parsing is very similar to MySQL, except it requires importing 105 | the `PostgreSQLProcessor` class: 106 | 107 | ```python 108 | from queryparser.postgresql import PostgreSQLQueryProcessor 109 | qp = PostgreSQLQueryProcessor() 110 | ``` 111 | 112 | The rest of the functionality remains the same. 113 | 114 | 115 | Translating ADQL 116 | ---------------- 117 | 118 | Translation of ADQL queries is done similarly by first creating an instance of 119 | the `ADQLQueryTranslator` class 120 | 121 | ```python 122 | from queryparser.adql import ADQLQueryTranslator 123 | adql = "SELECT TOP 100 POINT('ICRS', ra, de) FROM db.tab;" 124 | adt = ADQLQueryTranslator(adql) 125 | ``` 126 | 127 | and calling 128 | 129 | ```python 130 | adt.to_postgresql() 131 | ``` 132 | 133 | which returns a translated string representing a valid MySQL query if 134 | the ADQL query had no errors. The PostgreSQL query can then be parsed with the 135 | `PostgreSQLQueryProcessor` in the same way as shown above. 136 | 137 | Testing 138 | ------- 139 | 140 | First in the root directory of the project, install optional dependencies 141 | (`PyYAML` and `pytest`) by running 142 | 143 | ```bash 144 | python -m pip install .[test] 145 | ``` 146 | 147 | then run the test suite with 148 | 149 | ```bash 150 | python -m pytest lib/ 151 | ``` 152 | 153 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Development notes 2 | 3 | queryparser is a python tool for parsing of MySQL/PostgreSQL SELECT-like queries. 4 | It checks whether the query is syntactically and structurally correct and 5 | after traversing the query structure extracts all touched columns (even if 6 | they are aliased multiple times), touched tables, along with all used keywords 7 | and functions. These are accessible to the user after the query has been 8 | processed and can be used to check, for example, for permissions before the 9 | query is sent to the server. 10 | 11 | In addition, queryparser can also work with ADQL queries which can be translated 12 | to either MySQL or PostgreSQL language and then further processed the same 13 | way. 14 | 15 | Below is a more detailed description along with instructions for anyone who is 16 | interested in further developing the package. 17 | 18 | To start, let us first clone the repository: 19 | 20 | ```bash 21 | git clone git@github.com:aipescience/queryparser.git 22 | ``` 23 | 24 | and then create a virtual environment (inside of which we will install the 25 | package) and activate it: 26 | 27 | ```bash 28 | python -m venv qpenv 29 | source qpenv/bin/activate 30 | ``` 31 | 32 | After the virtual environment has been activated we can build and install 33 | the package from the root directory of the package with 34 | 35 | ```bash 36 | make 37 | python -m pip install . 38 | ``` 39 | 40 | ## Testing 41 | 42 | All tests from the test suite can be executed with 43 | 44 | ```bash 45 | pytest lib/ 46 | ``` 47 | 48 | Individual dialect functionality (MySQL in this case) with increased verbosity 49 | can be tested with 50 | 51 | ```bash 52 | pytest /lib/queryparser/testing/test_mysql.py -v 53 | ``` 54 | 55 | Individual tests (20th MySQL test in this case, but otherwise any test 56 | that includes the string 't20') are ran with 57 | 58 | ```bash 59 | pytest /lib/queryparser/testing/test_mysql.py -k t20 60 | ``` 61 | 62 | If the package `pytest-cov` is installed then the detailed coverage report 63 | can be generated with 64 | 65 | ```bash 66 | pytest --cov=queryparser --cov-report html lib 67 | ``` 68 | 69 | Continuous integration is enabled through GitHub Actions. The configuration is 70 | specified inside of `.github/workflows/pytest.yml` file. Edit as necessary. 71 | Coverage exclusions are defined within `.coveragerc`. 72 | 73 | ### Writing new tests 74 | 75 | All tests are found in the `tests.yaml` file. This file stores different types 76 | of tests bunched into several groups. During testing the tests are loaded by each of 77 | the testing stacks (MySQL, PostgreSQL and ADQL). To avoid code repetition 78 | pytest's `mark.parametrize` is used to loop over all the elements in 79 | each group. This also allows for an easy reuse of the common tests that 80 | are shared between MySQL and PostgreSQL. 81 | 82 | New tests can be easily added to the existing groups of tests and they 83 | will be automatically picked up on the next test run. The structure of each 84 | test group is given at the beginning of the group in the `tests.yaml` file. 85 | Of course, new groups can be added and then employed in the required stack. 86 | If a new type of test is need, it can be defined in the `__init__.py` file 87 | inside of the `testing` directory. 88 | 89 | ## Grammar development 90 | 91 | queryparser's grammar is written in antlr4 language. Each dialect's grammar 92 | stack consist of two files. A lexer that defines the symbols and words (any 93 | combination of characters), including how to handle white-spaces. 94 | The parser defines the rules of the SQL languages. Both files can be edited with any 95 | text editor, however, PyCharm has an integrated antlr functionality and it 96 | makes development and grammar debugging much easier. 97 | 98 | PyCharm offers a plugin for antlr4. It can be installed under File>Settings>Plugins. 99 | Once installed, a right click on any of the rules inside of the parser 100 | grammar file can be tested. A very helpful feature when testing rules this way 101 | is the displayed query tree structure. 102 | 103 | With both lexer and parser defined, antlr4 can generate a parser in a few 104 | different languages, including python. Generating parsers for this project 105 | is done easily with the helper script `generate.py` that can be found in 106 | the root directory of the project. 107 | 108 | #### Adding new PostgreSQL function 109 | 110 | To add a new PostgreSQL function first edit the `PostgreSQLLexer.g4` file and 111 | register the name of the new function. After the name has been added to the 112 | list of known variables and can be recognized by the lexer, the function can 113 | be added to the list under `custom_functions` in the file 114 | `PostgreSQLParser.g4`. 115 | 116 | ## Processor and translator development 117 | 118 | Currently, queryparser package consists of two processors (MySQL and PostgreSQL), 119 | and an ADQL translator. The processors accept any SELECT-like query and 120 | after the process_query() method has been executed, several elements of 121 | the query are extracted (all touch columns and tables, used keywords and functions). 122 | Before the processing the query is validated and invalid queries are rejected. 123 | The ADQL translator allows translating valid ADQL queries to MySQL or PostgreSQL. 124 | 125 | Most of the processor code is shared between MySQL and PostgreSQL and is 126 | therefore merged together inside of the `common.py`. This file consists 127 | of several listeners and the main SQLQueryProcessor class that also implements 128 | the ``process_query()`` method. 129 | 130 | Individual additions to the common stack for MySQL and PostgreSQL are in 131 | their respective sub-directories. 132 | 133 | The main antlr processing object it the `walker` that traverses the whole query 134 | or another context: 135 | 136 | ```python 137 | walker = antlr4.ParseTreeWalker() 138 | walker.walk(listener, context) 139 | ``` 140 | 141 | Listener is a class with methods that react each time the walker stumbles upon a rule 142 | defined in the parser part of the grammar. For example, if there is method 143 | called `enterAlias()` defined inside of a listener, it will be called once 144 | the walker reaches an `Alias` (as it is defined in the antlr parser file) in a 145 | query or another context. 146 | 147 | The main queryparser class that includes this antlr functionality is called 148 | `SQLQueryProcessor`. Besides the helper functions it includes the 149 | `process_query()` that binds the processing together. MySQL and PostgreSQL 150 | processors inherit from this class and extend it with their own listeners. 151 | 152 | 153 | ## New releases 154 | 155 | ### Requirements 156 | Install `build` and `twine` with 157 | ```bash 158 | pip install twine 159 | pip install build 160 | ``` 161 | Make sure you have accounts on `https://pypi.org/` and `https://test.pypi.org/`, 162 | and you are `Maintainer` of the `queryparser-python3` project. 163 | 164 | - https://pypi.org/project/queryparser-python3/ 165 | - https://test.pypi.org/project/queryparser-python3/ 166 | 167 | ### Publishing 168 | 169 | 1. Change the version number in `src/queryparser/__init__.py` 170 | 2. `python -m build .` 171 | 3. `twine check dist/*` 172 | 4. `twine upload --repository-url https://test.pypi.org/legacy/ dist/*` 173 | 5. Check whether the project was correctly uploaded on `test.pypi.org` by executing 174 | `python3 -m pip install --index-url https://test.pypi.org/simple/ queryparser-python3` 175 | 6. `twine upload dist/*` 176 | 7. Create a new release on github. 177 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | 4 | ### A simple PostgreSQL query 5 | 6 | ```SQL 7 | -- Compute HEALPix value from source_id 8 | 9 | SELECT FLOOR(source_id / (POW(2, 35) * POW(4, 6))) AS hpix, radial_velocity AS rv 10 | FROM gdr2.gaia_source 11 | WHERE random_index < 10000000 12 | AND radial_velocity IS NOT NULL; 13 | ``` 14 | 15 | The query is equipped with a comment and it uses a couple of mathematical 16 | functions but is otherwise very straight-forward as it only selects columns 17 | from a single table. 18 | 19 | Let us first create the `Processor` object and feed it the above query 20 | 21 | ```python 22 | qp = PostgreSQLQueryProcessor() 23 | qp.set_query(query) 24 | ``` 25 | 26 | The `query` here can be any multi-line python string. All whitespaces and 27 | line breaks will be ignored. 28 | 29 | We run the processing with 30 | 31 | ```python 32 | qp.process_query() 33 | ``` 34 | 35 | Afterwards, we get 36 | 37 | ```python 38 | print(qp.columns) 39 | [('gdr2', 'gaia_source', 'source_id'), ('gdr2', 'gaia_source', 'radial_velocity'), 40 | ('gdr2', 'gaia_source', 'random_index')] 41 | 42 | print(qp.display_columns) 43 | [('hpix', ['gdr2', 'gaia_source', 'source_id']), 44 | ('rv', ['gdr2', 'gaia_source', 'radial_velocity'])] 45 | 46 | print(qp.tables) 47 | [('gdr2', 'gaia_source')] 48 | 49 | print(qp.functions) 50 | ['FLOOR', 'POW'] 51 | 52 | print(qp.keywords) 53 | ['where'] 54 | ``` 55 | 56 | Besides the touched columns that are stored inside of `qp.columns` we also 57 | get the `display_columns`. These columns are selected (following the SELECT keyword) 58 | and may be aliased which is why it is useful to have a reference to which 59 | exact database column names they are pointing to. 60 | 61 | 62 | ### A more complicated SQL query selecting from multiple tables and having multiple joins 63 | 64 | ```SQL 65 | SELECT t.RAVE_OBS_ID AS c1, t.HEALPix AS c2, 66 | h.logg_SC AS c3, h.TEFF AS c4 67 | FROM RAVEPUB_DR5.RAVE_DR5 AS t 68 | JOIN ( 69 | SELECT sc.RAVE_OBS_ID, logg_SC, k.TEFF 70 | FROM RAVEPUB_DR5.RAVE_Gravity_SC sc 71 | JOIN ( 72 | SELECT RAVE_OBS_ID, TEFF 73 | FROM RAVEPUB_DR5.RAVE_ON 74 | LIMIT 1000 75 | ) AS k USING (RAVE_OBS_ID) 76 | ) AS h USING (RAVE_OBS_ID); 77 | ``` 78 | 79 | When processed using the `MySQLQueryProcessor` or `PostgreSQLQueryProcessor` 80 | it produces the following values 81 | 82 | ```python 83 | print(qp.columns) 84 | [('RAVEPUB_DR5', 'RAVE_ON', 'TEFF'), ('RAVEPUB_DR5', 'RAVE_DR5', 'HEALPix'), 85 | ('RAVEPUB_DR5', 'RAVE_Gravity_SC', 'logg_SC'), ('RAVEPUB_DR5', 'RAVE_Gravity_SC', 'RAVE_OBS_ID'), 86 | ('RAVEPUB_DR5', 'RAVE_DR5', 'RAVE_OBS_ID'), ('RAVEPUB_DR5', 'RAVE_ON', 'RAVE_OBS_ID')] 87 | 88 | print(qp.display_columns) 89 | [('c1', ['RAVEPUB_DR5', 'RAVE_DR5', 'RAVE_OBS_ID']), 90 | ('c2', ['RAVEPUB_DR5', 'RAVE_DR5', 'HEALPix']), 91 | ('c3', ['RAVEPUB_DR5', 'RAVE_Gravity_SC', 'logg_SC']), 92 | ('c4', ['RAVEPUB_DR5', 'RAVE_ON', 'TEFF'])] 93 | 94 | print(qp.tables) 95 | [('RAVEPUB_DR5', 'RAVE_ON'), ('RAVEPUB_DR5', 'RAVE_DR5'), ('RAVEPUB_DR5', 'RAVE_Gravity_SC')] 96 | 97 | print(qp.functions) 98 | [] 99 | 100 | print(qp.keywords) 101 | ['limit', 'join'] 102 | ``` 103 | 104 | 105 | -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | import shutil 6 | import subprocess 7 | import re 8 | 9 | ANTLR_JAR = 'antlr-4.13.1-complete.jar' 10 | ANTLR_DIRS = ('.', '/usr/local/lib/', '/usr/local/bin/') 11 | 12 | QUERYPARSER_SRC = 'src/queryparser/' 13 | MYSQL_SRC = 'src/queryparser/mysql' 14 | POSTGRESQL_SRC = 'src/queryparser/postgresql' 15 | ADQL_SRC = 'src/queryparser/adql' 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser(description='Generate the parsers.') 20 | parser.add_argument('-l', help='language (ADQL / MySQL / PostgreSQL)') 21 | 22 | args = parser.parse_args() 23 | 24 | languages = [args.l] if args.l else ['adql', 'mysql', 'postgresql'] 25 | 26 | if get_java_version() < 7: 27 | raise RuntimeError('You need a newer version of Java.') 28 | 29 | antlr_path = get_antlr_path() 30 | if not antlr_path: 31 | raise RuntimeError('You need %s installed in %s.' 32 | % (ANTLR_JAR, ':'.join(ANTLR_DIRS))) 33 | 34 | for language in languages: 35 | if language == 'adql': 36 | build_adql_translator(antlr_path) 37 | 38 | if language == 'mysql': 39 | build_mysql_parser(antlr_path) 40 | 41 | if language == 'postgresql': 42 | build_postgresql_parser(antlr_path) 43 | 44 | 45 | def get_java_version(): 46 | try: 47 | args = ['java', '-version'] 48 | output = subprocess.check_output(args, stderr=subprocess.STDOUT) 49 | match = re.search('version \"(\d+).(\d+).(.*?)\"', 50 | output.decode('utf-8')) 51 | if match: 52 | ver = int(match.group(2)) 53 | if ver == 0: 54 | ver = int(match.group(1)) 55 | return ver 56 | except OSError: 57 | pass 58 | 59 | raise RuntimeError('No java version found.') 60 | 61 | 62 | def get_antlr_path(): 63 | for directory in ANTLR_DIRS: 64 | path = os.path.join(directory, ANTLR_JAR) 65 | if os.path.exists(path): 66 | return path 67 | return False 68 | 69 | 70 | def build_mysql_parser(antlr_path): 71 | args = ['java', '-jar', antlr_path, '-Dlanguage=Python3', 72 | '-lib', MYSQL_SRC] 73 | 74 | print('building mysql lexer') 75 | subprocess.check_call(args + [os.path.join(MYSQL_SRC, 'MySQLLexer.g4')]) 76 | 77 | print('building mysql parser') 78 | subprocess.check_call(args + [os.path.join(MYSQL_SRC, 'MySQLParser.g4')]) 79 | 80 | directory = os.path.join('lib', 'queryparser/mysql') 81 | 82 | try: 83 | os.makedirs(directory) 84 | except OSError: 85 | pass 86 | 87 | for filename in ['MySQLLexer.py', 'MySQLParser.py', 88 | 'MySQLParserListener.py']: 89 | source = os.path.join(MYSQL_SRC, filename) 90 | target = os.path.join(directory, filename) 91 | 92 | print('moving %s -> %s' % (source, target)) 93 | shutil.move(source, target) 94 | 95 | os.remove(os.path.join(MYSQL_SRC, 'MySQLLexer.tokens')) 96 | os.remove(os.path.join(MYSQL_SRC, 'MySQLParser.tokens')) 97 | 98 | 99 | def build_postgresql_parser(antlr_path): 100 | args = ['java', '-jar', antlr_path, '-Dlanguage=Python3', 101 | '-lib', POSTGRESQL_SRC] 102 | 103 | print('building postgresql lexer') 104 | subprocess.check_call(args + [os.path.join(POSTGRESQL_SRC, 105 | 'PostgreSQLLexer.g4')]) 106 | 107 | print('building postgresql parser') 108 | subprocess.check_call(args + [os.path.join(POSTGRESQL_SRC, 109 | 'PostgreSQLParser.g4')]) 110 | 111 | directory = os.path.join('lib', 'queryparser/postgresql') 112 | 113 | try: 114 | os.makedirs(directory) 115 | except OSError: 116 | pass 117 | 118 | for filename in ['PostgreSQLLexer.py', 'PostgreSQLParser.py', 119 | 'PostgreSQLParserListener.py']: 120 | source = os.path.join(POSTGRESQL_SRC, filename) 121 | target = os.path.join(directory, filename) 122 | 123 | print('moving %s -> %s' % (source, target)) 124 | shutil.move(source, target) 125 | 126 | os.remove(os.path.join(POSTGRESQL_SRC, 'PostgreSQLLexer.tokens')) 127 | os.remove(os.path.join(POSTGRESQL_SRC, 'PostgreSQLParser.tokens')) 128 | 129 | 130 | def build_adql_translator(antlr_path): 131 | args = ['java', '-jar', antlr_path, '-Dlanguage=Python3', 132 | '-visitor', '-lib', ADQL_SRC] 133 | 134 | print('building adql lexer') 135 | subprocess.check_call(args + [os.path.join(ADQL_SRC, 'ADQLLexer.g4')]) 136 | 137 | print('building adql parser') 138 | subprocess.check_call(args + [os.path.join(ADQL_SRC, 'ADQLParser.g4')]) 139 | 140 | directory = os.path.join('lib', 'queryparser/adql') 141 | 142 | try: 143 | os.makedirs(directory) 144 | except OSError: 145 | pass 146 | 147 | for filename in ['ADQLLexer.py', 'ADQLParser.py', 'ADQLParserListener.py', 148 | 'ADQLParserVisitor.py']: 149 | source = os.path.join(ADQL_SRC, filename) 150 | target = os.path.join(directory, filename) 151 | 152 | print('moving %s -> %s' % (source, target)) 153 | shutil.move(source, target) 154 | 155 | os.remove(os.path.join(ADQL_SRC, 'ADQLLexer.tokens')) 156 | os.remove(os.path.join(ADQL_SRC, 'ADQLParser.tokens')) 157 | 158 | 159 | if __name__ == "__main__": 160 | main() 161 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "queryparser-python3" 7 | description = "Package for parsing PostgreSQL/MySQL and translating ADQL to PostgreSQL/MySQL." 8 | readme = "README.md" 9 | dynamic = ["version"] 10 | license = { text = "Apache-2.0" } 11 | authors = [{ name = "Gal Matijevic", email = "gmatijevic@aip.de" }] 12 | maintainers = [ 13 | { name = "Kirill Makan", email = "kmakan@aip.de" }, 14 | { name = "Simeon Reusch", email = "sreusch@aip.de" }, 15 | ] 16 | requires-python = ">=3.9" 17 | classifiers = [ 18 | "Development Status :: 5 - Production/Stable", 19 | "Intended Audience :: Developers", 20 | "Intended Audience :: Science/Research", 21 | "License :: OSI Approved :: Apache Software License", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3 :: Only", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | ] 28 | dependencies = ["antlr4-python3-runtime==4.13.1"] 29 | 30 | [project.optional-dependencies] 31 | test = ["pytest~=8.2.0", "PyYAML~=6.0"] 32 | dev = ["build", "ruff", "twine"] 33 | 34 | [project.urls] 35 | Repository = "https://github.com/aipescience/queryparser.git" 36 | Issues = "https://github.com/aipescience/queryparser/issues" 37 | Changelog = "https://github.com/aipescience/queryparser/blob/master/CHANGELOG.md" 38 | 39 | [tool.ruff] 40 | line-length = 120 41 | 42 | [tool.ruff.format] 43 | quote-style = "single" 44 | 45 | [tool.setuptools.dynamic] 46 | version = { attr = "queryparser.__version__" } 47 | 48 | [tool.setuptools] 49 | packages = [ 50 | "queryparser", 51 | "queryparser.adql", 52 | "queryparser.common", 53 | "queryparser.mysql", 54 | "queryparser.postgresql", 55 | "queryparser.exceptions", 56 | ] 57 | package-dir = { "" = "lib" } 58 | include-package-data = true 59 | -------------------------------------------------------------------------------- /src/queryparser/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.7.3' 2 | -------------------------------------------------------------------------------- /src/queryparser/adql/ADQLLexer.g4: -------------------------------------------------------------------------------- 1 | lexer grammar ADQLLexer; 2 | @ header { 3 | } 4 | 5 | fragment A_ : 'a' | 'A'; 6 | fragment B_ : 'b' | 'B'; 7 | fragment C_ : 'c' | 'C'; 8 | fragment D_ : 'd' | 'D'; 9 | fragment E_ : 'e' | 'E'; 10 | fragment F_ : 'f' | 'F'; 11 | fragment G_ : 'g' | 'G'; 12 | fragment H_ : 'h' | 'H'; 13 | fragment I_ : 'i' | 'I'; 14 | fragment J_ : 'j' | 'J'; 15 | fragment K_ : 'k' | 'K'; 16 | fragment L_ : 'l' | 'L'; 17 | fragment M_ : 'm' | 'M'; 18 | fragment N_ : 'n' | 'N'; 19 | fragment O_ : 'o' | 'O'; 20 | fragment P_ : 'p' | 'P'; 21 | fragment Q_ : 'q' | 'Q'; 22 | fragment R_ : 'r' | 'R'; 23 | fragment S_ : 's' | 'S'; 24 | fragment T_ : 't' | 'T'; 25 | fragment U_ : 'u' | 'U'; 26 | fragment V_ : 'v' | 'V'; 27 | fragment W_ : 'w' | 'W'; 28 | fragment X_ : 'x' | 'X'; 29 | fragment Y_ : 'y' | 'Y'; 30 | fragment Z_ : 'z' | 'Z'; 31 | 32 | 33 | ABS :A_ B_ S_ ; 34 | ACOS :A_ C_ O_ S_ ; 35 | AREA :A_ R_ E_ A_ ; 36 | ASIN :A_ S_ I_ N_ ; 37 | ATAN :A_ T_ A_ N_ ; 38 | ATAN2 :A_ T_ A_ N_ '2' ; 39 | BIT_AND :B_ I_ T_ '_' A_ N_ D_ ; 40 | BIT_NOT :B_ I_ T_ '_' N_ O_ T_ ; 41 | BIT_OR :B_ I_ T_ '_' O_ R_ ; 42 | BIT_XOR :B_ I_ T_ '_' X_ O_ R_ ; 43 | BOX :B_ O_ X_ ; 44 | CEILING :C_ E_ I_ L_ I_ N_ G_ ; 45 | //CENTROID :C_ E_ N_ T_ R_ O_ I_ D_ ; 46 | CIRCLE :C_ I_ R_ C_ L_ E_ ; 47 | CONTAINS :C_ O_ N_ T_ A_ I_ N_ S_ ; 48 | //COORD1 :C_ O_ O_ R_ D_ '1' ; 49 | //COORD2 :C_ O_ O_ R_ D_ '2' ; 50 | //COORDSYS :C_ O_ O_ R_ D_ S_ Y_ S_ ; 51 | COS :C_ O_ S_ ; 52 | COT :C_ O_ T_ ; 53 | DEGREES :D_ E_ G_ R_ E_ E_ S_ ; 54 | DISTANCE :D_ I_ S_ T_ A_ N_ C_ E_ ; 55 | EXP :E_ X_ P_ ; 56 | FLOOR :F_ L_ O_ O_ R_ ; 57 | ILIKE :I_ L_ I_ K_ E_ ; 58 | INTERSECTS :I_ N_ T_ E_ R_ S_ E_ C_ T_ S_ ; 59 | //IN_UNIT :I_ N_ '_' U_ N_ I_ T_ ; 60 | LOG :L_ O_ G_ ; 61 | LOG10 :L_ O_ G_ '10' ; 62 | MOD :M_ O_ D_ ; 63 | PI :P_ I_ ; 64 | POINT :P_ O_ I_ N_ T_ ; 65 | POLYGON :P_ O_ L_ Y_ G_ O_ N_ ; 66 | POWER :P_ O_ W_ E_ R_ ; 67 | RADIANS :R_ A_ D_ I_ A_ N_ S_ ; 68 | RAND :R_ A_ N_ D_ ; 69 | //REGION :R_ E_ G_ I_ O_ N_ ; 70 | ROUND :R_ O_ U_ N_ D_ ; 71 | SIN :S_ I_ N_ ; 72 | SQRT :S_ Q_ R_ T_ ; 73 | TAN :T_ A_ N_ ; 74 | TOP :T_ O_ P_ ; 75 | TRUNCATE :T_ R_ U_ N_ C_ A_ T_ E_ ; 76 | 77 | 78 | ABSOLUTE :A_ B_ S_ O_ L_ U_ T_ E_ ; 79 | ACTION :A_ C_ T_ I_ O_ N_ ; 80 | ADD :A_ D_ D_ ; 81 | ALL :A_ L_ L_ ; 82 | ALLOCATE :A_ L_ L_ O_ C_ A_ T_ E_ ; 83 | ALTER :A_ L_ T_ E_ R_ ; 84 | AND :A_ N_ D_ ; 85 | ANY :A_ N_ Y_ ; 86 | ARE :A_ R_ E_ ; 87 | AS :A_ S_ ; 88 | ASC :A_ S_ C_ ; 89 | ASSERTION :A_ S_ S_ E_ R_ T_ I_ O_ N_ ; 90 | AT :A_ T_ ; 91 | AUTHORIZATION :A_ U_ T_ H_ O_ R_ I_ Z_ A_ T_ I_ O_ N_ ; 92 | AVG :A_ V_ G_ ; 93 | BEGIN :B_ E_ G_ I_ N_ ; 94 | BETWEEN :B_ E_ T_ W_ E_ E_ N_ ; 95 | BIT :B_ I_ T_ ; 96 | BIT_LENGTH :B_ I_ T_ '_' L_ E_ N_ G_ T_ H_ ; 97 | BOTH :B_ O_ T_ H_ ; 98 | BY :B_ Y_ ; 99 | CASCADE :C_ A_ S_ C_ A_ D_ E_ ; 100 | CASCADED :C_ A_ S_ C_ A_ D_ E_ D_ ; 101 | CASE :C_ A_ S_ E_ ; 102 | CAST :C_ A_ S_ T_ ; 103 | CATALOG :C_ A_ T_ A_ L_ O_ G_ ; 104 | CHAR :C_ H_ A_ R_ ; 105 | CHARACTER :C_ H_ A_ R_ A_ C_ T_ E_ R_ ; 106 | CHAR_LENGTH :C_ H_ A_ R_ '_' L_ E_ N_ G_ T_ H_ ; 107 | CHARACTER_LENGTH :C_ H_ A_ R_ A_ C_ T_ E_ R_ '_' L_ E_ N_ G_ T_ H_ ; 108 | CHECK :C_ H_ E_ C_ K_ ; 109 | CLOSE :C_ L_ O_ S_ E_ ; 110 | COALESCE :C_ O_ A_ L_ E_ S_ C_ E_ ; 111 | COLLATE :C_ O_ L_ L_ A_ T_ E_ ; 112 | COLLATION :C_ O_ L_ L_ A_ T_ I_ O_ N_ ; 113 | COLUMN :C_ O_ L_ U_ M_ N_ ; 114 | COMMIT :C_ O_ M_ M_ I_ T_ ; 115 | CONNECT :C_ O_ N_ N_ E_ C_ T_ ; 116 | CONNECTION :C_ O_ N_ N_ E_ C_ T_ I_ O_ N_ ; 117 | CONSTRAINT :C_ O_ N_ S_ T_ R_ A_ I_ N_ T_ ; 118 | CONSTRAINTS :C_ O_ N_ S_ T_ R_ A_ I_ N_ T_ S_ ; 119 | CONTINUE :C_ O_ N_ T_ I_ N_ U_ E_ ; 120 | CONVERT :C_ O_ N_ V_ E_ R_ T_ ; 121 | CORRESPONDING :C_ O_ R_ R_ E_ S_ P_ O_ N_ D_ I_ N_ G_ ; 122 | COUNT :C_ O_ U_ N_ T_ ; 123 | CREATE :C_ R_ E_ A_ T_ E_ ; 124 | CROSS :C_ R_ O_ S_ S_ ; 125 | CURRENT :C_ U_ R_ R_ E_ N_ T_ ; 126 | CURRENT_DATE :C_ U_ R_ R_ E_ N_ T_ '_' D_ A_ T_ E_ ; 127 | CURRENT_TIME :C_ U_ R_ R_ E_ N_ T_ '_' T_ I_ M_ E_ ; 128 | CURRENT_TIMESTAMP :C_ U_ R_ R_ E_ N_ T_ '_' T_ I_ M_ E_ S_ T_ A_ M_ P_ ; 129 | CURRENT_USER :C_ U_ R_ R_ E_ N_ T_ '_' U_ S_ E_ R_ ; 130 | CURSOR :C_ U_ R_ S_ O_ R_ ; 131 | DATE :D_ A_ T_ E_ ; 132 | DAY :D_ A_ Y_ ; 133 | DEALLOCATE :D_ E_ A_ L_ L_ O_ C_ A_ T_ E_ ; 134 | DECIMAL :D_ E_ C_ I_ M_ A_ L_ ; 135 | DECLARE :D_ E_ C_ L_ A_ R_ E_ ; 136 | DEFAULT :D_ E_ F_ A_ U_ L_ T_ ; 137 | DEFERRABLE :D_ E_ F_ E_ R_ R_ A_ B_ L_ E_ ; 138 | DEFERRED :D_ E_ F_ E_ R_ R_ E_ D_ ; 139 | DELETE :D_ E_ L_ E_ T_ E_ ; 140 | DESC :D_ E_ S_ C_ ; 141 | DESCRIBE :D_ E_ S_ C_ R_ I_ B_ E_ ; 142 | DESCRIPTOR :D_ E_ S_ C_ R_ I_ P_ T_ O_ R_ ; 143 | DIAGNOSTICS :D_ I_ A_ G_ N_ O_ S_ T_ I_ C_ S_ ; 144 | DISCONNECT :D_ I_ S_ C_ O_ N_ N_ E_ C_ T_ ; 145 | DISTINCT :D_ I_ S_ T_ I_ N_ C_ T_ ; 146 | DOMAIN :D_ O_ M_ A_ I_ N_ ; 147 | DOUBLE :D_ O_ U_ B_ L_ E_ ; 148 | DROP :D_ R_ O_ P_ ; 149 | E_SYM :E_ ; 150 | ELSE :E_ L_ S_ E_ ; 151 | END :E_ N_ D_ ; 152 | ENDEXEC_SYM :E_ N_ D_ '-' E_ X_ E_ C_ ; 153 | ESCAPE :E_ S_ C_ A_ P_ E_ ; 154 | EXCEPT :E_ X_ C_ E_ P_ T_ ; 155 | EXCEPTION :E_ X_ C_ E_ P_ T_ I_ O_ N_ ; 156 | EXEC :E_ X_ E_ C_ ; 157 | EXECUTE :E_ X_ E_ C_ U_ T_ E_ ; 158 | EXISTS :E_ X_ I_ S_ T_ S_ ; 159 | EXTERNAL :E_ X_ T_ E_ R_ N_ A_ L_ ; 160 | EXTRACT :E_ X_ T_ R_ A_ C_ T_ ; 161 | FALSE :F_ A_ L_ S_ E_ ; 162 | FETCH :F_ E_ T_ C_ H_ ; 163 | FIRST :F_ I_ R_ S_ T_ ; 164 | FLOAT :F_ L_ O_ A_ T_ ; 165 | FOR :F_ O_ R_ ; 166 | FOREIGN :F_ O_ R_ E_ I_ G_ N_ ; 167 | FOUND :F_ O_ U_ N_ D_ ; 168 | FROM :F_ R_ O_ M_ ; 169 | FULL :F_ U_ L_ L_ ; 170 | GET :G_ E_ T_ ; 171 | GLOBAL :G_ L_ O_ B_ A_ L_ ; 172 | GO :G_ O_ ; 173 | GOTO :G_ O_ T_ O_ ; 174 | GRANT :G_ R_ A_ N_ T_ ; 175 | GROUP :G_ R_ O_ U_ P_ ; 176 | HAVING :H_ A_ V_ I_ N_ G_ ; 177 | HOUR :H_ O_ U_ R_ ; 178 | IDENTITY :I_ D_ E_ N_ T_ I_ T_ Y_ ; 179 | IMMEDIATE :I_ M_ M_ E_ D_ I_ A_ T_ E_ ; 180 | IN :I_ N_ ; 181 | INDICATOR :I_ N_ D_ I_ C_ A_ T_ O_ R_ ; 182 | INITIALLY :I_ N_ I_ T_ I_ A_ L_ L_ Y_ ; 183 | INNER :I_ N_ N_ E_ R_ ; 184 | INPUT :I_ N_ P_ U_ T_ ; 185 | INSENSITIVE :I_ N_ S_ E_ N_ S_ I_ T_ I_ V_ E_ ; 186 | INSERT :I_ N_ S_ E_ R_ T_ ; 187 | INT_SYM :I_ N_ T_ ; 188 | INTEGER :I_ N_ T_ E_ G_ E_ R_ ; 189 | INTERSECT :I_ N_ T_ E_ R_ S_ E_ C_ T_ ; 190 | INTERVAL :I_ N_ T_ E_ R_ V_ A_ L_ ; 191 | INTO :I_ N_ T_ O_ ; 192 | IS :I_ S_ ; 193 | ISOLATION :I_ S_ O_ L_ A_ T_ I_ O_ N_ ; 194 | JOIN :J_ O_ I_ N_ ; 195 | KEY :K_ E_ Y_ ; 196 | LANGUAGE :L_ A_ N_ G_ U_ A_ G_ E_ ; 197 | LAST :L_ A_ S_ T_ ; 198 | LEADING :L_ E_ A_ D_ I_ N_ G_ ; 199 | LEFT :L_ E_ F_ T_ ; 200 | LEVEL :L_ E_ V_ E_ L_ ; 201 | LIKE :L_ I_ K_ E_ ; 202 | LOCAL :L_ O_ C_ A_ L_ ; 203 | LOWER :L_ O_ W_ E_ R_ ; 204 | MATCH :M_ A_ T_ C_ H_ ; 205 | MAX :M_ A_ X_ ; 206 | MIN :M_ I_ N_ ; 207 | MINUTE :M_ I_ N_ U_ T_ E_ ; 208 | MODULE :M_ O_ D_ U_ L_ E_ ; 209 | MONTH :M_ O_ N_ T_ H_ ; 210 | NAMES :N_ A_ M_ E_ S_ ; 211 | NATIONAL :N_ A_ T_ I_ O_ N_ A_ L_ ; 212 | NATURAL :N_ A_ T_ U_ R_ A_ L_ ; 213 | NCHAR :N_ C_ H_ A_ R_ ; 214 | NEXT :N_ E_ X_ T_ ; 215 | NO :N_ O_ ; 216 | NOT :N_ O_ T_ ; 217 | NULL :N_ U_ L_ L_ ; 218 | NULLIF :N_ U_ L_ L_ I_ F_ ; 219 | NUMERIC :N_ U_ M_ E_ R_ I_ C_ ; 220 | OCTET_LENGTH :O_ C_ T_ E_ T_ '_' L_ E_ N_ G_ T_ H_ ; 221 | OF :O_ F_ ; 222 | OFFSET :O_ F_ F_ S_ E_ T_; 223 | ON :O_ N_ ; 224 | ONLY :O_ N_ L_ Y_ ; 225 | OPEN :O_ P_ E_ N_ ; 226 | OPTION :O_ P_ T_ I_ O_ N_ ; 227 | OR :O_ R_ ; 228 | ORDER :O_ R_ D_ E_ R_ ; 229 | OUTER :O_ U_ T_ E_ R_ ; 230 | OUTPUT :O_ U_ T_ P_ U_ T_ ; 231 | OVERLAPS :O_ V_ E_ R_ L_ A_ P_ S_ ; 232 | PAD :P_ A_ D_ ; 233 | PARTIAL :P_ A_ R_ T_ I_ A_ L_ ; 234 | POSITION :P_ O_ S_ I_ T_ I_ O_ N_ ; 235 | PRECISION :P_ R_ E_ C_ I_ S_ I_ O_ N_ ; 236 | PREPARE :P_ R_ E_ P_ A_ R_ E_ ; 237 | PRESERVE :P_ R_ E_ S_ E_ R_ V_ E_ ; 238 | PRIMARY :P_ R_ I_ M_ A_ R_ Y_ ; 239 | PRIOR :P_ R_ I_ O_ R_ ; 240 | PRIVILEGES :P_ R_ I_ V_ I_ L_ E_ G_ E_ S_ ; 241 | PROCEDURE :P_ R_ O_ C_ E_ D_ U_ R_ E_ ; 242 | //PUBLIC :P_ U_ B_ L_ I_ C_ ; 243 | READ :R_ E_ A_ D_ ; 244 | REAL_SYM :R_ E_ A_ L_ ; 245 | REFERENCES :R_ E_ F_ E_ R_ E_ N_ C_ E_ S_ ; 246 | RELATIVE :R_ E_ L_ A_ T_ I_ V_ E_ ; 247 | RESTRICT :R_ E_ S_ T_ R_ I_ C_ T_ ; 248 | REVOKE :R_ E_ V_ O_ K_ E_ ; 249 | RIGHT :R_ I_ G_ H_ T_ ; 250 | ROLLBACK :R_ O_ L_ L_ B_ A_ C_ K_ ; 251 | ROWS :R_ O_ W_ S_ ; 252 | SCHEMA :S_ C_ H_ E_ M_ A_ ; 253 | SCROLL :S_ C_ R_ O_ L_ L_ ; 254 | SECOND :S_ E_ C_ O_ N_ D_ ; 255 | SECTION :S_ E_ C_ T_ I_ O_ N_ ; 256 | SELECT :S_ E_ L_ E_ C_ T_ ; 257 | SESSION :S_ E_ S_ S_ I_ O_ N_ ; 258 | SESSION_USER :S_ E_ S_ S_ I_ O_ N_ '_' U_ S_ E_ R_ ; 259 | SET :S_ E_ T_ ; 260 | SIZE :S_ I_ Z_ E_ ; 261 | SMALLINT :S_ M_ A_ L_ L_ I_ N_ T_ ; 262 | SOME :S_ O_ M_ E_ ; 263 | SPACE :S_ P_ A_ C_ E_ ; 264 | SQL :S_ Q_ L_ ; 265 | SQLCODE :S_ Q_ L_ C_ O_ D_ E_ ; 266 | SQLERROR :S_ Q_ L_ E_ R_ R_ O_ R_ ; 267 | SQLSTATE :S_ Q_ L_ S_ T_ A_ T_ E_ ; 268 | SUBSTRING :S_ U_ B_ S_ T_ R_ I_ N_ G_ ; 269 | SUM :S_ U_ M_ ; 270 | SYSTEM_USER :S_ Y_ S_ T_ E_ M_ '_' U_ S_ E_ R_ ; 271 | TABLE :T_ A_ B_ L_ E_ ; 272 | TEMPORARY :T_ E_ M_ P_ O_ R_ A_ R_ Y_ ; 273 | THEN :T_ H_ E_ N_ ; 274 | TIME :T_ I_ M_ E_ ; 275 | TIMESTAMP :T_ I_ M_ E_ S_ T_ A_ M_ P_ ; 276 | TIMEZONE_HOUR :T_ I_ M_ E_ Z_ O_ N_ E_ '_' H_ O_ U_ R_ ; 277 | TIMEZONE_MINUTE :T_ I_ M_ E_ Z_ O_ N_ E_ '_' M_ I_ N_ U_ T_ E_ ; 278 | TO :T_ O_ ; 279 | TRAILING :T_ R_ A_ I_ L_ I_ N_ G_ ; 280 | TRANSACTION :T_ R_ A_ N_ S_ A_ C_ T_ I_ O_ N_ ; 281 | TRANSLATE :T_ R_ A_ N_ S_ L_ A_ T_ E_ ; 282 | TRANSLATION :T_ R_ A_ N_ S_ L_ A_ T_ I_ O_ N_ ; 283 | TRIM :T_ R_ I_ M_ ; 284 | TRUE :T_ R_ U_ E_ ; 285 | UNION :U_ N_ I_ O_ N_ ; 286 | UNIQUE :U_ N_ I_ Q_ U_ E_ ; 287 | UNKNOWN :U_ N_ K_ N_ O_ W_ N_ ; 288 | UPDATE :U_ P_ D_ A_ T_ E_ ; 289 | UPPER :U_ P_ P_ E_ R_ ; 290 | USAGE :U_ S_ A_ G_ E_ ; 291 | USER :U_ S_ E_ R_ ; 292 | USING :U_ S_ I_ N_ G_ ; 293 | VALUE :V_ A_ L_ U_ E_ ; 294 | VALUES :V_ A_ L_ U_ E_ S_ ; 295 | VARCHAR :V_ A_ R_ C_ H_ A_ R_ ; 296 | VARYING :V_ A_ R_ Y_ I_ N_ G_ ; 297 | VIEW :V_ I_ E_ W_ ; 298 | WHEN :W_ H_ E_ N_ ; 299 | WHENEVER :W_ H_ E_ N_ E_ V_ E_ R_ ; 300 | WHERE :W_ H_ E_ R_ E_ ; 301 | WITH :W_ I_ T_ H_ ; 302 | WORK :W_ O_ R_ K_ ; 303 | WRITE :W_ R_ I_ T_ E_ ; 304 | YEAR :Y_ E_ A_ R_ ; 305 | ZONE :Z_ O_ N_ E_ ; 306 | 307 | 308 | INT : [0-9]+; 309 | EXPONENT: INT ; 310 | REAL : ( INT DOT INT | INT DOT | DOT INT ) 311 | ( ('E'|'e') ( PLUS | MINUS )? INT )? 312 | ; 313 | 314 | fragment HEX_DIGIT_FRAGMENT: ( 'a'..'f' | 'A'..'F' | '0'..'9' ) ; 315 | HEX_DIGIT: 316 | ( '0x' (HEX_DIGIT_FRAGMENT)+ ) 317 | ; 318 | 319 | CSL: SQ ('\u0020' .. '\u0026' | '\u0028' .. '\u007f')+ SQ; 320 | 321 | ID: 322 | (( 'A'..'Z' | 'a'..'z' | '_' | '%' | '$') ( 'A'..'Z' | 'a'..'z' | '_' | '$' | '%' | '0'..'9' )*) | 323 | //(DQ NQC+ DQ) 324 | (DQ ('\u0020' .. '\u0021' | '\u0023' .. '\u007f')+ DQ) 325 | ; 326 | 327 | 328 | //NDQC: 'A'..'Z' | 'a'..'z' | '_' | '0'..'9' ; 329 | 330 | AMPERSAND : '&' ; 331 | TILDE : '~' ; 332 | CIRCUMFLEX : '^' ; 333 | LPAREN : '(' ; 334 | RPAREN : ')' ; 335 | ASTERISK : '*' ; 336 | PLUS : '+' ; 337 | COMMA : ',' ; 338 | MINUS : '-' ; 339 | DOT : '.' ; 340 | COLON : ':' ; 341 | SEMI : ';' ; 342 | LTH : '<' ; 343 | EQ : '=' ; 344 | GTH : '>' ; 345 | QUESTION : '?' ; 346 | VERTBAR : '|' ; 347 | UNDERSCORE : '_' ; 348 | SOLIDUS : '/' ; 349 | CONCAT : '||' ; 350 | LEET : '<=' ; 351 | GRET : '>=' ; 352 | NOT_EQ : '<>' | '!=' ; 353 | DQ : '"' ; 354 | SQ : '\'' ; 355 | MOD_SYM : '%' ; 356 | 357 | DQ_SYM : DQ DQ ; 358 | 359 | // all unicode whitespace characters 360 | WS 361 | : ( ' ' | '\t' | '\n' | '\r' | '\u000b' | '\u000c' | 362 | '\u0085' | '\u00a0' | '\u1680' | '\u2000' | '\u2001' | '\u2002' | '\u2003' | 363 | '\u2004' | '\u2005' | '\u2006' | '\u2007' | '\u2008' | '\u2009' | '\u200a' | '\u2028' | 364 | '\u2029' | '\u202f' | '\u205f' | '\u3000' )+ -> channel(HIDDEN) 365 | ; 366 | 367 | COMMENT: '--' ~( '\r' | '\n' )* -> skip ; 368 | -------------------------------------------------------------------------------- /src/queryparser/adql/ADQLParser.g4: -------------------------------------------------------------------------------- 1 | parser grammar ADQLParser; 2 | 3 | options 4 | { tokenVocab = ADQLLexer; } 5 | 6 | approximate_numeric_literal: REAL ; 7 | area: AREA LPAREN geometry_value_expression RPAREN ; 8 | as_clause: ( AS )? column_name ; 9 | between_predicate: value_expression ( NOT )? BETWEEN value_expression AND value_expression ; 10 | bitwise_and: AMPERSAND ; 11 | bitwise_not: TILDE ; 12 | bitwise_or: VERTBAR ; 13 | bitwise_xor: CIRCUMFLEX ; 14 | boolean_factor: ( NOT )? boolean_primary ; 15 | //boolean_function: // This is empty in the document! 16 | boolean_literal: TRUE | FALSE ; 17 | boolean_primary: LPAREN search_condition RPAREN | predicate | boolean_value_expression ; 18 | boolean_term: boolean_factor | boolean_term AND boolean_factor ; 19 | boolean_value_expression: boolean_literal | user_defined_function ; //| boolean_function ; 20 | box: 21 | BOX 22 | LPAREN 23 | ( coord_sys COMMA )? 24 | coordinates COMMA numeric_value_expression COMMA numeric_value_expression 25 | RPAREN ; 26 | catalog_name: ID ; 27 | // centroid: CENTROID LPAREN geometry_value_expression RPAREN ; 28 | // character_representation: nonquote_character ;// | SQ SQ ; 29 | char_function: LOWER LPAREN character_string_literal RPAREN ; 30 | character_string_literal: CSL ; //SQ ( SL )* SQ ; //SQ ( character_representation )* SQ ; 31 | character_value_expression: 32 | character_value_expression concatenation_operator ( value_expression_primary | string_value_function ) 33 | | value_expression_primary 34 | | string_value_function ; 35 | circle: CIRCLE LPAREN (( coord_sys COMMA )? circle_center ) COMMA radius RPAREN ; 36 | circle_center: coordinates | coord_value ; 37 | column_name: identifier ; 38 | column_name_list: column_name ( COMMA column_name )* ; 39 | column_reference: ( qualifier DOT )? column_name ; 40 | comp_op: EQ | NOT_EQ | LTH | GTH | GRET | LEET ; 41 | comparison_predicate: value_expression comp_op value_expression ; 42 | concatenation_operator: CONCAT ; 43 | contains: CONTAINS LPAREN geometry_value_expression COMMA geometry_value_expression RPAREN ; 44 | contains_predicate: INT EQ contains | contains EQ INT; 45 | coord_sys: string_value_expression ; 46 | coord_value: point_value | column_reference ; 47 | //coord1: COORD1 LPAREN coord_value RPAREN ; 48 | //coord2: COORD2 LPAREN coord_value RPAREN ; 49 | coordinate1: numeric_value_expression ; 50 | coordinate2: numeric_value_expression ; 51 | coordinates: coordinate1 COMMA coordinate2 ; 52 | correlation_name: identifier ; 53 | correlation_specification: ( AS )? correlation_name ; 54 | //default_function_prefix: // this is empty in the document! 55 | delimited_identifier: DQ ID DQ ; 56 | derived_column: value_expression ( as_clause )? ; 57 | derived_table: table_subquery ; 58 | distance: 59 | DISTANCE 60 | LPAREN ( 61 | ( coord_value COMMA coord_value ) | 62 | ( numeric_value_expression COMMA numeric_value_expression COMMA 63 | numeric_value_expression COMMA numeric_value_expression ) 64 | ) RPAREN ; 65 | //double_quote_symbol: DQ DQ ; 66 | exact_numeric_literal: unsigned_decimal ( DOT ( unsigned_decimal )? )? | DOT unsigned_decimal; 67 | exists_predicate: EXISTS table_subquery ; 68 | // extract_coordsys: COORDSYS LPAREN geometry_value_expression RPAREN ; 69 | factor: ( sign )? numeric_primary ; 70 | from_clause: FROM table_reference ( COMMA table_reference )* ; 71 | general_literal: character_string_literal ; 72 | general_set_function: set_function_type LPAREN ( set_quantifier )? value_expression RPAREN ; 73 | geometry_value_expression: box | circle | point | polygon | user_defined_function ; //| centroid | region 74 | group_by_clause: GROUP BY grouping_column_reference_list ; 75 | grouping_column_reference: column_reference ; 76 | grouping_column_reference_list: grouping_column_reference ( COMMA grouping_column_reference )* ; 77 | having_clause: HAVING search_condition ; 78 | identifier: regular_identifier | delimited_identifier ; 79 | in_predicate: value_expression ( NOT )? IN in_predicate_value ; 80 | in_predicate_value: table_subquery | LPAREN in_value_list RPAREN ; 81 | in_value_list: value_expression ( COMMA value_expression )* ; 82 | intersects: INTERSECTS LPAREN geometry_value_expression COMMA geometry_value_expression RPAREN ; 83 | intersects_predicate: INT EQ intersects | intersects EQ INT ; 84 | join_column_list: column_name_list ; 85 | join_condition: ON search_condition ; 86 | join_specification: join_condition | named_columns_join ; 87 | join_type: INNER | outer_join_type ( OUTER )? ; 88 | joined_table: 89 | table_reference ( NATURAL )? ( join_type )? JOIN table_reference ( join_specification )? 90 | | LPAREN joined_table RPAREN ; 91 | like_predicate: match_value ( NOT )? LIKE pattern | match_value ( NOT )? ILIKE pattern ; 92 | match_value: character_value_expression ; 93 | math_function: 94 | ABS LPAREN numeric_value_expression RPAREN 95 | | CEILING LPAREN numeric_value_expression RPAREN 96 | | DEGREES LPAREN numeric_value_expression RPAREN 97 | | EXP LPAREN numeric_value_expression RPAREN 98 | | FLOOR LPAREN numeric_value_expression RPAREN 99 | | LOG LPAREN numeric_value_expression RPAREN 100 | | LOG10 LPAREN numeric_value_expression RPAREN 101 | | MOD LPAREN numeric_value_expression COMMA numeric_value_expression RPAREN 102 | | PI LPAREN RPAREN 103 | | POWER LPAREN numeric_value_expression COMMA numeric_value_expression RPAREN 104 | | RADIANS LPAREN numeric_value_expression RPAREN 105 | | RAND LPAREN ( unsigned_decimal )? RPAREN 106 | | ROUND LPAREN numeric_value_expression ( COMMA signed_integer )? RPAREN 107 | | SQRT LPAREN numeric_value_expression RPAREN 108 | | TRUNCATE LPAREN numeric_value_expression ( COMMA signed_integer )? RPAREN ; 109 | named_columns_join: USING LPAREN join_column_list RPAREN ; 110 | non_join_query_expression: non_join_query_term | query_expression UNION ( ALL )? query_term 111 | | query_expression EXCEPT ( ALL )? query_term ; 112 | non_join_query_primary: query_specification | LPAREN non_join_query_expression RPAREN ; 113 | non_join_query_term: non_join_query_primary | query_term INTERSECT ( ALL )? query_expression ; 114 | non_predicate_geometry_function:area | distance ; //coord1 | coord2 | 115 | //nondoublequote_character: NDQC ; 116 | //nonquote_character: NQC ; 117 | null_predicate: column_reference IS ( NOT )? NULL ; 118 | numeric_geometry_function: predicate_geometry_function | non_predicate_geometry_function ; 119 | numeric_primary: ( sign )? value_expression_primary | numeric_value_function ; 120 | numeric_value_expression: 121 | term 122 | | bitwise_not numeric_value_expression 123 | | numeric_value_expression bitwise_and numeric_value_expression 124 | | numeric_value_expression bitwise_or numeric_value_expression 125 | | numeric_value_expression bitwise_xor numeric_value_expression 126 | | numeric_value_expression PLUS term 127 | | numeric_value_expression MINUS term ; 128 | numeric_value_function: trig_function | math_function | numeric_geometry_function | user_defined_function ; 129 | offset_clause: OFFSET unsigned_decimal ; 130 | order_by_clause: ORDER BY sort_specification_list ; 131 | ordering_specification: ASC | DESC ; 132 | outer_join_type: LEFT | RIGHT | FULL ; 133 | pattern: character_value_expression ; 134 | point: POINT LPAREN ( coord_sys COMMA )? coordinates RPAREN ; 135 | point_value: point | user_defined_function ;//| centroid 136 | polygon: POLYGON LPAREN 137 | ( coord_sys COMMA )? 138 | coordinates COMMA 139 | coordinates ( COMMA coordinates )+ RPAREN ; 140 | predicate: 141 | contains_predicate 142 | | intersects_predicate 143 | | comparison_predicate 144 | | between_predicate 145 | | in_predicate 146 | | like_predicate 147 | | null_predicate 148 | | exists_predicate ; 149 | predicate_geometry_function: contains | intersects ; 150 | qualifier: column_name | table_name | correlation_name ; 151 | query_expression: 152 | non_join_query_term 153 | | query_expression UNION ( ALL )? query_term 154 | | query_expression EXCEPT ( ALL )? query_term 155 | | joined_table ; 156 | query_name: ID ; 157 | query: query_expression SEMI; 158 | query_specification: ( WITH with_query )? select_query ; 159 | query_term: non_join_query_primary | query_term INTERSECT ( ALL )? query_expression | joined_table ; 160 | radius: numeric_value_expression ; 161 | //region: REGION LPAREN string_value_expression RPAREN ; 162 | regular_identifier: ID ; 163 | schema_name: ID ; //( catalog_name DOT )? unqualified_schema_name ; 164 | search_condition: boolean_term | search_condition OR boolean_term ; 165 | select_list: ( select_sublist ( COMMA select_sublist )* ) | ( ASTERISK ( COMMA select_sublist ( COMMA select_sublist )* )? ) ; 166 | select_query: SELECT ( set_quantifier )? ( set_limit )? select_list table_expression ; 167 | select_sublist: derived_column | qualifier DOT ASTERISK ; 168 | set_function_specification: COUNT LPAREN ASTERISK RPAREN | general_set_function ; 169 | set_function_type: AVG | MAX | MIN | SUM | COUNT ; 170 | set_limit: TOP unsigned_decimal ; 171 | set_quantifier: DISTINCT | ALL ; 172 | sign: PLUS | MINUS ; 173 | signed_integer: ( sign )? unsigned_decimal ; 174 | sort_key: value_expression| column_reference | unsigned_decimal ; 175 | sort_specification: sort_key (ordering_specification )? ; 176 | sort_specification_list: sort_specification ( COMMA sort_specification )* ; 177 | //string_geometry_function: extract_coordsys ; 178 | string_value_expression: character_value_expression ; 179 | string_value_function: user_defined_function | char_function; //string_geometry_function | 180 | subquery: LPAREN query_expression RPAREN ; 181 | table_expression: 182 | from_clause 183 | ( where_clause )? 184 | ( group_by_clause )? 185 | ( having_clause )? 186 | ( order_by_clause )? 187 | ( offset_clause )? ; 188 | table_name: ( schema_name DOT )? identifier ; 189 | table_reference: 190 | table_name ( correlation_specification )? 191 | | derived_table correlation_specification 192 | | table_reference ( NATURAL )? ( join_type )? JOIN table_reference ( join_specification )? 193 | | LPAREN joined_table RPAREN ; 194 | table_subquery: subquery ; 195 | term: factor | term ASTERISK factor | term SOLIDUS factor | term MOD_SYM factor; 196 | trig_function: ACOS LPAREN numeric_value_expression RPAREN 197 | | ACOS LPAREN numeric_value_expression RPAREN 198 | | ASIN LPAREN numeric_value_expression RPAREN 199 | | ATAN LPAREN numeric_value_expression RPAREN 200 | | ATAN2 LPAREN numeric_value_expression COMMA numeric_value_expression RPAREN 201 | | COS LPAREN numeric_value_expression RPAREN 202 | | COT LPAREN numeric_value_expression RPAREN 203 | | SIN LPAREN numeric_value_expression RPAREN 204 | | TAN LPAREN numeric_value_expression RPAREN ; 205 | unqualified_schema_name: ID ; 206 | unsigned_decimal: INT ; 207 | unsigned_hexadecimal: HEX_DIGIT ; 208 | unsigned_literal: unsigned_numeric_literal | general_literal ; 209 | unsigned_numeric_literal: exact_numeric_literal | approximate_numeric_literal | unsigned_hexadecimal ; 210 | unsigned_value_specification: unsigned_literal ; 211 | user_defined_function: 212 | user_defined_function_name 213 | LPAREN 214 | ( user_defined_function_param ( COMMA user_defined_function_param )* )? 215 | RPAREN ; 216 | user_defined_function_name: regular_identifier ; //( default_function_prefix )? regular_identifier ; 217 | user_defined_function_param: value_expression ; 218 | value_expression: 219 | numeric_value_expression 220 | | string_value_expression 221 | | boolean_value_expression 222 | | geometry_value_expression ; 223 | value_expression_primary: 224 | unsigned_value_specification 225 | | column_reference 226 | | set_function_specification 227 | | LPAREN value_expression RPAREN ; 228 | where_clause: WHERE search_condition ; 229 | with_query: query_name ( LPAREN column_name ( COMMA column_name )* RPAREN )? AS LPAREN ( query_specification )? RPAREN ; 230 | -------------------------------------------------------------------------------- /src/queryparser/adql/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .adqltranslator import ADQLQueryTranslator 4 | 5 | __all__ = ["ADQLQueryTranslator"] 6 | -------------------------------------------------------------------------------- /src/queryparser/adql/adqltranslator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | import antlr4 6 | from antlr4.error.ErrorListener import ErrorListener 7 | 8 | from .ADQLLexer import ADQLLexer 9 | from .ADQLParser import ADQLParser 10 | from .ADQLParserVisitor import ADQLParserVisitor 11 | from .ADQLParserListener import ADQLParserListener 12 | 13 | from ..exceptions import QueryError, QuerySyntaxError 14 | 15 | 16 | # Function names need to be trecognized because there whitespace 17 | # between the name and left parenthesis is not allowed and needs to be 18 | # deleted. 19 | adql_function_names = ('ABS', 'ACOS', 'ASIN', 'ATAN', 'ATAN2', 'CEILING', 20 | 'COS', 'DEGREES', 'EXP', 'FLOOR', 'LOG', 'LOG10', 21 | 'MOD', 'PI', 'POWER', 'RADIANS', 'RAND', 'SIN', 22 | 'SQRT', 'TAN', 'TRUNCATE') 23 | 24 | 25 | def _removeFirstChild(ctx): 26 | if ctx.children is not None: 27 | del ctx.children[0] 28 | 29 | 30 | def _remove_children(ctx, reverse=False): 31 | for _ in range(ctx.getChildCount() - 1): 32 | if reverse: 33 | _removeFirstChild(ctx) 34 | else: 35 | ctx.removeLastChild() 36 | 37 | 38 | def _convert_values(ctx, cidx, output_sql): 39 | """ 40 | Values inside the ADQL functions can be floats, expressions, or 41 | strings. Strings need to be treated differently because 42 | mysql-sphere syntax differs slightly if we pass a column name 43 | instead of a value. 44 | 45 | :param ctx: 46 | antlr context. 47 | 48 | :param cidx: 49 | Context child index. 50 | 51 | """ 52 | vals = [] 53 | for i in ctx.children[cidx].getText().split(','): 54 | try: 55 | val = float(i) 56 | except ValueError: 57 | try: 58 | val = float(eval(i)) 59 | except (AttributeError, ValueError, NameError, SyntaxError): 60 | if output_sql == 'mysql': 61 | val = '.'.join('`{0}`'.format(v.rstrip("'"). 62 | lstrip("'"). 63 | rstrip('"').lstrip('"')) 64 | for v in i.split('.')) 65 | elif output_sql == 'postgresql': 66 | val = '.'.join('{0}'.format(v) 67 | for v in i.split('.')) 68 | vals.append(val) 69 | return vals 70 | 71 | def _process_regular_identifier(ctx_text, sql_output): 72 | if sql_output == 'mysql': 73 | ri = ctx_text.rstrip("'").lstrip("'").rstrip('"').lstrip('"') 74 | return '`' + ri + '`' 75 | elif sql_output == 'postgresql': 76 | return ctx_text 77 | else: 78 | return ri 79 | 80 | def _get_ancestor_class_node(ctx, ancestor_class, depth=1): 81 | """ Returns the ancestor node at 'depth' level above the current node if 82 | the node is of type 'ancestor_class'. Otherwise, returns None 83 | """ 84 | if not hasattr(ctx, 'parentCtx'): 85 | return None 86 | 87 | if depth > 1: 88 | return _get_ancestor_class_node(ctx.parentCtx, ancestor_class, depth-1) 89 | elif depth == 1: 90 | if isinstance(ctx.parentCtx, ancestor_class): 91 | return ctx.parentCtx 92 | return None 93 | 94 | 95 | class SyntaxErrorListener(ErrorListener): 96 | def __init__(self): 97 | super(SyntaxErrorListener, self).__init__() 98 | self.syntax_errors = [] 99 | 100 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 101 | self.syntax_errors.append((line, column, offendingSymbol.text)) 102 | 103 | 104 | class ADQLGeometryTranslationVisitor(ADQLParserVisitor): 105 | """ 106 | 1) Find the rule we need to translate. Point, for example. 107 | 2) After processing it, get rid of all off its children except the first 108 | one. This still keeps a token in the stream so it can be accessed later 109 | by other visitors or listeners. Otherwise is impossible (or hard?) to 110 | stick a new token in the stream so the walking works flawlessly. 111 | 3) Hash the replacement string in the contexts dictionary. Use the context 112 | as a hash. This way we can return the hashed string instead the 113 | token so we effectively translate the rule. 114 | 115 | :param conunits: 116 | What should we be converting the units to. If no conversion is 117 | necessary, just pass an empty string. 118 | 119 | """ 120 | def __init__(self, output_sql, conunits="RADIANS"): 121 | self.contexts = {} 122 | self.output_sql = output_sql 123 | self.conunits = conunits 124 | 125 | def _convert_values(self, ctx, cidx): 126 | """ 127 | Values inside the ADQL functions can be floats, expressions, or 128 | strings. Strings need to be treated differently because 129 | mysql-sphere syntax differs slightly if we pass a column name 130 | instead of a value. 131 | 132 | :param ctx: 133 | antlr context. 134 | 135 | :param cidx: 136 | Context child index. 137 | 138 | """ 139 | vals = [] 140 | for i in ctx.children[cidx].getText().split(','): 141 | try: 142 | val = float(i) 143 | except ValueError: 144 | try: 145 | val = float(eval(i)) 146 | except (AttributeError, ValueError, NameError, SyntaxError): 147 | if self.output_sql == 'mysql': 148 | val = '.'.join('`{0}`'.format(v.rstrip("'"). 149 | lstrip("'"). 150 | rstrip('"').lstrip('"')) 151 | for v in i.split('.')) 152 | elif self.output_sql == 'postgresql': 153 | val = '.'.join('{0}'.format(v) 154 | for v in i.split('.')) 155 | vals.append(val) 156 | return vals 157 | 158 | def visitRegular_identifier(self, ctx): 159 | if isinstance(ctx.parentCtx, 160 | ADQLParser.User_defined_function_nameContext): 161 | return 162 | ri = _process_regular_identifier(ctx.getText(), self.output_sql) 163 | self.contexts[ctx] = ri 164 | 165 | def visitSchema_name(self, ctx): 166 | ri = _process_regular_identifier(ctx.getText(), self.output_sql) 167 | self.contexts[ctx] = ri 168 | 169 | def visitAs_clause(self, ctx): 170 | # We need to visit the AS clause to avoid aliases being treated same 171 | # as regular identifiers and backticked. 172 | try: 173 | ri = _process_regular_identifier(ctx.children[1].getText(), 174 | self.output_sql) 175 | alidx = 1 176 | except IndexError: 177 | ri = _process_regular_identifier(ctx.children[0].getText(), 178 | self.output_sql) 179 | alidx = 0 180 | 181 | if ctx.children[alidx].getText()[0] != '"': 182 | if self.output_sql == 'mysql': 183 | ri = ri.replace('`', '') 184 | _remove_children(ctx) 185 | self.contexts[ctx] = 'AS ' + ri 186 | 187 | def visitPoint(self, ctx): 188 | coords = [] 189 | if len(ctx.children) > 4: 190 | for j in (2, 4): 191 | coords.extend(self._convert_values(ctx, j)) 192 | else: 193 | coords.extend(self._convert_values(ctx, 2)) 194 | 195 | if len(coords) == 3: 196 | coords = coords[1:] 197 | 198 | if self.output_sql == 'mysql': 199 | ctx_text = "spoint( %s(%s), %s(%s) )" % (self.conunits, coords[0], 200 | self.conunits, coords[1]) 201 | elif self.output_sql == 'postgresql': 202 | ctx_text = "spoint( %s(%s), %s(%s) )" % (self.conunits, coords[0], 203 | self.conunits, coords[1]) 204 | derived_column = _get_ancestor_class_node(ctx, ADQLParser.Derived_columnContext, depth=3) 205 | if derived_column is not None: 206 | ctx_text = f"spoint_to_array_deg({ctx_text})" 207 | if not (any([isinstance(child, ADQLParser.As_clauseContext) for child in derived_column.children])): 208 | ctx_text = f"{ctx_text} AS adql_point" 209 | else: 210 | ctx_text = '' 211 | 212 | _remove_children(ctx) 213 | self.contexts[ctx] = ctx_text 214 | 215 | def visitBox(self, ctx): 216 | pars = [] 217 | s = 4 if len(ctx.children) > 8 else 2 218 | pars.extend(self._convert_values(ctx, s)) 219 | pars.extend(self._convert_values(ctx, s+2)) 220 | pars.extend(self._convert_values(ctx, s+4)) 221 | 222 | try: 223 | pos_cent_ra = float(pars[0]) 224 | pos_cent_dec = float(pars[1]) 225 | dra = float(pars[2])/2 226 | ddec = float(pars[3])/2 227 | except ValueError: 228 | raise QueryError('sbox values incorrect') 229 | 230 | if self.output_sql in ('mysql', 'postgresql'): 231 | ctx_text = "sbox( spoint(%s(%s),%s(%s)),spoint(%s(%s),%s(%s)) )" %\ 232 | (self.conunits, '%.12f' % (pos_cent_ra - dra), 233 | self.conunits, '%.12f' % (pos_cent_dec - ddec), 234 | self.conunits, '%.12f' % (pos_cent_ra + dra), 235 | self.conunits, '%.12f' % (pos_cent_dec + ddec)) 236 | 237 | else: 238 | ctx_text = '' 239 | 240 | _remove_children(ctx) 241 | self.contexts[ctx] = ctx_text 242 | 243 | def visitCircle(self, ctx): 244 | ctx_text = '' 245 | if self.output_sql in ('mysql', 'postgresql'): 246 | s = 4 if isinstance(ctx.children[2], ADQLParser.Coord_sysContext) else 2 247 | radius = self._convert_values(ctx, s+2)[0] 248 | circle_center = ctx.children[s] 249 | if isinstance(circle_center.children[0], ADQLParser.CoordinatesContext): 250 | point_parameters = self._convert_values(circle_center, 0) 251 | point_ctx_text = "spoint(%s(%s), %s(%s))" %\ 252 | (self.conunits, point_parameters[0], 253 | self.conunits, point_parameters[1]) 254 | else: 255 | point_ctx = circle_center.children[0].children[0].children[0] 256 | if isinstance(point_ctx, ADQLParser.PointContext): 257 | self.visitPoint(point_ctx) 258 | point_ctx_text = self.contexts[point_ctx] 259 | else: 260 | raise QueryError('In the current implementation, circle ' + 261 | 'allows only explicitly defined point as ' + 262 | 'the circle center. For instance, ' + 263 | 'CIRCLE(POINT(t.ra, r.dec), 0.1)') 264 | 265 | ctx_text = "scircle( %s, %s(%s) )" %\ 266 | (point_ctx_text, self.conunits, radius) 267 | if self.output_sql == 'postgresql': 268 | derived_column = _get_ancestor_class_node(ctx, ADQLParser.Derived_columnContext, depth=3) 269 | if derived_column is not None: 270 | ctx_text = f"scircle_to_array_deg({ctx_text})" 271 | if not (any([isinstance(child, ADQLParser.As_clauseContext) for child in derived_column.children])): 272 | ctx_text = f"{ctx_text} AS circle" 273 | _remove_children(ctx) 274 | self.contexts[ctx] = ctx_text 275 | 276 | def visitPolygon(self, ctx): 277 | pars = [] 278 | 279 | for j in range(2, len(ctx.children), 2): 280 | par = self._convert_values(ctx, j) 281 | # only append coordinates 282 | # (ctx.children[2] can still return coord_sys which is deprecated) 283 | if len(par) > 1: 284 | pars.append(par) 285 | 286 | ustr = '' 287 | if self.conunits == "RADIANS": 288 | ustr = 'd' 289 | 290 | if self.output_sql in ('mysql', 'postgresql'): 291 | ctx_text = "spoly('{" 292 | for p in pars: 293 | ctx_text += '(%s%s,%s%s),' % (str(p[0]), ustr, str(p[1]), ustr) 294 | ctx_text = ctx_text[:-1] + "}')" 295 | if self.output_sql == 'postgresql': 296 | derived_column = _get_ancestor_class_node(ctx, ADQLParser.Derived_columnContext, depth=3) 297 | if derived_column is not None: 298 | ctx_text = f"spoly_to_array_deg({ctx_text})" 299 | if not (any([isinstance(child, ADQLParser.As_clauseContext) for child in derived_column.children])): 300 | ctx_text = f"{ctx_text} AS adql_polygon" 301 | else: 302 | ctx_text = '' 303 | 304 | _remove_children(ctx) 305 | self.contexts[ctx] = ctx_text 306 | 307 | 308 | class ADQLFunctionsTranslationVisitor(ADQLParserVisitor): 309 | """ 310 | Run this visitor after the geometry has already been processed. 311 | 312 | :param contexts: 313 | A dictionary that was created in the previous run and includes 314 | the replaced geometry chunks. 315 | 316 | """ 317 | def __init__(self, contexts, output_sql, conunits="DEGREES"): 318 | self.contexts = contexts 319 | self.output_sql = output_sql 320 | self.conunits = conunits 321 | 322 | def visitArea(self, ctx): 323 | arg = self.contexts[ctx.children[2].children[0]] 324 | if self.output_sql == 'mysql': 325 | ctx_text = 'sarea(%s)' % arg 326 | elif self.output_sql == 'postgresql': 327 | ctx_text = 'square_degrees(area(%s))' % arg 328 | else: 329 | ctx_text = '' 330 | derived_column = _get_ancestor_class_node(ctx, ADQLParser.Derived_columnContext, depth=9) 331 | if derived_column is not None: 332 | if not (any([isinstance(child, ADQLParser.As_clauseContext) for child in derived_column.children])): 333 | ctx_text = f"{ctx_text} AS adql_area" 334 | 335 | for _ in range(ctx.getChildCount() - 1): 336 | ctx.removeLastChild() 337 | self.contexts[ctx] = ctx_text 338 | 339 | ''' 340 | def visitCentroid(self, ctx): 341 | """ 342 | Works only for circles. 343 | 344 | """ 345 | # arg = self.contexts[ctx.children[2].children[0].children[0]] 346 | arg = self.contexts[ctx.children[2].children[0]] 347 | 348 | if self.output_sql == 'mysql': 349 | ctx_text = 'scenter(%s)' % arg 350 | elif self.output_sql == 'postgresql': 351 | ctx_text = '@@ %s' % arg 352 | else: 353 | ctx_text = '' 354 | 355 | _remove_children(ctx) 356 | self.contexts[ctx] = ctx_text 357 | ''' 358 | 359 | def visitContains_predicate(self, ctx): 360 | comp_value_l = ctx.children[0].getText() 361 | comp_value_r = ctx.children[2].getText() 362 | if comp_value_l == '1' or comp_value_l == '0': 363 | self.visitContains(ctx.children[2]) 364 | ctx_text = self.contexts[ctx.children[2]] 365 | if self.output_sql == 'mysql': 366 | ctx_text = f"{comp_value_l} = {ctx_text}" 367 | elif self.output_sql == 'postgresql' and comp_value_l == '0': 368 | ctx_text = ctx_text.replace('@', '!@') 369 | _remove_children(ctx) 370 | elif comp_value_r == '1' or comp_value_r == '0': 371 | self.visitContains(ctx.children[0]) 372 | ctx_text = self.contexts[ctx.children[0]] 373 | if self.output_sql == 'mysql': 374 | ctx_text = f"{comp_value_r} = {ctx_text}" 375 | elif self.output_sql == 'postgresql' and comp_value_r == '0': 376 | ctx_text = ctx_text.replace('@', '!@') 377 | _remove_children(ctx, reverse=True) 378 | else: 379 | raise QueryError('The function CONTAINS allows comparison to 1 or 0 only.') 380 | 381 | self.contexts[ctx] = ctx_text 382 | 383 | 384 | def visitContains(self, ctx): 385 | arg = (self.contexts[ctx.children[2].children[0]], 386 | self.contexts[ctx.children[4].children[0]]) 387 | 388 | if self.output_sql == 'mysql': 389 | ctx_text = 'srcontainsl(%s, %s)' % arg 390 | elif self.output_sql == 'postgresql': 391 | ctx_text = '%s @ %s' % arg 392 | else: 393 | ctx_text = '' 394 | 395 | _remove_children(ctx) 396 | self.contexts[ctx] = ctx_text 397 | 398 | def visitDistance(self, ctx): 399 | arg = ('', '') 400 | if isinstance(ctx.children[2],ADQLParser.Coord_valueContext): 401 | if isinstance(ctx.children[2].children[0].children[0], ADQLParser.PointContext): 402 | point_ctx1 = ctx.children[2].children[0].children[0] 403 | point_ctx2 = ctx.children[4].children[0].children[0] 404 | arg = (self.contexts[point_ctx1], 405 | self.contexts[point_ctx2]) 406 | else: 407 | raise QueryError('Distance in the current implementation is ' + 408 | 'possible only between two explicitly ' + 409 | 'defined points. For instance, ' + 410 | 'DISTANCE(POINT(t.ra, r.dec), POINT(0.0, 0.0)) ' + 411 | 'or DISTANCE(t.ra, r.dec, 0.0, 0.0)') 412 | else: 413 | arg = (f"spoint(RADIANS({_convert_values(ctx, 2, self.output_sql)[0]}), " + 414 | f"RADIANS({_convert_values(ctx, 4, self.output_sql)[0]}))", 415 | f"spoint(RADIANS({_convert_values(ctx, 6, self.output_sql)[0]}), " + 416 | f"RADIANS({_convert_values(ctx, 8, self.output_sql)[0]}))") 417 | 418 | if self.output_sql == 'mysql': 419 | ctx_text = '%s(sdist(%s, %s))' % ((self.conunits, ) + arg) 420 | elif self.output_sql == 'postgresql': 421 | ctx_text = '%s(%s <-> %s)' % ((self.conunits, ) + arg) 422 | derived_column = _get_ancestor_class_node(ctx, ADQLParser.Derived_columnContext, depth=9) 423 | if derived_column is not None: 424 | if not (any([isinstance(child, ADQLParser.As_clauseContext) for child in derived_column.children])): 425 | ctx_text = f"{ctx_text} AS distance" 426 | else: 427 | ctx_text = '' 428 | 429 | for _ in range(ctx.getChildCount() - 1): 430 | ctx.removeLastChild() 431 | self.contexts[ctx] = ctx_text 432 | 433 | 434 | def visitIntersects_predicate(self, ctx): 435 | comp_value_l = ctx.children[0].getText() 436 | comp_value_r = ctx.children[2].getText() 437 | if comp_value_l == '1' or comp_value_l == '0': 438 | self.visitIntersects(ctx.children[2]) 439 | ctx_text = self.contexts[ctx.children[2]] 440 | if self.output_sql == 'mysql': 441 | ctx_text = f"{comp_value_l} = {ctx_text}" 442 | elif self.output_sql == 'postgresql' and comp_value_l == '0': 443 | ctx_text = ctx_text.replace('&&', '!&&') 444 | _remove_children(ctx) 445 | elif comp_value_r == '1' or comp_value_r == '0': 446 | self.visitIntersects(ctx.children[0]) 447 | ctx_text = self.contexts[ctx.children[0]] 448 | if self.output_sql == 'mysql': 449 | ctx_text = f"{comp_value_r} = {ctx_text}" 450 | elif self.output_sql == 'postgresql' and comp_value_r == '0': 451 | ctx_text = ctx_text.replace('&&', '!&&') 452 | _remove_children(ctx, reverse=True) 453 | else: 454 | raise QueryError('The function INTERSECTS allows comparison to 1 or 0 only.') 455 | 456 | self.contexts[ctx] = ctx_text 457 | 458 | 459 | def visitIntersects(self, ctx): 460 | arg = (self.contexts[ctx.children[2].children[0]], 461 | self.contexts[ctx.children[4].children[0]]) 462 | 463 | if self.output_sql == 'mysql': 464 | ctx_text = 'soverlaps(%s, %s)' % arg 465 | elif self.output_sql == 'postgresql': 466 | ctx_text = '%s && %s' % arg 467 | else: 468 | ctx_text = '' 469 | 470 | _remove_children(ctx) 471 | self.contexts[ctx] = ctx_text 472 | 473 | def visitMath_function(self, ctx): 474 | ctx_text = ctx.getText() 475 | if self.output_sql == 'postgresql' and ctx_text[:5].lower() == 'log10': 476 | _remove_children(ctx) 477 | self.contexts[ctx] = 'LOG' + ctx_text[5:] 478 | elif self.output_sql == 'postgresql' and ctx_text[:3].lower() == 'log': 479 | _remove_children(ctx) 480 | self.contexts[ctx] = 'LN' + ctx_text[3:] 481 | 482 | 483 | class SelectQueryListener(ADQLParserListener): 484 | def __init__(self): 485 | self.limit_visitor = LimitVisitor() 486 | self.limit_contexts = {} 487 | 488 | def enterSelect_query(self, ctx): 489 | self.limit_visitor.visit(ctx) 490 | self.limit_contexts.update(self.limit_visitor.limit_contexts) 491 | 492 | 493 | class LimitVisitor(ADQLParserVisitor): 494 | def __init__(self, remove=False): 495 | self.limit_terminal_visitor = LimitTerminalVisitor() 496 | self.limit_contexts = {} 497 | 498 | def visitSet_limit(self, ctx): 499 | try: 500 | self.limit = int(ctx.children[1].getText()) 501 | ctx.removeLastChild() 502 | ctx.removeLastChild() 503 | 504 | self.limit_terminal_visitor.visit(ctx.parentCtx) 505 | lstr = 'LIMIT %d' % self.limit 506 | self.limit_contexts[self.limit_terminal_visitor.terminal] = lstr 507 | except IndexError: 508 | pass 509 | 510 | 511 | class LimitTerminalVisitor(ADQLParserVisitor): 512 | def __init__(self): 513 | self.terminal = None 514 | 515 | def visitTerminal(self, ctx): 516 | self.terminal = ctx 517 | 518 | 519 | class FormatListener(ADQLParserListener): 520 | """ 521 | Used for formating the output query. 522 | 523 | """ 524 | def __init__(self, parser, contexts, limit_contexts): 525 | self._parser = parser 526 | self.nodes = [] 527 | self.contexts = contexts 528 | self.limit_contexts = limit_contexts 529 | 530 | def visitTerminal(self, node): 531 | try: 532 | if node.parentCtx.INTERSECT(): 533 | raise QueryError('INTERSECT operator not supported. Please ' + 534 | 'rewrite query using WHERE statement.') 535 | except AttributeError: 536 | pass 537 | 538 | try: 539 | if node.parentCtx.EXCEPT(): 540 | raise QueryError('EXCEPT operator not supported. Please ' + 541 | 'rewrite query using WHERE statement.') 542 | except AttributeError: 543 | pass 544 | 545 | try: 546 | if node.parentCtx.WITH(): 547 | raise QueryError('WITH clause not supported.') 548 | 549 | except AttributeError: 550 | pass 551 | 552 | try: 553 | nd = self.contexts[node.parentCtx] 554 | except KeyError: 555 | nd = node.getText() 556 | if isinstance(node.parentCtx, 557 | ADQLParser.Character_string_literalContext): 558 | if nd == "'": 559 | nd = None 560 | 561 | if nd is not None: 562 | if isinstance(node.parentCtx, ADQLParser.Set_function_typeContext)\ 563 | or isinstance(node.parentCtx.parentCtx, 564 | ADQLParser.User_defined_function_nameContext)\ 565 | or nd.upper() in adql_function_names: 566 | nd += '_' 567 | 568 | self.nodes.append(nd) 569 | 570 | try: 571 | nd = self.limit_contexts[node] 572 | self.nodes.append(nd) 573 | except KeyError: 574 | pass 575 | 576 | def format_query(self): 577 | query = ' '.join(self.nodes).rstrip(';') 578 | query = query.replace('_ ', '') 579 | query = query.replace(' . ', '.') 580 | query = query.replace(' , ', ', ') 581 | query = query.replace('( ', '(') 582 | query = query.replace(' )', ')') 583 | query = query.rstrip() 584 | return '%s;' % query.rstrip() 585 | 586 | 587 | class ADQLQueryTranslator(object): 588 | """ 589 | The main translator object used to do the actual translation. 590 | 591 | :param query: 592 | ADQL query string. 593 | 594 | """ 595 | def __init__(self, query=None): 596 | self._query = None 597 | 598 | if query is not None: 599 | self.set_query(query) 600 | 601 | def parse(self): 602 | """ 603 | Parse the input query and store the output in self.tree. 604 | 605 | """ 606 | inpt = antlr4.InputStream(self.query) 607 | lexer = ADQLLexer(inpt) 608 | self.stream = antlr4.CommonTokenStream(lexer) 609 | self.parser = ADQLParser(self.stream) 610 | self.syntax_error_listener = SyntaxErrorListener() 611 | self.parser._listeners = [self.syntax_error_listener] 612 | 613 | self.tree = self.parser.query() 614 | 615 | if len(self.syntax_error_listener.syntax_errors): 616 | raise QuerySyntaxError(self.syntax_error_listener.syntax_errors) 617 | 618 | self.walker = antlr4.ParseTreeWalker() 619 | 620 | @property 621 | def query(self): 622 | """ 623 | Get the query string. 624 | 625 | """ 626 | return self._query 627 | 628 | def set_query(self, query): 629 | """ 630 | Set the query string. A semicolon is added in case it is missing. 631 | 632 | :param value: 633 | Query string. 634 | 635 | """ 636 | self._query = query.lstrip('\n').rstrip().rstrip(';') + ';' 637 | self.parse() 638 | 639 | def translate(self, translator_visitor): 640 | 641 | select_query_listener = SelectQueryListener() 642 | self.walker.walk(select_query_listener, self.tree) 643 | 644 | format_listener = FormatListener(self.parser, 645 | translator_visitor.contexts, 646 | select_query_listener.limit_contexts) 647 | self.walker.walk(format_listener, self.tree) 648 | return format_listener.format_query() 649 | 650 | def to_mysql(self): 651 | """ 652 | Translate ADQL query to a MySQL query using mysql_sphere plugin 653 | for the spherical functions. 654 | 655 | """ 656 | if self._query is None: 657 | raise QueryError('No query given.') 658 | 659 | self.parse() 660 | 661 | translator_visitor = ADQLGeometryTranslationVisitor(output_sql='mysql') 662 | translator_visitor.visit(self.tree) 663 | translator_visitor = \ 664 | ADQLFunctionsTranslationVisitor(translator_visitor.contexts, 665 | output_sql='mysql') 666 | translator_visitor.visit(self.tree) 667 | 668 | translated_query = self.translate(translator_visitor) 669 | # fix wrapper functions: 670 | for func in (('CENTROID ', 'scenter'), ('DISTANCE ', 'sdist')): 671 | translated_query = translated_query.replace(*func) 672 | return translated_query 673 | 674 | def to_postgresql(self): 675 | """ 676 | Translate ADQL query to a PostgreSQL query using pg_sphere plugin 677 | for the spherical functions. 678 | 679 | """ 680 | if self._query is None: 681 | raise QueryError('No query given.') 682 | 683 | self.parse() 684 | 685 | translator_visitor = ADQLGeometryTranslationVisitor( 686 | output_sql='postgresql') 687 | translator_visitor.visit(self.tree) 688 | translator_visitor = \ 689 | ADQLFunctionsTranslationVisitor(translator_visitor.contexts, 690 | output_sql='postgresql') 691 | translator_visitor.visit(self.tree) 692 | 693 | translated_query = self.translate(translator_visitor) 694 | return translated_query 695 | -------------------------------------------------------------------------------- /src/queryparser/common/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .common import process_column_name, get_column_name_listener,\ 4 | SQLQueryProcessor 5 | 6 | __all__ = ['SQLQueryProcessor', 'process_column_name', 7 | 'get_column_name_listener'] 8 | -------------------------------------------------------------------------------- /src/queryparser/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class QueryError(Exception): 5 | def __init__(self, messages=[]): 6 | self.messages = messages 7 | 8 | 9 | class QuerySyntaxError(Exception): 10 | def __init__(self, syntax_errors=[]): 11 | self.syntax_errors = syntax_errors 12 | -------------------------------------------------------------------------------- /src/queryparser/mysql/MySQLParser.g4: -------------------------------------------------------------------------------- 1 | 2 | parser grammar MySQLParser; 3 | 4 | options 5 | { tokenVocab = MySQLLexer; } 6 | 7 | ////////////////////////////////////////////////////////////////////////////// 8 | 9 | relational_op: 10 | EQ | LTH | GTH | NOT_EQ | LET | GET ; 11 | 12 | cast_data_type: 13 | BINARY (LPAREN INTEGER_NUM RPAREN)? 14 | | CHAR (LPAREN INTEGER_NUM RPAREN)? 15 | | DATE_SYM 16 | | DATETIME 17 | | DECIMAL_SYM (LPAREN INTEGER_NUM (COMMA INTEGER_NUM)? RPAREN)? 18 | | SIGNED_SYM (INTEGER_SYM)? 19 | | TIME_SYM 20 | | UNSIGNED_SYM (INTEGER_SYM)? ; 21 | 22 | search_modifier: 23 | (IN_SYM NATURAL LANGUAGE MODE_SYM ( WITH QUERY_SYM EXPANSION_SYM )?) 24 | | (IN_SYM BOOLEAN_SYM MODE_SYM) 25 | | (WITH QUERY_SYM EXPANSION_SYM) ; 26 | 27 | interval_unit: 28 | SECOND | MINUTE | HOUR | DAY_SYM | WEEK | MONTH | QUARTER | YEAR 29 | | SECOND_MICROSECOND | MINUTE_MICROSECOND | MINUTE_SECOND 30 | | HOUR_MICROSECOND | HOUR_SECOND | HOUR_MINUTE | DAY_MICROSECOND 31 | | DAY_SECOND | DAY_MINUTE | DAY_HOUR | YEAR_MONTH ; 32 | 33 | transcoding_name: 34 | LATIN1 | UTF8 ; 35 | 36 | 37 | ////////////////////////////////////////////////////////////////////////////// 38 | // LITERALS 39 | 40 | bit_literal: BIT_NUM ; 41 | boolean_literal: TRUE_SYM | FALSE_SYM ; 42 | hex_literal: HEX_DIGIT ; 43 | number_literal: ( PLUS | MINUS )? ( INTEGER_NUM | REAL_NUMBER ) ; 44 | string_literal: TEXT_STRING ; 45 | 46 | 47 | ////////////////////////////////////////////////////////////////////////////// 48 | // FUNCTIONS 49 | 50 | char_functions: 51 | ASCII_SYM | BIN | BIT_LENGTH | CHAR_LENGTH | CHAR | CONCAT_WS | CONCAT 52 | | ELT | EXPORT_SET | FIELD | FIND_IN_SET | FORMAT | FROM_BASE64 | HEX 53 | | INSERT | INSTR | LEFT | LENGTH | LOAD_FILE | LOCATE | LOWER | LPAD 54 | | LTRIM | MAKE_SET | MID | OCT | ORD | QUOTE | REPEAT | REPLACE | REVERSE 55 | | RIGHT | RPAD | RTRIM | SOUNDEX | SPACE | STRCMP | SUBSTRING_INDEX 56 | | SUBSTRING | TO_BASE64 | TRIM | UNHEX | UPPER | WEIGHT_STRING ; 57 | 58 | group_functions: 59 | AVG | COUNT | MAX_SYM | MIN_SYM | SUM | BIT_AND | BIT_OR | BIT_XOR 60 | | BIT_COUNT | GROUP_CONCAT | STD | STDDEV | STDDEV_POP | STDDEV_SAMP 61 | | VAR_POP | VAR_SAMP | VARIANCE ; 62 | 63 | number_functions: 64 | ABS | ACOS | ASIN | ATAN2 | ATAN | CEIL | CEILING | CONV | COS | COT 65 | | CRC32 | DEGREES | EXP | FLOOR | LN | LOG10 | LOG2 | LOG | MOD | PI | POW 66 | | POWER | RADIANS | RAND | ROUND | SIGN | SIN | SQRT | TAN | TRUNCATE ; 67 | 68 | other_functions: 69 | MAKE_SET | LOAD_FILE | IF | IFNULL | AES_ENCRYPT | AES_DECRYPT | DECODE 70 | | ENCODE | DES_DECRYPT | DES_ENCRYPT | ENCRYPT | MD5 | OLD_PASSWORD 71 | | PASSWORD | BENCHMARK | CHARSET | COERCIBILITY | COLLATION | CONNECTION_ID 72 | | CURRENT_USER | DATABASE | SCHEMA | USER | SESSION_USER | SYSTEM_USER 73 | | VERSION_SYM | FOUND_ROWS | LAST_INSERT_ID | DEFAULT | GET_LOCK 74 | | RELEASE_LOCK | IS_FREE_LOCK | IS_USED_LOCK | MASTER_POS_WAIT | INET_ATON 75 | | INET_NTOA | NAME_CONST | SLEEP | UUID | VALUES ; 76 | 77 | time_functions: 78 | ADDDATE | ADDTIME | CONVERT_TZ | CURDATE | CURTIME | DATE_ADD 79 | | DATE_FORMAT | DATE_SUB | DATE_SYM | DATEDIFF | DAYNAME | DAYOFMONTH 80 | | DAYOFWEEK | DAYOFYEAR | EXTRACT | FROM_DAYS | FROM_UNIXTIME | GET_FORMAT 81 | | HOUR | LAST_DAY | MAKEDATE | MAKETIME | MICROSECOND | MINUTE | MONTH 82 | | MONTHNAME | NOW | PERIOD_ADD | PERIOD_DIFF | QUARTER | SEC_TO_TIME 83 | | SECOND | STR_TO_DATE | SUBTIME | SYSDATE | TIME_FORMAT | TIME_TO_SEC 84 | | TIME_SYM | TIMEDIFF | TIMESTAMP | TIMESTAMPADD | TIMESTAMPDIFF | TO_DAYS 85 | | TO_SECONDS | UNIX_TIMESTAMP | UTC_DATE | UTC_TIME | UTC_TIMESTAMP | WEEK 86 | | WEEKDAY | WEEKOFYEAR | YEAR | YEARWEEK ; 87 | 88 | mysql_sphere_functions: 89 | SDIST | SAREA | SCENTER | SCIRCUM | SLENGTH | SSWAP | SNPOINTS | SSTR 90 | | MYSQL_SPHERE_VERSION | SRCONTAINSL | SLCONTAINSR | SRNOTCONTAINSL 91 | | SLNOTCONTAINSR | SOVERLAPS | SNOTOVERLAPS | SEQUAL | SNOTEQUAL 92 | | STRANSFORM | SINVERSE | SPOINT | SPOINT_LONG | SPOINT_LAT | SPOINT_X 93 | | SPOINT_Y | SPOINT_Z | SPOINT_EQUAL | STRANS | STRANS_POINT 94 | | STRANS_POINT_INVERSE | STRANS_EQUAL | STRANS_EQUAL_NEG | STRANS_PHI 95 | | STRANS_THETA | STRANS_PSI | STRANS_AXES | STRANS_INVERT | STRANS_ZXZ 96 | | STRANS_TRANS | STRANS_TRANS_INV | SCIRCLE | SCIRCLE_RADIUS 97 | | SCIRCLE_EQUAL | SCIRCLE_EQUAL_NEG | SCIRCLE_OVERLAP 98 | | SCIRCLE_OVERLAP_NEG | SCIRCLE_CONTAINED_BY_CIRCLE 99 | | SCIRCLE_CONTAINED_BY_CIRCLE_NEG | SCIRCLE_CONTAINS_CIRCLE 100 | | SCIRCLE_CONTAINS_CIRCLE_NEG | SPOINT_CONTAINED_BY_CIRCLE 101 | | SPOINT_CONTAINED_BY_CIRCLE_NEG | SPOINT_CONTAINED_BY_CIRCLE_COM 102 | | SPOINT_CONTAINED_BY_CIRCLE_COM_NEG | STRANS_CIRCLE 103 | | STRANS_CIRCLE_INVERSE | SLINE | SMERIDIAN | SLINE_BEG | SLINE_END 104 | | SLINE_EQUAL | SLINE_EQUAL_NEG | SLINE_TURN | SLINE_CROSSES 105 | | SLINE_CROSSES_NEG | SLINE_OVERLAP | SLINE_CONTAINS_POINT 106 | | SLINE_CONTAINS_POINT_COM | SLINE_CONTAINS_POINT_NEG 107 | | SLINE_CONTAINS_POINT_COM_NEG | STRANS_LINE | STRANS_LINE_INVERSE 108 | | SLINE_OVERLAP_CIRCLE | SLINE_OVERLAP_CIRCLE_COM 109 | | SLINE_OVERLAP_CIRCLE_NEG | SLINE_OVERLAP_CIRCLE_COM_NEG 110 | | SCIRCLE_CONTAINS_LINE | SCIRCLE_CONTAINS_LINE_COM 111 | | SCIRCLE_CONTAINS_LINE_NEG | SCIRCLE_CONTAINS_LINE_COM_NEG | SELLIPSE 112 | | SELLIPSE_INC | SELLIPSE_LRAD | SELLIPSE_SRAD | SELLIPSE_EQUAL 113 | | SELLIPSE_EQUAL_NEG | SELLIPSE_CONTAINS_ELLIPSE 114 | | SELLIPSE_CONTAINS_ELLIPSE_NEG | SELLIPSE_CONTAINS_ELLIPSE_COM 115 | | SELLIPSE_CONTAINS_ELLIPSE_COM_NEG | SELLIPSE_OVERLAP_ELLIPSE 116 | | SELLIPSE_OVERLAP_ELLIPSE_NEG | SELLIPSE_CONTAINS_POINT 117 | | SELLIPSE_CONTAINS_POINT_NEG | SELLIPSE_CONTAINS_POINT_COM 118 | | SELLIPSE_CONTAINS_POINT_COM_NEG | SELLIPSE_CONTAINS_CIRCLE 119 | | SELLIPSE_CONTAINS_CIRCLE_NEG | SELLIPSE_CONTAINS_CIRCLE_COM 120 | | SELLIPSE_CONTAINS_CIRCLE_COM_NEG | SCIRCLE_CONTAINS_ELLIPSE 121 | | SCIRCLE_CONTAINS_ELLIPSE_NEG | SCIRCLE_CONTAINS_ELLIPSE_COM 122 | | SCIRCLE_CONTAINS_ELLIPSE_COM_NEG | SELLIPSE_OVERLAP_CIRCLE 123 | | SELLIPSE_OVERLAP_CIRCLE_NEG | SELLIPSE_OVERLAP_CIRCLE_COM 124 | | SELLIPSE_OVERLAP_CIRCLE_COM_NEG | SELLIPSE_OVERLAP_LINE 125 | | SELLIPSE_OVERLAP_LINE_NEG | SELLIPSE_OVERLAP_LINE_COM 126 | | SELLIPSE_OVERLAP_LINE_COM_NEG | SELLIPSE_CONTAINS_LINE 127 | | SELLIPSE_CONTAINS_LINE_NEG | SELLIPSE_CONTAINS_LINE_COM 128 | | SELLIPSE_CONTAINS_LINE_COM_NEG | STRANS_ELLIPSE 129 | | STRANS_ELLIPSE_INVERSE | SPOLY | SPOLY_EQUAL | SPOLY_EQUAL_NEG 130 | | SPOLY_CONTAINS_POLYGON | SPOLY_CONTAINS_POLYGON_NEG 131 | | SPOLY_CONTAINS_POLYGON_COM | SPOLY_CONTAINS_POLYGON_COM_NEG 132 | | SPOLY_OVERLAP_POLYGON | SPOLY_OVERLAP_POLYGON_NEG 133 | | SPOLY_CONTAINS_POINT | SPOLY_CONTAINS_POINT_NEG 134 | | SPOLY_CONTAINS_POINT_COM | SPOLY_CONTAINS_POINT_COM_NEG 135 | | SPOLY_CONTAINS_CIRCLE | SPOLY_CONTAINS_CIRCLE_NEG 136 | | SPOLY_CONTAINS_CIRCLE_COM | SPOLY_CONTAINS_CIRCLE_COM_NEG 137 | | SCIRCLE_CONTAINS_POLYGON | SCIRCLE_CONTAINS_POLYGON_NEG 138 | | SCIRCLE_CONTAINS_POLYGON_COM | SCIRCLE_CONTAINS_POLYGON_COM_NEG 139 | | SPOLY_OVERLAP_CIRCLE | SPOLY_OVERLAP_CIRCLE_NEG 140 | | SPOLY_OVERLAP_CIRCLE_COM | SPOLY_OVERLAP_CIRCLE_COM_NEG 141 | | SPOLY_CONTAINS_LINE | SPOLY_CONTAINS_LINE_NEG | SPOLY_CONTAINS_LINE_COM 142 | | SPOLY_CONTAINS_LINE_COM_NEG | SPOLY_OVERLAP_LINE 143 | | SPOLY_OVERLAP_LINE_NEG | SPOLY_OVERLAP_LINE_COM 144 | | SPOLY_OVERLAP_LINE_COM_NEG | SPOLY_CONTAINS_ELLIPSE 145 | | SPOLY_CONTAINS_ELLIPSE_NEG | SPOLY_CONTAINS_ELLIPSE_COM 146 | | SPOLY_CONTAINS_ELLIPSE_COM_NEG | SELLIPSE_CONTAINS_POLYGON 147 | | SELLIPSE_CONTAINS_POLYGON_NEG | SELLIPSE_CONTAINS_POLYGON_COM 148 | | SELLIPSE_CONTAINS_POLYGON_COM_NEG | SPOLY_OVERLAP_ELLIPSE 149 | | SPOLY_OVERLAP_ELLIPSE_NEG | SPOLY_OVERLAP_ELLIPSE_COM 150 | | SPOLY_OVERLAP_ELLIPSE_COM_NEG | STRANS_POLY | STRANS_POLY_INVERSE 151 | | SPOLY_ADD_POINT_AGGR | SPOLY_AGGR | SPATH | SPATH_EQUAL 152 | | SPATH_EQUAL_NEG | SPATH_OVERLAP_PATH | SPATH_OVERLAP_PATH_NEG 153 | | SPATH_CONTAINS_POINT | SPATH_CONTAINS_POINT_NEG 154 | | SPATH_CONTAINS_POINT_COM | SPATH_CONTAINS_POINT_COM_NEG 155 | | SCIRCLE_CONTAINS_PATH | SCIRCLE_CONTAINS_PATH_NEG 156 | | SCIRCLE_CONTAINS_PATH_COM | SCIRCLE_CONTAINS_PATH_COM_NEG 157 | | SCIRCLE_OVERLAP_PATH | SCIRCLE_OVERLAP_PATH_NEG 158 | | SCIRCLE_OVERLAP_PATH_COM | SCIRCLE_OVERLAP_PATH_COM_NEG 159 | | SPATH_OVERLAP_LINE | SPATH_OVERLAP_LINE_NEG | SPATH_OVERLAP_LINE_COM 160 | | SPATH_OVERLAP_LINE_COM_NEG | SELLIPSE_CONTAINS_PATH 161 | | SELLIPSE_CONTAINS_PATH_NEG | SELLIPSE_CONTAINS_PATH_COM 162 | | SELLIPSE_CONTAINS_PATH_COM_NEG | SELLIPSE_OVERLAP_PATH 163 | | SELLIPSE_OVERLAP_PATH_NEG | SELLIPSE_OVERLAP_PATH_COM 164 | | SELLIPSE_OVERLAP_PATH_COM_NEG | SPOLY_CONTAINS_PATH 165 | | SPOLY_CONTAINS_PATH_NEG | SPOLY_CONTAINS_PATH_COM 166 | | SPOLY_CONTAINS_PATH_COM_NEG | SPOLY_OVERLAP_PATH 167 | | SPOLY_OVERLAP_PATH_NEG | SPOLY_OVERLAP_PATH_COM 168 | | SPOLY_OVERLAP_PATH_COM_NEG | STRANS_PATH | STRANS_PATH_INVERSE 169 | | SPATH_ADD_POINT_AGGR | SPATH_AGGR | SBOX | SBOX_SW | SBOX_SE | SBOX_NW 170 | | SBOX_NE | SBOX_EQUAL | SBOX_EQUAL_NEG | SBOX_CONTAINS_BOX 171 | | SBOX_CONTAINS_BOX_NEG | SBOX_CONTAINS_BOX_COM 172 | | SBOX_CONTAINS_BOX_COM_NEG | SBOX_OVERLAP_BOX | SBOX_OVERLAP_BOX_NEG 173 | | SBOX_CONTAINS_POINT | SBOX_CONTAINS_POINT_NEG | SBOX_CONTAINS_POINT_COM 174 | | SBOX_CONTAINS_POINT_COM_NEG | SBOX_CONTAINS_CIRCLE 175 | | SBOX_CONTAINS_CIRCLE_NEG | SBOX_CONTAINS_CIRCLE_COM 176 | | SBOX_CONTAINS_CIRCLE_COM_NEG | SCIRCLE_CONTAINS_BOX 177 | | SCIRCLE_CONTAINS_BOX_NEG | SCIRCLE_CONTAINS_BOX_COM 178 | | SCIRCLE_CONTAINS_BOX_COM_NEG | SBOX_OVERLAP_CIRCLE 179 | | SBOX_OVERLAP_CIRCLE_NEG | SBOX_OVERLAP_CIRCLE_COM 180 | | SBOX_OVERLAP_CIRCLE_COM_NEG | SBOX_CONTAINS_LINE 181 | | SBOX_CONTAINS_LINE_NEG | SBOX_CONTAINS_LINE_COM 182 | | SBOX_CONTAINS_LINE_COM_NEG | SBOX_OVERLAP_LINE | SBOX_OVERLAP_LINE_NEG 183 | | SBOX_OVERLAP_LINE_COM | SBOX_OVERLAP_LINE_COM_NEG 184 | | SBOX_CONTAINS_ELLIPSE | SBOX_CONTAINS_ELLIPSE_NEG 185 | | SBOX_CONTAINS_ELLIPSE_COM | SBOX_CONTAINS_ELLIPSE_COM_NEG 186 | | SELLIPSE_CONTAINS_BOX | SELLIPSE_CONTAINS_BOX_NEG 187 | | SELLIPSE_CONTAINS_BOX_COM | SELLIPSE_CONTAINS_BOX_COM_NEG 188 | | SBOX_OVERLAP_ELLIPSE | SBOX_OVERLAP_ELLIPSE_NEG 189 | | SBOX_OVERLAP_ELLIPSE_COM | SBOX_OVERLAP_ELLIPSE_COM_NEG 190 | | SBOX_CONTAINS_POLY | SBOX_CONTAINS_POLY_NEG | SBOX_CONTAINS_POLY_COM 191 | | SBOX_CONTAINS_POLY_COM_NEG | SPOLY_CONTAINS_BOX 192 | | SPOLY_CONTAINS_BOX_NEG | SPOLY_CONTAINS_BOX_COM 193 | | SPOLY_CONTAINS_BOX_COM_NEG | SBOX_OVERLAP_POLY | SBOX_OVERLAP_POLY_NEG 194 | | SBOX_OVERLAP_POLY_COM | SBOX_OVERLAP_POLY_COM_NEG | SBOX_CONTAINS_PATH 195 | | SBOX_CONTAINS_PATH_NEG | SBOX_CONTAINS_PATH_COM 196 | | SBOX_CONTAINS_PATH_COM_NEG | SBOX_OVERLAP_PATH | SBOX_OVERLAP_PATH_NEG 197 | | SBOX_OVERLAP_PATH_COM | SBOX_OVERLAP_PATH_COM_NEG ; 198 | 199 | mysql_udf_functions: 200 | STRRPOS | IDLE | ANGDIST | HILBERTKEY | COORDFROMHILBERTKEY 201 | | SUM_OF_SQUARES | PARTITADD_SUM_OF_SQARES | GAIA_HEALPIX ; 202 | 203 | functionList: 204 | number_functions | char_functions | time_functions | other_functions 205 | | mysql_sphere_functions | mysql_udf_functions; 206 | 207 | 208 | literal_value: 209 | string_literal | number_literal | hex_literal | boolean_literal 210 | | bit_literal | NULL_SYM ; 211 | 212 | 213 | ////////////////////////////////////////////////////////////////////////////// 214 | // MAIN 215 | 216 | select_expression: 217 | SELECT 218 | 219 | ( ALL | DISTINCT | DISTINCTROW )? 220 | ( HIGH_PRIORITY )? 221 | ( STRAIGHT_JOIN )? 222 | ( SQL_SMALL_RESULT )? ( SQL_BIG_RESULT )? ( SQL_BUFFER_RESULT )? 223 | ( SQL_CACHE_SYM | SQL_NO_CACHE_SYM )? ( SQL_CALC_FOUND_ROWS )? 224 | 225 | select_list 226 | 227 | ( 228 | FROM table_references 229 | ( partition_clause )? 230 | ( where_clause )? 231 | ( groupby_clause )? 232 | ( having_clause )? 233 | ) ? 234 | 235 | ( orderby_clause )? 236 | ( limit_clause )? 237 | ( ( FOR_SYM UPDATE ) | ( LOCK IN_SYM SHARE_SYM MODE_SYM ) )? 238 | 239 | SEMI? 240 | ; 241 | 242 | 243 | ////////////////////////////////////////////////////////////////////////////// 244 | // TOKENS 245 | 246 | alias: ( AS_SYM )? ID ; 247 | bit_expr: factor1 ( VERTBAR factor1 )? ; 248 | 249 | bool_primary: 250 | predicate ( relational_op predicate )? | ( ( NOT_SYM )? EXISTS subquery); 251 | 252 | case_when_statement: CASE_SYM ( case_when_statement1 | case_when_statement2 ) 253 | ( ELSE_SYM bit_expr )? END_SYM; 254 | case_when_statement1: ( WHEN_SYM expression THEN_SYM bit_expr )+ ; 255 | case_when_statement2: bit_expr ( WHEN_SYM bit_expr THEN_SYM bit_expr )+ ; 256 | column_list: LPAREN column_spec ( COMMA column_spec )* RPAREN ; 257 | column_name: ID ; 258 | column_spec: ( ( schema_name DOT )? table_name DOT )? column_name ; 259 | 260 | displayed_column : ( table_spec DOT ASTERISK ) | ( bit_expr ( alias )? ) ; 261 | 262 | exp_factor1: exp_factor2 ( XOR exp_factor2 )* ; 263 | exp_factor2: exp_factor3 ( AND_SYM exp_factor3 )* ; 264 | exp_factor3: ( NOT_SYM )? exp_factor4 ; 265 | exp_factor4: bool_primary ( IS_SYM ( NOT_SYM )? 266 | (boolean_literal | NULL_SYM) )? ; 267 | expression: exp_factor1 ( OR_SYM exp_factor1 )* ; 268 | expression_list: LPAREN expression ( COMMA expression )* RPAREN ; 269 | 270 | factor1: factor2 ( BITAND factor2 )? ; 271 | factor2: factor3 ( ( SHIFT_LEFT | SHIFT_RIGHT ) factor3 )? ; 272 | factor3: factor4 ( ( PLUS | MINUS ) factor4 )* ; 273 | factor4: factor5 ( ( ASTERISK | DIVIDE | MOD_SYM | POWER_OP ) factor5 )* ; 274 | factor5: ( PLUS | MINUS | NEGATION | BINARY )? simple_expr 275 | ( ( PLUS | MINUS ) interval_expr )? ; 276 | function_call: 277 | ( functionList ( LPAREN ( expression ( COMMA expression )* )? RPAREN ) ? ) 278 | | ( CONVERT_SYM LPAREN expression COMMA cast_data_type RPAREN ) 279 | | ( CONVERT_SYM LPAREN expression USING_SYM transcoding_name RPAREN ) 280 | | ( CAST_SYM LPAREN expression AS_SYM cast_data_type RPAREN ) 281 | | ( group_functions LPAREN ( ASTERISK | ALL | DISTINCT )? ( bit_expr )? RPAREN ) ; 282 | 283 | groupby_clause: GROUP_SYM BY_SYM groupby_item ( COMMA groupby_item )* ( WITH ROLLUP_SYM )? ; 284 | groupby_item: (column_spec | INTEGER_NUM | bit_expr ) ( ASC | DESC )?; 285 | 286 | having_clause: HAVING expression ; 287 | 288 | 289 | index_hint: 290 | USE_SYM index_options LPAREN ( index_list )? RPAREN 291 | | IGNORE_SYM index_options LPAREN index_list RPAREN 292 | | FORCE_SYM index_options LPAREN index_list RPAREN ; 293 | index_hint_list: index_hint ( COMMA index_hint )* ; 294 | index_name: ID ; 295 | index_list: index_name ( COMMA index_name )* ; 296 | index_options: ( INDEX_SYM | KEY_SYM ) ( FOR_SYM (( JOIN_SYM ) | ( ORDER_SYM BY_SYM ) | ( GROUP_SYM BY_SYM )) )? ; 297 | 298 | interval_expr: INTERVAL_SYM expression interval_unit ; 299 | 300 | join_condition: ( ON expression ) | ( USING_SYM column_list ) ; 301 | 302 | limit_clause: LIMIT ( ( ( offset COMMA )? row_count) | ( row_count OFFSET_SYM offset ) ); 303 | 304 | match_against_statement:MATCH ( column_spec ( COMMA column_spec )* ) AGAINST ( expression ( search_modifier )? ) ; 305 | 306 | offset: INTEGER_NUM ; 307 | row_count: INTEGER_NUM ; 308 | 309 | orderby_clause: ORDER_SYM BY_SYM orderby_item ( COMMA orderby_item )* ; 310 | orderby_item: groupby_item ( ASC | DESC )? ; 311 | 312 | partition_clause: PARTITION_SYM LPAREN partition_names RPAREN ; 313 | partition_name: ID ; 314 | partition_names: partition_name ( COMMA partition_name )* ; 315 | 316 | bit_fac1: ( NOT_SYM )? ( (IN_SYM ( subquery | expression_list )) | (LIKE_SYM simple_expr ( ESCAPE_SYM simple_expr )?) | (REGEXP bit_expr) | (BETWEEN bit_expr AND_SYM predicate)) ; 317 | bit_fac2: SOUNDS_SYM LIKE_SYM bit_expr; 318 | predicate: 319 | bit_expr (( bit_fac1 | bit_fac2)?) ; 320 | /* 321 | | ( bit_expr ( NOT_SYM )? IN_SYM ( subquery | expression_list ) ); //done 322 | | ( bit_expr ( NOT_SYM )? BETWEEN bit_expr AND_SYM predicate ) //done 323 | | ( bit_expr SOUNDS_SYM LIKE_SYM bit_expr ) //done 324 | | ( bit_expr ( NOT_SYM )? LIKE_SYM simple_expr ( ESCAPE_SYM simple_expr )? ) //done 325 | | ( bit_expr ( NOT_SYM )? REGEXP bit_expr ) //done 326 | | ( bit_expr ) ; //done 327 | */ 328 | 329 | query: select_statement SEMI; 330 | 331 | schema_name: ID ; 332 | select_list: ( ( displayed_column ( COMMA displayed_column )* ) | 333 | ( ASTERISK ( COMMA displayed_column ( COMMA displayed_column )* )? ) 334 | ) ; 335 | select_statement: select_expression ( (UNION_SYM ( ALL )? ) select_expression )* ; 336 | 337 | simple_expr: 338 | literal_value 339 | | expression_list 340 | | column_spec 341 | | function_call 342 | | (ROW_SYM expression_list) 343 | | subquery 344 | | EXISTS subquery 345 | | interval_expr 346 | | match_against_statement 347 | | case_when_statement ; 348 | 349 | subquery: LPAREN select_statement RPAREN ; 350 | 351 | table_atom: 352 | ( table_spec ( partition_clause )? ( alias )? ( index_hint_list )? ) 353 | | ( subquery alias ) 354 | | ( LPAREN table_references RPAREN ) 355 | | ( OJ_SYM table_reference LEFT OUTER JOIN_SYM table_reference ON expression ) ; 356 | table_name: ID ; 357 | 358 | table_factor1: table_factor2 ( (INNER_SYM | CROSS)? JOIN_SYM table_atom ( join_condition )? )* ; 359 | table_factor2: table_factor3 ( STRAIGHT_JOIN table_atom ( ON expression )? )? ; 360 | table_factor3: table_factor4 ( ( LEFT | RIGHT ) ( OUTER )? JOIN_SYM table_factor4 join_condition )* ; 361 | table_factor4: table_atom ( NATURAL ( ( LEFT | RIGHT ) ( OUTER )? )? JOIN_SYM table_atom )? ; 362 | 363 | table_reference: table_factor1 ; //| table_atom ; 364 | table_references: table_reference ( COMMA table_reference )* ; 365 | table_spec: ( schema_name DOT )? table_name ; 366 | 367 | where_clause: WHERE expression ; 368 | -------------------------------------------------------------------------------- /src/queryparser/mysql/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .mysqlprocessor import MySQLQueryProcessor 4 | 5 | __all__ = ["MySQLQueryProcessor"] 6 | -------------------------------------------------------------------------------- /src/queryparser/mysql/mysqllisteners.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Put any MySQL specific listeners here. 4 | -------------------------------------------------------------------------------- /src/queryparser/mysql/mysqlprocessor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | MySQL processor. Its task is to check if a query has any syntax errors and 4 | to extract all accessed columns as well as keywords and functions being 5 | used in a query. 6 | 7 | """ 8 | 9 | from __future__ import (absolute_import, print_function) 10 | 11 | __all__ = ["MySQLQueryProcessor"] 12 | 13 | from .MySQLLexer import MySQLLexer 14 | from .MySQLParser import MySQLParser 15 | from .MySQLParserListener import MySQLParserListener 16 | 17 | from ..common import SQLQueryProcessor 18 | 19 | 20 | class MySQLQueryProcessor(SQLQueryProcessor): 21 | def __init__(self, query=None): 22 | super().__init__(MySQLLexer, MySQLParser, MySQLParserListener, '`', 23 | query) 24 | -------------------------------------------------------------------------------- /src/queryparser/postgresql/PostgreSQLLexer.g4: -------------------------------------------------------------------------------- 1 | 2 | lexer grammar PostgreSQLLexer; 3 | @ header { } 4 | 5 | fragment A_ : 'a' | 'A'; 6 | fragment B_ : 'b' | 'B'; 7 | fragment C_ : 'c' | 'C'; 8 | fragment D_ : 'd' | 'D'; 9 | fragment E_ : 'e' | 'E'; 10 | fragment F_ : 'f' | 'F'; 11 | fragment G_ : 'g' | 'G'; 12 | fragment H_ : 'h' | 'H'; 13 | fragment I_ : 'i' | 'I'; 14 | fragment J_ : 'j' | 'J'; 15 | fragment K_ : 'k' | 'K'; 16 | fragment L_ : 'l' | 'L'; 17 | fragment M_ : 'm' | 'M'; 18 | fragment N_ : 'n' | 'N'; 19 | fragment O_ : 'o' | 'O'; 20 | fragment P_ : 'p' | 'P'; 21 | fragment Q_ : 'q' | 'Q'; 22 | fragment R_ : 'r' | 'R'; 23 | fragment S_ : 's' | 'S'; 24 | fragment T_ : 't' | 'T'; 25 | fragment U_ : 'u' | 'U'; 26 | fragment V_ : 'v' | 'V'; 27 | fragment W_ : 'w' | 'W'; 28 | fragment X_ : 'x' | 'X'; 29 | fragment Y_ : 'y' | 'Y'; 30 | fragment Z_ : 'z' | 'Z'; 31 | 32 | ABS : A_ B_ S_ ; 33 | ACOS : A_ C_ O_ S_ ; 34 | ALL : A_ L_ L_ ; 35 | ANY : A_ N_ Y_ ; 36 | ASC : A_ S_ C_ ; 37 | ASIN : A_ S_ I_ N_ ; 38 | AS_SYM : A_ S_ ; 39 | ATAN : A_ T_ A_ N_ ; 40 | ATAN2 : A_ T_ A_ N_ '2' ; 41 | AVG : A_ V_ G_; 42 | BETWEEN : B_ E_ T_ W_ E_ E_ N_ ; 43 | BIGINT : B_ I_ G_ I_ N_ T_ ; 44 | BINARY : B_ I_ N_ A_ R_ Y_ ; 45 | BIT_AND : B_ I_ T_ '_' A_ N_ D_ ; 46 | BIT_LENGTH : B_ I_ T_ '_' L_ E_ N_ G_ T_ H_; 47 | BIT_OR : B_ I_ T_ '_' O_ R_ ; 48 | BOOLEAN_SYM : B_ O_ O_ L_ E_ A_ N_ ; 49 | BY_SYM : B_ Y_ ; 50 | CASE_SYM : C_ A_ S_ E_ ; 51 | CAST_SYM : C_ A_ S_ T_ ; 52 | CBRT : C_ B_ R_ T_ ; 53 | CEIL : C_ E_ I_ L_ ; 54 | CEILING : C_ E_ I_ L_ I_ N_ G_ ; 55 | CHAR : C_ H_ A_ R_ ; 56 | CHR : C_ H_ R_ ; 57 | CHAR_LENGTH : (C_ H_ A_ R_ '_' L_ E_ N_ G_ T_ H_) | (C_ H_ A_ R_ A_ C_ T_ E_ R_ '_' L_ E_ N_ G_ T_ H_) ; 58 | CONCAT : C_ O_ N_ C_ A_ T_ ; 59 | CONCAT_WS : C_ O_ N_ C_ A_ T_ '_' W_ S_ ; 60 | CONVERT_SYM : C_ O_ N_ V_ E_ R_ T_ ; 61 | COS : C_ O_ S_ ; 62 | COT : C_ O_ T_ ; 63 | COUNT : C_ O_ U_ N_ T_ ; 64 | CROSS : C_ R_ O_ S_ S_ ; 65 | DATE_PART : D_ A_ T_ E_ '_' P_ A_ R_ T_ ; 66 | DATE_SYM : D_ A_ T_ E_ ; 67 | DATETIME : D_ A_ T_ E_ T_ I_ M_ E_ ; 68 | DAY_SYM : D_ A_ Y_ ; 69 | DECIMAL_SYM : D_ E_ C_ I_ M_ A_ L_ ; 70 | DEGREES : D_ E_ G_ R_ E_ E_ S_ ; 71 | DESC : D_ E_ S_ C_ ; 72 | DIV : D_ I_ V_ ; 73 | DISTINCT : D_ I_ S_ T_ I_ N_ C_ T_ ; 74 | DOUBLE_PRECISION_SYM: D_ O_ U_ B_ L_ E_ ' ' P_ R_ E_ C_ I_ S_ I_ O_ N_ ; 75 | ELSE_SYM : E_ L_ S_ E_ ; 76 | ENCODE : E_ N_ C_ O_ D_ E_ ; 77 | END_SYM : E_ N_ D_ ; 78 | ESCAPE_SYM : E_ S_ C_ A_ P_ E_ ; 79 | EXISTS : E_ X_ I_ S_ T_ S_ ; 80 | EXP : E_ X_ P_ ; 81 | EXPANSION_SYM : E_ X_ P_ A_ N_ S_ I_ O_ N_ ; 82 | FALSE_SYM : F_ A_ L_ S_ E_ ; 83 | FIRST_SYM : F_ I_ R_ S_ T_ ; 84 | FLOAT : F_ L_ O_ A_ T_ ; 85 | FLOOR : F_ L_ O_ O_ R_ ; 86 | FOR_SYM : F_ O_ R_ ; 87 | FORCE_SYM : F_ O_ R_ C_ E_ ; 88 | FROM : F_ R_ O_ M_ ; 89 | GAIA_HEALPIX_INDEX : G_ A_ I_ A_ '_' H_ E_ A_ L_ P_ I_ X_ '_' I_ N_ D_ E_ X_ ; 90 | GROUP_SYM : G_ R_ O_ U_ P_ ; 91 | HAVING : H_ A_ V_ I_ N_ G_ ; 92 | IGNORE_SYM : I_ G_ N_ O_ R_ E_ ; 93 | ILIKE_SYM : I_ L_ I_ K_ E_ ; 94 | INDEX_SYM : I_ N_ D_ E_ X_ ; 95 | INNER_SYM : I_ N_ N_ E_ R_ ; 96 | INTEGER_SYM : I_ N_ T_ E_ G_ E_ R_ ; 97 | INTERVAL_SYM : I_ N_ T_ E_ R_ V_ A_ L_ ; 98 | IN_SYM : I_ N_ ; 99 | ISNULL : I_ S_ N_ U_ L_ L_ ; 100 | IS_SYM : I_ S_ ; 101 | JOIN_SYM : J_ O_ I_ N_ ; 102 | KEY_SYM : K_ E_ Y_ ; 103 | LAST_SYM : L_ A_ S_ T_ ; 104 | LEFT : L_ E_ F_ T_ ; 105 | LENGTH : (L_ E_ N_ G_ T_ H_) | (O_ C_ T_ E_ T_ '_' L_ E_ N_ G_ T_ H_) ; 106 | LIKE_SYM : L_ I_ K_ E_ ; 107 | LIMIT : L_ I_ M_ I_ T_ ; 108 | LN : L_ N_ ; 109 | LOG : L_ O_ G_ ; 110 | LOWER : (L_ O_ W_ E_ R_) | (L_ C_ A_ S_ E_) ; 111 | LPAD : L_ P_ A_ D_ ; 112 | LTRIM : L_ T_ R_ I_ M_ ; 113 | MAX_SYM : M_ A_ X_ ; 114 | MD5 : M_ D_ '5' ; 115 | MINUTE : M_ I_ N_ U_ T_ E_ ; 116 | MIN_SYM : M_ I_ N_ ; 117 | MOD : M_ O_ D_ ; 118 | MODE_SYM : M_ O_ D_ E_ ; 119 | NATURAL : N_ A_ T_ U_ R_ A_ L_ ; 120 | NOT_SYM : (N_ O_ T_) | ('!') ; 121 | NOTNULL : N_ O_ T_ N_ U_ L_ L_ ; 122 | NOW : (N_ O_ W_) | (L_ O_ C_ A_ L_ T_ I_ M_ E_) | (L_ O_ C_ A_ L_ T_ I_ M_ E_ S_ T_ A_ M_ P_) | (C_ U_ R_ R_ E_ N_ T_ '_' T_ I_ M_ E_ S_ T_ A_ M_ P_); 123 | NULL_SYM : N_ U_ L_ L_ ; 124 | NULLS_SYM : N_ U_ L_ L_ S_ ; 125 | OFFSET_SYM : O_ F_ F_ S_ E_ T_ ; 126 | OJ_SYM : O_ J_ ; 127 | ON : O_ N_ ; 128 | ORDER_SYM : O_ R_ D_ E_ R_ ; 129 | OUTER : O_ U_ T_ E_ R_ ; 130 | PARTITION_SYM : P_ A_ R_ T_ I_ T_ I_ O_ N_ ; 131 | PDIST : P_ D_ I_ S_ T_ ; 132 | PI : P_ I_ ; 133 | POINT : P_ O_ I_ N_ T_ ; 134 | POLYGON : P_ O_ L_ Y_ G_ O_ N_ ; 135 | POSITION_SYM : P_ O_ S_ I_ T_ I_ O_ N_ ; 136 | POW : P_ O_ W_ ; 137 | POWER : P_ O_ W_ E_ R_ ; 138 | QUERY_SYM : Q_ U_ E_ R_ Y_ ; 139 | RADIANS : R_ A_ D_ I_ A_ N_ S_ ; 140 | RANDOM : R_ A_ N_ D_ O_ M_ ; 141 | REAL : R_ E_ A_ L_ ; 142 | REGEXP : (R_ E_ G_ E_ X_ P_) | (R_ L_ I_ K_ E_); 143 | REPEAT : R_ E_ P_ E_ A_ T_ ; 144 | REPLACE : R_ E_ P_ L_ A_ C_ E_ ; 145 | REVERSE : R_ E_ V_ E_ R_ S_ E_ ; 146 | RIGHT : R_ I_ G_ H_ T_ ; 147 | ROLLUP_SYM : R_ O_ L_ L_ U_ P_ ; 148 | ROUND : R_ O_ U_ N_ D_ ; 149 | ROW_SYM : R_ O_ W_ ; 150 | RPAD : R_ P_ A_ D_ ; 151 | RTRIM : R_ T_ R_ I_ M_ ; 152 | SECOND : S_ E_ C_ O_ N_ D_ ; 153 | SELECT : S_ E_ L_ E_ C_ T_ ; 154 | SHARE_SYM : S_ H_ A_ R_ E_ ; 155 | SIGN : S_ I_ G_ N_ ; 156 | SIGNED_SYM : S_ I_ G_ N_ E_ D_ ; 157 | SIN : S_ I_ N_ ; 158 | SOUNDS_SYM : S_ O_ U_ N_ D_ S_ ; 159 | SQUARE_DEGREES : S_ Q_ U_ A_ R_ E_ '_' D_ E_ G_ R_ E_ E_ S_ ; 160 | SQRT : S_ Q_ R_ T_ ; 161 | STDDEV : S_ T_ D_ D_ E_ V_ ; 162 | STDDEV_POP : S_ T_ D_ D_ E_ V_ '_' P_ O_ P_ ; 163 | STDDEV_SAMP : S_ T_ D_ D_ E_ V_ '_' S_ A_ M_ P_ ; 164 | STERADIANS : S_ T_ E_ R_ A_ D_ I_ A_ N_ S_ ; 165 | STRAIGHT_JOIN : S_ T_ R_ A_ I_ G_ H_ T_ '_' J_ O_ I_ N_ ; 166 | SUBSTRING : (S_ U_ B_ S_ T_ R_ I_ N_ G_) | (S_ U_ B_ S_ T_ R_) ; 167 | SUM : S_ U_ M_ ; 168 | SYMMETRIC : S_ Y_ M_ M_ E_ T_ R_ I_ C_ ; 169 | TAN : T_ A_ N_ ; 170 | THEN_SYM : T_ H_ E_ N_ ; 171 | TIME_SYM : T_ I_ M_ E_ ; 172 | TIMESTAMP : T_ I_ M_ E_ S_ T_ A_ M_ P_ ; 173 | TRUE_SYM : T_ R_ U_ E_ ; 174 | TRUNCATE : T_ R_ U_ N_ C_ A_ T_ E_ ; 175 | UDF_0 : U_ D_ F_ '_' '0' ; 176 | UDF_1 : U_ D_ F_ '_' '1' ; 177 | UDF_2 : U_ D_ F_ '_' '2' ; 178 | UDF_3 : U_ D_ F_ '_' '3' ; 179 | UDF_4 : U_ D_ F_ '_' '4' ; 180 | UDF_5 : U_ D_ F_ '_' '5' ; 181 | UDF_6 : U_ D_ F_ '_' '6' ; 182 | UDF_7 : U_ D_ F_ '_' '7' ; 183 | UDF_8 : U_ D_ F_ '_' '8' ; 184 | UDF_9 : U_ D_ F_ '_' '9' ; 185 | UNION_SYM : U_ N_ I_ O_ N_ ; 186 | UNSIGNED_SYM : U_ N_ S_ I_ G_ N_ E_ D_ ; 187 | UPDATE : U_ P_ D_ A_ T_ E_ ; 188 | UPPER : (U_ P_ P_ E_ R_) | (U_ C_ A_ S_ E_) ; 189 | USE_SYM : U_ S_ E_ ; 190 | USING_SYM : U_ S_ I_ N_ G_ ; 191 | UTC_DATE : U_ T_ C_ '_' D_ A_ T_ E_ ; 192 | UTC_TIME : U_ T_ C_ '_' T_ I_ M_ E_ ; 193 | UTC_TIMESTAMP : U_ T_ C_ '_' T_ I_ M_ E_ S_ T_ A_ M_ P_ ; 194 | VALUES : V_ A_ L_ U_ E_ S_ ; 195 | VARIANCE : V_ A_ R_ I_ A_ N_ C_ E_ ; 196 | VAR_POP : V_ A_ R_ '_' P_ O_ P_ ; 197 | VAR_SAMP : V_ A_ R_ '_' S_ A_ M_ P_ ; 198 | WHEN_SYM : W_ H_ E_ N_ ; 199 | WHERE : W_ H_ E_ R_ E_ ; 200 | WITH : W_ I_ T_ H_ ; 201 | YEAR : Y_ E_ A_ R_ ; 202 | 203 | // character sets 204 | ASCII_SYM : A_ S_ C_ I_ I_ ; 205 | LATIN1 : L_ A_ T_ I_ N_ '1' ; 206 | UTF8 : U_ T_ F_ '8' ; 207 | 208 | //pg_sphere 209 | SPOINT : S_ P_ O_ I_ N_ T_ ; 210 | SCIRCLE : S_ C_ I_ R_ C_ L_ E_ ; 211 | SLINE : S_ L_ I_ N_ E_ ; 212 | SELLIPSE : S_ E_ L_ L_ I_ P_ S_ E_ ; 213 | SPOLY : S_ P_ O_ L_ Y_ ; 214 | SPATH : S_ P_ A_ T_ H_ ; 215 | SBOX : S_ B_ O_ X_ ; 216 | STRANS : S_ T_ R_ A_ N_ S_ ; 217 | AREA : A_ R_ E_ A_ ; 218 | 219 | ARRAY_LENGTH : A_ R_ R_ A_ Y_ '_' L_ E_ N_ G_ T_ H_ ; 220 | SPOINT_TO_ARRAY : S_ P_ O_ I_ N_ T_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ ; 221 | SBOX_TO_ARRAY : S_ B_ O_ X_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ ; 222 | SCIRCLE_TO_ARRAY : S_ C_ I_ R_ C_ L_ E_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ ; 223 | SPOLY_TO_ARRAY : S_ P_ O_ L_ Y_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ ; 224 | SPOINT_TO_ARRAY_DEG : S_ P_ O_ I_ N_ T_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ '_' D_ E_ G_ ; 225 | SBOX_TO_ARRAY_DEG : S_ B_ O_ X_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ '_' D_ E_ G_ ; 226 | SCIRCLE_TO_ARRAY_DEG : S_ C_ I_ R_ C_ L_ E_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ '_' D_ E_ G_ ; 227 | SPOLY_TO_ARRAY_DEG : S_ P_ O_ L_ Y_ '_' T_ O_ '_' A_ R_ R_ A_ Y_ '_' D_ E_ G_ ; 228 | 229 | // basic token definition ----------------------------------------------------- 230 | 231 | DIVIDE : ( D_ I_ V_ ) | '/' ; 232 | MOD_SYM : ( M_ O_ D_ ) | '%' ; 233 | OR_SYM : ( O_ R_ ) | '||'; 234 | AND_SYM : ( A_ N_ D_ ) | '&&'; 235 | 236 | ABS_VAL_OR_SCONTAINS : '@'; 237 | DFACTORIAL : '!!'; 238 | 239 | EQ : '=' | '<=>' ; 240 | NOT_EQ : '<>' | '!=' | '~='| '^='; 241 | LET : '<=' ; 242 | GET : '>=' ; 243 | SHIFT_LEFT : '<<' ; 244 | SHIFT_RIGHT : '>>' ; 245 | 246 | SEMI : ';' ; 247 | COLON : ':' ; 248 | DOT : '.' ; 249 | COMMA : ',' ; 250 | ASTERISK : '*' ; 251 | RPAREN : ')' ; 252 | LPAREN : '(' ; 253 | RBRACK : ']' ; 254 | LBRACK : '[' ; 255 | PLUS : '+' ; 256 | MINUS : '-' ; 257 | NEGATION : '~' ; 258 | VERTBAR : '|' ; 259 | BITAND : '&' ; 260 | POWER_OP : '^' ; 261 | GTH : '>' ; 262 | LTH : '<' ; 263 | 264 | // pg_sphere operators - those that are commented out are already used by postgres 265 | 266 | //SCONTAINS : '@' ; 267 | SCONTAINS2 : '<@' ; 268 | //SLEFTCONTAINS : '~' ; 269 | SLEFTCONTAINS2 : '@>' ; 270 | SNOTCONTAINS : '!@' ; 271 | SNOTCONTAINS2 : '!<@' ; 272 | SLEFTNOTCONTAINS : '!~' ; 273 | SLEFTNOTCONTAINS2 : '!@>' ; 274 | //SOVERLAP : '&&' ; 275 | SNOTOVERLAP : '!&&' ; 276 | SCROSS : '#' ; 277 | SDISTANCE : '<->' ; 278 | SLENGTH : '@-@' ; 279 | SCENTER : '@@' ; 280 | 281 | INTEGER_NUM : ('0'..'9')+ ; 282 | 283 | fragment HEX_DIGIT_FRAGMENT: ( 'a'..'f' | 'A'..'F' | '0'..'9' ) ; 284 | HEX_DIGIT: 285 | ( '0x' (HEX_DIGIT_FRAGMENT)+ ) 286 | | 287 | ( 'X' '\'' (HEX_DIGIT_FRAGMENT)+ '\'' ) 288 | ; 289 | 290 | BIT_NUM: 291 | ( '0b' ('0'|'1')+ ) 292 | | 293 | ( B_ '\'' ('0'|'1')+ '\'' ) 294 | ; 295 | 296 | REAL_NUMBER: 297 | ( INTEGER_NUM DOT INTEGER_NUM | INTEGER_NUM DOT | DOT INTEGER_NUM | INTEGER_NUM ) 298 | ( ('E'|'e') ( PLUS | MINUS )? INTEGER_NUM )? 299 | ; 300 | 301 | TRANS: 302 | '\''( 'X' | 'Y' | 'Z' )( 'X' | 'Y' | 'Z' )( 'X' | 'Y' | 'Z' )'\'' 303 | ; 304 | 305 | TEXT_STRING: 306 | ( N_ | ('_' U_ T_ F_ '8') )? 307 | ( 308 | ( '\'' ( ('\\' '\\') | ('\'' '\'') | ('\\' '\'') | ~('\'') )* '\'' ) 309 | 310 | ) 311 | ; 312 | 313 | ID: 314 | (( 'A'..'Z' | 'a'..'z' | '_' | '$' ) ( 'A'..'Z' | 'a'..'z' | '_' | '$' | '0'..'9' )*) | 315 | ( '"' ( ( '\u0023' .. '\uffff' ) | ( '\u0020' ) )+ '"' ) 316 | ; 317 | 318 | COMMENT: '--' ~( '\r' | '\n' )* -> skip ; 319 | 320 | // all unicode whitespace characters 321 | WS 322 | : ( ' ' | '\t' | '\n' | '\r' | '\u000b' | '\u000c' | 323 | '\u0085' | '\u00a0' | '\u1680' | '\u2000' | '\u2001' | '\u2002' | '\u2003' | 324 | '\u2004' | '\u2005' | '\u2006' | '\u2007' | '\u2008' | '\u2009' | '\u200a' | '\u2028' | 325 | '\u2029' | '\u202f' | '\u205f' | '\u3000' )+ -> channel(HIDDEN) 326 | ; 327 | 328 | -------------------------------------------------------------------------------- /src/queryparser/postgresql/PostgreSQLParser.g4: -------------------------------------------------------------------------------- 1 | 2 | parser grammar PostgreSQLParser; 3 | 4 | options 5 | { tokenVocab = PostgreSQLLexer; } 6 | 7 | ////////////////////////////////////////////////////////////////////////////// 8 | 9 | relational_op: 10 | EQ | LTH | GTH | NOT_EQ | LET | GET; 11 | 12 | cast_data_type: 13 | BINARY (LPAREN INTEGER_NUM RPAREN)? 14 | | CHAR (LPAREN INTEGER_NUM RPAREN)? 15 | | DATE_SYM 16 | | DATETIME 17 | | TIME_SYM 18 | | TIMESTAMP 19 | | INTERVAL_SYM 20 | | DECIMAL_SYM (LPAREN INTEGER_NUM (COMMA INTEGER_NUM)? RPAREN)? 21 | | INTEGER_SYM 22 | | BIGINT 23 | | FLOAT 24 | | REAL 25 | | DOUBLE_PRECISION_SYM; 26 | 27 | ////////////////////////////////////////////////////////////////////////////// 28 | // LITERALS 29 | 30 | bit_literal: BIT_NUM ; 31 | boolean_literal: TRUE_SYM | FALSE_SYM ; 32 | hex_literal: HEX_DIGIT ; 33 | number_literal: ( PLUS | MINUS )? ( INTEGER_NUM | REAL_NUMBER ) ; 34 | string_literal: TEXT_STRING ; 35 | 36 | 37 | ////////////////////////////////////////////////////////////////////////////// 38 | // FUNCTIONS 39 | 40 | char_functions: 41 | ASCII_SYM | BIT_LENGTH | CHAR_LENGTH | CHR | CONCAT_WS | CONCAT 42 | | LEFT | LENGTH | LOWER | LPAD | LTRIM | REPEAT | REPLACE | REVERSE 43 | | RIGHT | RPAD | RTRIM | SUBSTRING | UPPER ; 44 | 45 | group_functions: 46 | AVG | COUNT | MAX_SYM | MIN_SYM | SUM | BIT_AND | BIT_OR 47 | | STDDEV | STDDEV_POP | STDDEV_SAMP | VAR_POP | VAR_SAMP | VARIANCE ; 48 | 49 | number_functions: 50 | ABS | ACOS | ASIN | ATAN2 | ATAN | CBRT | CEIL | CEILING | COS | COT 51 | | DEGREES | DIV | EXP | FLOOR | LN | LOG | MOD | PI | POW 52 | | POWER | RADIANS | RANDOM | ROUND | SIGN | SIN | SQUARE_DEGREES | SQRT 53 | | STERADIANS | TAN | TRUNCATE ; 54 | 55 | other_functions: 56 | ENCODE | MD5 ; 57 | 58 | time_functions: 59 | DATE_PART | DATE_SYM | NOW | SECOND | TIME_SYM | TIMESTAMP 60 | | UTC_DATE | UTC_TIME | UTC_TIMESTAMP | YEAR ; 61 | 62 | array_functions: 63 | ARRAY_LENGTH ; 64 | 65 | custom_functions: 66 | GAIA_HEALPIX_INDEX | PDIST | UDF_0 | UDF_1 | UDF_2 | UDF_3 | UDF_4 | UDF_5 | UDF_6 | UDF_7 | UDF_8 | UDF_9 ; 67 | 68 | pg_sphere_functions: 69 | AREA ; 70 | 71 | functionList: 72 | number_functions | char_functions | time_functions | other_functions 73 | | pg_sphere_functions | array_functions | custom_functions; 74 | 75 | literal_value: 76 | string_literal | number_literal | hex_literal | boolean_literal 77 | | bit_literal | NULL_SYM ; 78 | 79 | 80 | ////////////////////////////////////////////////////////////////////////////// 81 | // MAIN 82 | 83 | select_expression: 84 | SELECT 85 | ( ALL | DISTINCT )? 86 | select_list 87 | 88 | ( 89 | FROM table_references 90 | ( partition_clause )? 91 | ( where_clause )? 92 | ( groupby_clause )? 93 | ( having_clause )? 94 | ) ? 95 | 96 | ( orderby_clause )? 97 | ( limit_clause )? 98 | ( offset_clause )? 99 | ( FOR_SYM UPDATE )? 100 | 101 | SEMI? 102 | ; 103 | 104 | 105 | ////////////////////////////////////////////////////////////////////////////// 106 | // TOKENS 107 | 108 | alias: ( AS_SYM )? ID ; 109 | bit_expr: factor1 ( VERTBAR factor1 )? ; 110 | 111 | bool_primary: 112 | predicate ( relational_op ( ANY )? predicate )? | ( ( NOT_SYM )? EXISTS subquery); 113 | 114 | case_when_statement: CASE_SYM ( case_when_statement1 | case_when_statement2 ) 115 | ( ELSE_SYM bit_expr )? END_SYM; 116 | case_when_statement1: ( WHEN_SYM expression THEN_SYM bit_expr )+ ; 117 | case_when_statement2: bit_expr ( WHEN_SYM bit_expr THEN_SYM bit_expr )+ ; 118 | column_list: LPAREN column_spec ( COMMA column_spec )* RPAREN ; 119 | column_name: ID; 120 | column_spec: ( ( schema_name DOT )? table_name DOT )? column_name ( slice_spec )?; 121 | 122 | displayed_column : ( table_spec DOT ASTERISK ) 123 | | ( ( bit_expr | sbit_expr | displayed_column_arr ) ( ( LIKE_SYM | ILIKE_SYM ) TEXT_STRING )? ( alias )? ) ; 124 | 125 | displayed_column_arr : 126 | ( spoint_to_array_deg | spoint_to_array 127 | | sbox_to_array_deg | sbox_to_array 128 | | scircle_to_array_deg | scircle_to_array 129 | | spoly_to_array_deg | spoly_to_array); 130 | 131 | exp_factor1: exp_factor2 ( AND_SYM exp_factor2 )* ; 132 | exp_factor2: ( NOT_SYM )? exp_factor3 ; 133 | exp_factor3: bool_primary ( ( IS_SYM ( NOT_SYM )? (boolean_literal | NULL_SYM | ( DISTINCT FROM ) ) )? 134 | | ( ISNULL | NOTNULL )? 135 | ); 136 | expression: ( exp_factor1 ( OR_SYM exp_factor1 )* ) ; 137 | expression_list: LPAREN expression ( COMMA expression )* RPAREN ; 138 | 139 | factor1: factor2 ( BITAND factor2 )? ; 140 | factor2: factor3 ( ( SHIFT_LEFT | SHIFT_RIGHT ) factor3 )? ; 141 | factor3: factor4 ( ( PLUS | MINUS ) factor4 )* ; 142 | factor4: factor5 ( ( ASTERISK | DIVIDE | MOD_SYM | POWER_OP) factor5 )* ; 143 | factor5: ( PLUS | MINUS | NEGATION | BINARY | ABS_VAL_OR_SCONTAINS | DFACTORIAL )? simple_expr ( NOT_SYM )? ( ( PLUS | MINUS ) interval_expr )? ; 144 | 145 | function_call: 146 | ( functionList ( LPAREN ( expression ( COMMA expression )* )? RPAREN ) ? ) 147 | | ( CAST_SYM LPAREN expression AS_SYM cast_data_type RPAREN ) 148 | | ( CONVERT_SYM LPAREN TEXT_STRING COMMA TEXT_STRING COMMA TEXT_STRING RPAREN ) 149 | | ( POSITION_SYM LPAREN expression IN_SYM expression RPAREN ) 150 | | ( group_functions LPAREN ( ASTERISK | ALL | DISTINCT )? ( ( bit_expr | sbit_expr ) )? RPAREN ) ; 151 | 152 | groupby_clause: GROUP_SYM BY_SYM groupby_item ( COMMA groupby_item )* ( WITH ROLLUP_SYM )? ; 153 | groupby_item: (column_spec | INTEGER_NUM | bit_expr ) ( ASC | DESC )?; 154 | 155 | having_clause: HAVING expression ; 156 | 157 | 158 | index_hint: 159 | USE_SYM index_options LPAREN ( index_list )? RPAREN 160 | | IGNORE_SYM index_options LPAREN index_list RPAREN 161 | | FORCE_SYM index_options LPAREN index_list RPAREN ; 162 | index_hint_list: index_hint ( COMMA index_hint )* ; 163 | index_name: ID ; 164 | index_list: index_name ( COMMA index_name )* ; 165 | index_options: ( INDEX_SYM | KEY_SYM ) ( FOR_SYM (( JOIN_SYM ) | ( ORDER_SYM BY_SYM ) | ( GROUP_SYM BY_SYM )) )? ; 166 | 167 | interval_expr: INTERVAL_SYM string_literal; 168 | 169 | join_condition: ( ON expression ) | ( USING_SYM column_list ) ; 170 | 171 | limit_clause: ( LIMIT row_count ( OFFSET_SYM offset )? ) | ( OFFSET_SYM offset LIMIT row_count ); 172 | 173 | offset: INTEGER_NUM ; 174 | offset_clause: OFFSET_SYM offset ; 175 | row_count: INTEGER_NUM ; 176 | 177 | orderby_clause: ORDER_SYM BY_SYM orderby_item ( COMMA orderby_item )* ; 178 | orderby_item: groupby_item ( ( ASC | DESC )? | ( NULLS_SYM ( FIRST_SYM | LAST_SYM ) )? ) | groupby_item USING_SYM ( GTH | LTH ); 179 | 180 | partition_clause: PARTITION_SYM LPAREN partition_names RPAREN ; 181 | partition_name: ID ; 182 | partition_names: partition_name ( COMMA partition_name )* ; 183 | 184 | bit_fac1: 185 | ( NOT_SYM )? ( 186 | (IN_SYM ( subquery | expression_list )) 187 | | ((LIKE_SYM | ILIKE_SYM) simple_expr ( ESCAPE_SYM simple_expr )?) 188 | | (REGEXP ( bit_expr | sbit_expr )) 189 | | (BETWEEN ( SYMMETRIC )? ( bit_expr | sbit_expr ) AND_SYM predicate) 190 | ) ; 191 | 192 | bit_fac2: (SOUNDS_SYM ( LIKE_SYM | ILIKE_SYM ) ( bit_expr | sbit_expr )); 193 | predicate: 194 | ( bit_expr | sbit_expr ) (( bit_fac1 | bit_fac2)?) ; 195 | 196 | query: select_statement SEMI; 197 | 198 | schema_name: ID ; 199 | select_list: ( displayed_column ( COMMA displayed_column )* ) | 200 | ( ASTERISK ( COMMA displayed_column ( COMMA displayed_column )* )? ) | 201 | ( ON (subselect_list) ( COMMA displayed_column )* ); 202 | subselect_list: ( displayed_column ( COMMA displayed_column )* ); 203 | select_statement: select_expression ( (UNION_SYM ( ALL )? ) select_expression )* ; 204 | 205 | simple_expr: 206 | literal_value 207 | | expression_list 208 | | column_spec 209 | | function_call 210 | | (ROW_SYM expression_list) 211 | | subquery 212 | | EXISTS subquery 213 | | interval_expr 214 | | case_when_statement ; 215 | 216 | slice_spec: ( LBRACK INTEGER_NUM ( COLON INTEGER_NUM )? RBRACK )+; 217 | 218 | subquery: LPAREN select_statement RPAREN ; 219 | 220 | table_atom: 221 | ( table_spec ( partition_clause )? ( alias )? ( index_hint_list )? ) 222 | | ( subquery alias ) 223 | | ( LPAREN table_references RPAREN ) 224 | | ( OJ_SYM table_reference LEFT OUTER JOIN_SYM table_reference ON expression ) ; 225 | table_name: ID ; 226 | 227 | table_factor1: table_factor2 ( (INNER_SYM | CROSS | LEFT | RIGHT)? JOIN_SYM table_atom ( join_condition )? )* ; 228 | table_factor2: table_factor3 ( STRAIGHT_JOIN table_atom ( ON expression )? )? ; 229 | table_factor3: table_factor4 ( ( LEFT | RIGHT ) ( OUTER )? JOIN_SYM table_factor4 join_condition )* ; 230 | table_factor4: table_atom ( NATURAL ( ( LEFT | RIGHT ) ( OUTER )? )? JOIN_SYM table_atom )? ; 231 | 232 | table_reference: table_factor1 | ( LPAREN values_list RPAREN ) alias ( column_list )? ; 233 | table_references: table_reference ( COMMA table_reference )* ; 234 | table_spec: ( schema_name DOT )? table_name ; 235 | 236 | values_list: VALUES ( expression_list ( COMMA expression_list )* ); 237 | 238 | where_clause: WHERE expression ; 239 | 240 | pg_sphere_op: 241 | ABS_VAL_OR_SCONTAINS | SCONTAINS2 | NEGATION | SLEFTCONTAINS2 | SNOTCONTAINS 242 | | SNOTCONTAINS2 | SLEFTNOTCONTAINS | SLEFTNOTCONTAINS2 | AND_SYM 243 | | SNOTOVERLAP ; 244 | 245 | 246 | sbit_expr: 247 | ( polygon SLEFTCONTAINS2 point ) 248 | | ( point SCONTAINS2 polygon ) 249 | | ( pg_sphere_object | spoint ) 250 | | ( ( spoint | simple_expr ) pg_sphere_op pg_sphere_object) 251 | | ( pg_sphere_object EQ pg_sphere_object ) 252 | | ( pg_sphere_object pg_sphere_op pg_sphere_object ) 253 | | ( sline | simple_expr SCROSS sline | simple_expr ) 254 | | ( ( spoint | scircle | simple_expr ) SDISTANCE ( spoint | scircle | simple_expr ) ) 255 | | ( SLENGTH ( scircle | sbox | spoly | simple_expr ) ) 256 | | ( SCENTER ( scircle | sellipse | simple_expr ) ) 257 | | ( MINUS ( sline | spath | simple_expr ) ) 258 | | ( ( spoint | scircle | sline | sellipse | spoly | spath | simple_expr ) ( ( PLUS | MINUS )? strans )+ ) ; 259 | 260 | 261 | polygon: POLYGON string_literal ; 262 | point: POINT LPAREN bit_expr COMMA bit_expr RPAREN ; 263 | spoint: SPOINT LPAREN bit_expr COMMA bit_expr RPAREN ; 264 | scircle: SCIRCLE LPAREN spoint COMMA bit_expr RPAREN ; 265 | sline: ( SLINE LPAREN spoint COMMA spoint RPAREN ) | ( SLINE LPAREN strans COMMA bit_expr RPAREN ); 266 | sellipse: SELLIPSE LPAREN spoint COMMA bit_expr COMMA bit_expr COMMA bit_expr RPAREN ; 267 | sbox: SBOX LPAREN spoint COMMA spoint RPAREN ; 268 | spoly: SPOLY TEXT_STRING| SPOLY LPAREN column_spec RPAREN | SPOLY LPAREN TEXT_STRING RPAREN; 269 | spath: SPATH TEXT_STRING | SPATH LPAREN column_spec RPAREN ; 270 | strans: STRANS LPAREN bit_expr COMMA bit_expr COMMA bit_expr COMMA TRANS RPAREN ; 271 | spoint_to_array: SPOINT_TO_ARRAY LPAREN spoint RPAREN ; 272 | spoint_to_array_deg: SPOINT_TO_ARRAY_DEG LPAREN spoint RPAREN ; 273 | sbox_to_array: SBOX_TO_ARRAY LPAREN sbox RPAREN ; 274 | sbox_to_array_deg: SBOX_TO_ARRAY_DEG LPAREN sbox RPAREN ; 275 | scircle_to_array: SCIRCLE_TO_ARRAY LPAREN scircle RPAREN ; 276 | scircle_to_array_deg: SCIRCLE_TO_ARRAY_DEG LPAREN scircle RPAREN ; 277 | spoly_to_array: SPOLY_TO_ARRAY LPAREN spoly RPAREN ; 278 | spoly_to_array_deg: SPOLY_TO_ARRAY_DEG LPAREN spoly RPAREN ; 279 | 280 | pg_sphere_object: scircle | sline | sellipse | sbox | spoly | spath | simple_expr; 281 | -------------------------------------------------------------------------------- /src/queryparser/postgresql/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .postgresqlprocessor import PostgreSQLQueryProcessor 4 | 5 | __all__ = ["PostgreSQLQueryProcessor"] 6 | -------------------------------------------------------------------------------- /src/queryparser/postgresql/postgresqllisteners.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Put any PostgreSQL specific listeners here. 4 | -------------------------------------------------------------------------------- /src/queryparser/postgresql/postgresqlprocessor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | PostgreSQL processor. Its task is to check if a query has any syntax errors and 4 | to extract all accessed columns as well as keywords and functions being 5 | used in a query. 6 | 7 | """ 8 | 9 | from __future__ import absolute_import, print_function 10 | 11 | __all__ = ["PostgreSQLQueryProcessor"] 12 | 13 | from ..common import SQLQueryProcessor 14 | from .PostgreSQLLexer import PostgreSQLLexer 15 | from .PostgreSQLParser import PostgreSQLParser 16 | from .PostgreSQLParserListener import PostgreSQLParserListener 17 | 18 | 19 | class PostgreSQLQueryProcessor(SQLQueryProcessor): 20 | def __init__(self, query=None): 21 | super().__init__( 22 | PostgreSQLLexer, PostgreSQLParser, PostgreSQLParserListener, '"', query 23 | ) 24 | -------------------------------------------------------------------------------- /src/queryparser/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | pytest.register_assert_rewrite('queryparser.testing.utils') 6 | -------------------------------------------------------------------------------- /src/queryparser/testing/test_adql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from queryparser.exceptions import QuerySyntaxError 4 | from .utils import _test_parsing 5 | 6 | from queryparser.adql import ADQLQueryTranslator 7 | from queryparser.mysql import MySQLQueryProcessor 8 | from queryparser.postgresql import PostgreSQLQueryProcessor 9 | 10 | import os 11 | import pytest 12 | import yaml 13 | 14 | with open(os.path.dirname(__file__) + '/tests.yaml') as f: 15 | tests = yaml.load(f, Loader=yaml.FullLoader) 16 | 17 | 18 | @pytest.mark.parametrize("t", tests['adql_mysql_tests']) 19 | def test_adql_mysql_translation(t): 20 | query, translated_query = t 21 | adt = ADQLQueryTranslator(query) 22 | if translated_query is not None: 23 | assert translated_query.strip() == adt.to_mysql() 24 | 25 | 26 | @pytest.mark.parametrize("t", tests['adql_postgresql_tests']) 27 | def test_adql_postgresql_parsing(t): 28 | _, postgres_query = t 29 | print(f"Failed query: {postgres_query}") 30 | PostgreSQLQueryProcessor(postgres_query) 31 | 32 | 33 | 34 | @pytest.mark.parametrize("t", tests['adql_postgresql_tests']) 35 | def test_adql_postgresql_translation(t): 36 | query, translated_query = t 37 | adt = ADQLQueryTranslator(query) 38 | if translated_query is not None: 39 | assert translated_query.strip() == adt.to_postgresql() 40 | 41 | 42 | @pytest.mark.parametrize("t", tests['common_translation_tests']) 43 | def test_adql_mysql_parsing_common(t): 44 | _test_parsing(MySQLQueryProcessor, t, translate=True) 45 | 46 | 47 | @pytest.mark.parametrize("t", tests['common_translation_tests']) 48 | def test_adql_postgresql_parsing_common(t): 49 | _test_parsing(PostgreSQLQueryProcessor, t, translate=True) 50 | 51 | 52 | @pytest.mark.parametrize("t", tests['common_syntax_tests']) 53 | def test_postgresql_syntax(t): 54 | with pytest.raises(QuerySyntaxError): 55 | ADQLQueryTranslator(t) 56 | 57 | -------------------------------------------------------------------------------- /src/queryparser/testing/test_mysql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .utils import _test_parsing 4 | 5 | from queryparser.exceptions import QueryError, QuerySyntaxError 6 | from queryparser.mysql import MySQLQueryProcessor 7 | 8 | import os 9 | import pytest 10 | import yaml 11 | 12 | 13 | with open(os.path.dirname(__file__) + '/tests.yaml') as f: 14 | tests = yaml.load(f, Loader=yaml.FullLoader) 15 | 16 | 17 | @pytest.mark.parametrize("t", tests['common_tests']) 18 | def test_mysql_parsing_common(t): 19 | _test_parsing(MySQLQueryProcessor, t) 20 | 21 | 22 | @pytest.mark.parametrize("t", tests['mysql_tests']) 23 | def test_mysql_parsing(t): 24 | _test_parsing(MySQLQueryProcessor, t) 25 | 26 | 27 | @pytest.mark.parametrize("t", tests['common_syntax_tests']) 28 | def test_mysql_syntax(t): 29 | with pytest.raises(QuerySyntaxError): 30 | MySQLQueryProcessor(t) 31 | 32 | @pytest.mark.parametrize("t", tests['common_query_tests']) 33 | def test_mysql_query(t): 34 | with pytest.raises(QueryError): 35 | MySQLQueryProcessor(t) 36 | -------------------------------------------------------------------------------- /src/queryparser/testing/test_postgresql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | import pytest 6 | import yaml 7 | 8 | from queryparser.exceptions import QueryError, QuerySyntaxError 9 | from queryparser.postgresql import PostgreSQLQueryProcessor 10 | 11 | from .utils import _test_failure_parsing, _test_parsing 12 | 13 | with open(os.path.dirname(__file__) + '/tests.yaml') as f: 14 | tests = yaml.load(f, Loader=yaml.FullLoader) 15 | 16 | 17 | @pytest.mark.parametrize('t', tests['common_tests']) 18 | def test_postgresql_parsing_common(t): 19 | _test_parsing(PostgreSQLQueryProcessor, t) 20 | 21 | 22 | @pytest.mark.parametrize('t', tests['postgresql_tests']) 23 | def test_postgresql_parsing(t): 24 | _test_parsing(PostgreSQLQueryProcessor, t) 25 | 26 | 27 | @pytest.mark.parametrize('t', tests['common_syntax_tests']) 28 | def test_postgresql_syntax(t): 29 | with pytest.raises(QuerySyntaxError): 30 | PostgreSQLQueryProcessor(t) 31 | 32 | 33 | @pytest.mark.parametrize('t', tests['common_query_tests']) 34 | def test_postrgresql_query(t): 35 | with pytest.raises(QueryError): 36 | PostgreSQLQueryProcessor(t) 37 | 38 | 39 | @pytest.mark.parametrize('t', tests['postgresql_failure_tests']) 40 | def test_postgresql_failure_parsing(t): 41 | _test_failure_parsing(PostgreSQLQueryProcessor, t) 42 | -------------------------------------------------------------------------------- /src/queryparser/testing/tests.yaml: -------------------------------------------------------------------------------- 1 | # Each test below consists of: 2 | # 3 | # - query string 4 | # - columns, each column in form db.tab.col 5 | # - SQL keywords used in the query 6 | # - SQL functions used in the query 7 | # - display columns in form col_name: db.tab.col 8 | # - tables accessed by the query 9 | # - schema replacement name (optional) 10 | 11 | common_tests: 12 | - 13 | - SELECT tab.a AS col1 FROM db.tab; 14 | - ['db.tab.a'] 15 | - 16 | - 17 | - ['col1: db.tab.a'] 18 | - ['db.tab'] 19 | - 20 | 21 | - 22 | - SELECT t.a FROM db.tab1 as t, db.tab2; 23 | - ['db.tab1.a'] 24 | - 25 | - 26 | - ['a: db.tab1.a'] 27 | - ['db.tab1', 'db.tab2'] 28 | - 29 | 30 | - 31 | - SELECT COUNT(*), a*2, b, 100 FROM db.tab; 32 | - ['db.tab.a', 'db.tab.b'] 33 | - 34 | - ['COUNT'] 35 | - ['a: db.tab.a', 'b: db.tab.b'] 36 | - ['db.tab'] 37 | - 38 | 39 | - 40 | - SELECT (((((((1+2)*3)/4)^5)%6)&7)>>8) FROM db.tab; 41 | - 42 | - 43 | - 44 | - 45 | - ['db.tab'] 46 | - 47 | 48 | - 49 | - SELECT ABS(a),AVG(b) FROM db.tab; 50 | - ['db.tab.a', 'db.tab.b'] 51 | - 52 | - ['AVG', 'ABS'] 53 | - ['a: db.tab.a', 'b: db.tab.b'] 54 | - ['db.tab'] 55 | - 56 | 57 | - 58 | - SELECT AVG(((((b & a) << 1) + 1) / a) ^ 4.5) FROM db.tab; 59 | - ['db.tab.a', 'db.tab.b'] 60 | - 61 | - ['AVG'] 62 | - 63 | - ['db.tab'] 64 | - 65 | 66 | - 67 | - SELECT A.a,B.* FROM db.tab1 A,db.tab2 AS B LIMIT 10; 68 | - ['db.tab1.a', 'db.tab2.*'] 69 | - ['limit', '*'] 70 | - 71 | - ['a: db.tab1.a', '*: db.tab2.*'] 72 | - ['db.tab1', 'db.tab2'] 73 | - 74 | 75 | - 76 | - SELECT fofid, x, y, z, vx, vy, vz 77 | FROM MDR1.FOF 78 | WHERE snapnum=85 79 | ORDER BY mass DESC 80 | LIMIT 20 81 | - ['MDR1.FOF.fofid', 'MDR1.FOF.x', 'MDR1.FOF.y', 'MDR1.FOF.z', 'MDR1.FOF.vx', 'MDR1.FOF.vy', 'MDR1.FOF.vz', 'MDR1.FOF.snapnum', 'MDR1.FOF.mass'] 82 | - ['where', 'order by', 'limit'] 83 | - 84 | - ['fofid: MDR1.FOF.fofid', 'x: MDR1.FOF.x', 'y: MDR1.FOF.y', 'z: MDR1.FOF.z', 'vx: MDR1.FOF.vx', 'vy: MDR1.FOF.vy', 'vz: MDR1.FOF.vz'] 85 | - ['MDR1.FOF'] 86 | - 87 | 88 | - 89 | - SELECT article, dealer, price 90 | FROM world.shop s 91 | WHERE price=(SELECT MAX(price) FROM universe.shop); 92 | - ['world.shop.article', 'world.shop.dealer', 'world.shop.price', 'universe.shop.price'] 93 | - ['where'] 94 | - ['MAX'] 95 | - ['article: world.shop.article', 'dealer: world.shop.dealer', 'price: world.shop.price'] 96 | - ['world.shop', 'universe.shop'] 97 | - 98 | 99 | - 100 | - SELECT dealer, price 101 | FROM db.shop s1 102 | WHERE price=(SELECT MAX(s2.price) 103 | FROM db.warehouse s2 104 | WHERE s1.article = s2.article 105 | AND s1.foo = s2.bar); 106 | - ['db.shop.article', 'db.shop.dealer', 'db.shop.price', 'db.warehouse.price', 'db.warehouse.article', 'db.shop.foo', 'db.warehouse.bar'] 107 | - ['where'] 108 | - ['MAX'] 109 | - ['price: db.shop.price', 'dealer: db.shop.dealer'] 110 | - ['db.shop', 'db.warehouse'] 111 | - 112 | 113 | - 114 | - SELECT A.*, B.* 115 | FROM db1.table1 A 116 | LEFT JOIN db2.table1 B 117 | ON A.id = B.id; 118 | - ['db1.table1.*', 'db2.table1.*'] 119 | - ['join', '*'] 120 | - 121 | - ['*: db1.table1.*', '*: db2.table1.*'] 122 | - ['db1.table1', 'db2.table1'] 123 | - 124 | 125 | - 126 | - SELECT * FROM mmm.products 127 | WHERE (price BETWEEN 1.0 AND 2.0) 128 | AND (quantity BETWEEN 1000 AND 2000); 129 | - ['mmm.products.*'] 130 | - ['WHERE', '*'] 131 | - 132 | - ['*: mmm.products.*'] 133 | - ['mmm.products'] 134 | - 135 | 136 | - 137 | - SELECT t.table_name AS tname, t.description AS tdesc, 138 | h.column_name AS hcol, 139 | j.column_name AS jcol, 140 | k.column_name AS kcol 141 | FROM tap_schema.tabs AS t 142 | JOIN ( 143 | SELECT table_name, column_name 144 | FROM tap_schema.cols 145 | WHERE ucd='phot.mag;em.IR.H' 146 | ) AS h USING (table_name) 147 | JOIN ( 148 | SELECT table_name, column_name 149 | FROM tap_schema.cols 150 | WHERE ucd='phot.mag;em.IR.J' 151 | ) AS j USING (table_name) 152 | JOIN ( 153 | SELECT table_name, column_name 154 | FROM tap_schema.cols 155 | WHERE ucd='phot.mag;em.IR.K' 156 | ) AS k USING (table_name) 157 | - ['tap_schema.tabs.table_name', 'tap_schema.tabs.description', 158 | 'tap_schema.cols.table_name', 'tap_schema.cols.column_name', 159 | 'tap_schema.cols.ucd'] 160 | - ['join', 'where'] 161 | - 162 | - ['tname: tap_schema.tabs.table_name', 163 | 'tdesc: tap_schema.tabs.description', 164 | 'hcol: tap_schema.cols.column_name', 165 | 'jcol: tap_schema.cols.column_name', 166 | 'kcol: tap_schema.cols.column_name'] 167 | - ['tap_schema.tabs', 'tap_schema.cols'] 168 | - 169 | 170 | - 171 | - SELECT t1.a FROM d.tab t1 172 | - ['foo.tab.a'] 173 | - 174 | - 175 | - ['a: foo.tab.a'] 176 | - ['foo.tab'] 177 | - 'd': 'foo' 178 | - 179 | 180 | - 181 | - SELECT DISTINCT t.table_name 182 | FROM tap_schema.tabs AS t 183 | JOIN tap_schema.cols AS c USING (table_name) 184 | WHERE (t.description LIKE '%qso%' OR t.description LIKE '%quasar%') 185 | AND c.ucd LIKE '%em.X-ray%' 186 | - ['tap_schema.tabs.table_name', 'tap_schema.cols.table_name', 187 | 'tap_schema.tabs.description', 'tap_schema.cols.ucd'] 188 | - ['join', 'where'] 189 | - 190 | - ['table_name: tap_schema.tabs.table_name'] 191 | - ['tap_schema.tabs', 'tap_schema.cols'] 192 | - 193 | 194 | - 195 | - SELECT s.* FROM db.person p INNER JOIN db.shirt s 196 | ON s.owner = p.id 197 | WHERE p.name LIKE 'Lilliana%' 198 | AND s.color <> 'white'; 199 | - ['db.shirt.*', 'db.person.id', 'db.person.name'] 200 | - ['join', 'where', '*'] 201 | - 202 | - ['*: db.shirt.*'] 203 | - ['db.shirt', 'db.person'] 204 | - 205 | 206 | - 207 | - SELECT x, y, z, mass 208 | FROM MDR1.FOF 209 | GROUP BY snapnum 210 | ORDER BY mass DESC 211 | LIMIT 10 212 | - ['MDR1.FOF.x', 'MDR1.FOF.y', 'MDR1.FOF.z', 'MDR1.FOF.mass', 213 | 'MDR1.FOF.snapnum'] 214 | - ['limit', 'order by', 'group by'] 215 | - 216 | - ['x: MDR1.FOF.x', 'y: MDR1.FOF.y', 'z: MDR1.FOF.z', 217 | 'mass: MDR1.FOF.mass'] 218 | - ['MDR1.FOF'] 219 | - 220 | 221 | - 222 | - SELECT h.Mvir, h.spin, g.diskMassStellar, 223 | g.diskMassStellar/h.Mvir AS mass_ratio 224 | FROM MDPL2.Rockstar AS h, MDPL2.Galacticus AS g 225 | WHERE g.rockstarId = h.rockstarId 226 | AND h.snapnum=125 AND g.snapnum=125 227 | AND h.Mvir>1.e10 228 | ORDER BY g.diskMassStellar/h.Mvir 229 | - ['MDPL2.Rockstar.Mvir', 'MDPL2.Galacticus.diskMassStellar', 230 | 'MDPL2.Rockstar.rockstarId', 'MDPL2.Galacticus.rockstarId', 231 | 'MDPL2.Rockstar.snapnum', 'MDPL2.Galacticus.snapnum', 232 | 'MDPL2.Rockstar.spin'] 233 | - ['where', 'order by'] 234 | - 235 | - ['Mvir: MDPL2.Rockstar.Mvir', 236 | 'diskMassStellar: MDPL2.Galacticus.diskMassStellar', 237 | 'spin: MDPL2.Rockstar.spin'] 238 | - ['MDPL2.Rockstar', 'MDPL2.Galacticus'] 239 | - 240 | 241 | - 242 | - SELECT bdmId, Rbin, mass, dens 243 | FROM Bolshoi.BDMVProf 244 | WHERE bdmId = 245 | (SELECT bdmId FROM Bolshoi.BDMV 246 | WHERE snapnum=416 ORDER BY Mvir DESC LIMIT 1) 247 | OR 248 | bdmId = 249 | (SELECT bdmId FROM Bolshoi.BDMV 250 | WHERE bdmId = 251 | (SELECT bdmId FROM Bolshoi.BDMV 252 | WHERE snapnum=416 ORDER BY Mvir DESC LIMIT 1) 253 | OR 254 | bdmId = 255 | (SELECT bdmId FROM Bolshoi.BDMV 256 | WHERE snapnum=AVG(Mvir)) 257 | ) 258 | ORDER BY Rbin 259 | - ['Bolshoi.BDMVProf.bdmId', 'Bolshoi.BDMVProf.Rbin', 260 | 'Bolshoi.BDMVProf.mass', 'Bolshoi.BDMVProf.dens', 261 | 'Bolshoi.BDMV.bdmId', 'Bolshoi.BDMV.snapnum', 262 | 'Bolshoi.BDMV.Mvir'] 263 | - ['where', 'order by', 'limit'] 264 | - ['AVG'] 265 | - ['bdmId: Bolshoi.BDMVProf.bdmId', 'Rbin: Bolshoi.BDMVProf.Rbin', 266 | 'mass: Bolshoi.BDMVProf.mass', 'dens: Bolshoi.BDMVProf.dens'] 267 | - ['Bolshoi.BDMVProf', 'Bolshoi.BDMV'] 268 | - 269 | 270 | - 271 | - SELECT t.RAVE_OBS_ID AS c1, t.HEALPix AS c2, 272 | h.logg_SC AS c3, h.TEFF AS c4 273 | FROM RAVEPUB_DR5.RAVE_DR5 AS t 274 | JOIN ( 275 | SELECT sc.RAVE_OBS_ID, logg_SC, k.TEFF 276 | FROM RAVEPUB_DR5.RAVE_Gravity_SC sc 277 | JOIN ( 278 | SELECT RAVE_OBS_ID, TEFF 279 | FROM RAVEPUB_DR5.RAVE_ON 280 | LIMIT 1000 281 | ) AS k USING (RAVE_OBS_ID) 282 | ) AS h USING (RAVE_OBS_ID) 283 | - ['RAVEPUB_DR5.RAVE_DR5.RAVE_OBS_ID', 284 | 'RAVEPUB_DR5.RAVE_DR5.HEALPix', 'RAVEPUB_DR5.RAVE_ON.TEFF', 285 | 'RAVEPUB_DR5.RAVE_Gravity_SC.logg_SC', 286 | 'RAVEPUB_DR5.RAVE_ON.RAVE_OBS_ID', 287 | 'RAVEPUB_DR5.RAVE_Gravity_SC.RAVE_OBS_ID'] 288 | - ['join', 'limit'] 289 | - 290 | - ['c1: RAVEPUB_DR5.RAVE_DR5.RAVE_OBS_ID', 291 | 'c2: RAVEPUB_DR5.RAVE_DR5.HEALPix', 292 | 'c3: RAVEPUB_DR5.RAVE_Gravity_SC.logg_SC', 293 | 'c4: RAVEPUB_DR5.RAVE_ON.TEFF'] 294 | - ['RAVEPUB_DR5.RAVE_DR5', 'RAVEPUB_DR5.RAVE_Gravity_SC', 295 | 'RAVEPUB_DR5.RAVE_ON'] 296 | - 297 | 298 | - 299 | - SELECT db.tab.a FROM db.tab; 300 | - ['db.tab.a'] 301 | - 302 | - 303 | - ['a: db.tab.a'] 304 | - ['db.tab'] 305 | - 306 | 307 | - 308 | - SELECT COUNT(*) AS n, id, mra, mlem AS qqq, blem 309 | FROM ( 310 | SELECT inner1.id, mra, mlem, 311 | inner2.col3 + inner2.parallax AS blem 312 | FROM ( 313 | SELECT qwerty.id, MAX(ra) AS mra, inner1.parallax, 314 | qwerty.mlem mlem 315 | FROM db.tab dbt 316 | JOIN ( 317 | SELECT rekt AS parallax, id, mlem 318 | FROM db.bar 319 | ) AS qwerty USING (id) 320 | ) AS inner1 321 | JOIN ( 322 | SELECT qqq, col2 AS ra2, parallax, subsub.col3 323 | FROM ( 324 | SELECT ra AS qqq, col2, col3, parallax 325 | FROM db.gaia AS gaia 326 | WHERE col5 > 5 327 | ) AS subsub 328 | ) AS inner2 329 | ON inner1.parallax = inner2.parallax 330 | ) AS subq 331 | GROUP BY id; 332 | - ['db.bar.id', 'db.bar.rekt', 'db.bar.mlem', 'db.tab.id', 333 | 'db.tab.ra', 'db.gaia.ra', 'db.gaia.col2', 334 | 'db.gaia.col3', 'db.gaia.parallax', 'db.gaia.col5'] 335 | - ['join', 'where', 'group by'] 336 | - ['MAX', 'COUNT'] 337 | - ['n: None.None.None', 'id: db.bar.id', 'mra: db.tab.ra', 338 | 'qqq: db.bar.mlem', 'blem: None.None.blem'] 339 | - ['db.tab', 'db.bar', 'db.gaia'] 340 | - 341 | 342 | - 343 | - SELECT 344 | g_min_ks_index / 10 AS g_min_ks, 345 | g_mag_abs_index / 10 AS g_mag_abs, 346 | count(*) AS n 347 | FROM ( 348 | SELECT gaia.source_id, 349 | floor((gaia.phot_g_mean_mag+5*log(gaia.parallax)-10) * 10) 350 | AS g_mag_abs_index, 351 | floor((gaia.phot_g_mean_mag-tmass.ks_m) * 10) 352 | AS g_min_ks_index 353 | FROM gaiadr1.tgas_source AS gaia 354 | INNER JOIN gaiadr1.tmass_best_neighbour AS xmatch 355 | ON gaia.source_id = xmatch.source_id 356 | INNER JOIN gaiadr1.tmass_original_valid AS tmass 357 | ON tmass.tmass_oid = xmatch.tmass_oid 358 | WHERE gaia.parallax/gaia.parallax_error >= 5 AND 359 | xmatch.ph_qual = 'AAA' AND 360 | sqrt(power(2.5 / log(10) * gaia.phot_g_mean_flux_error 361 | / gaia.phot_g_mean_flux, 2)) <= 0.05 AND 362 | sqrt(power(2.5 / log(10) * gaia.phot_g_mean_flux_error 363 | / gaia.phot_g_mean_flux, 2) 364 | + power(tmass.ks_msigcom, 2)) <= 0.05 365 | ) AS subquery 366 | GROUP BY g_min_ks_index, g_mag_abs_index 367 | - ['gaiadr1.tgas_source.source_id', 368 | 'gaiadr1.tgas_source.parallax', 369 | 'gaiadr1.tgas_source.parallax_error', 370 | 'gaiadr1.tgas_source.phot_g_mean_flux', 371 | 'gaiadr1.tgas_source.phot_g_mean_flux_error', 372 | 'gaiadr1.tgas_source.phot_g_mean_mag', 373 | 'gaiadr1.tmass_best_neighbour.ph_qual', 374 | 'gaiadr1.tmass_best_neighbour.source_id', 375 | 'gaiadr1.tmass_best_neighbour.tmass_oid', 376 | 'gaiadr1.tmass_original_valid.ks_m', 377 | 'gaiadr1.tmass_original_valid.ks_msigcom', 378 | 'gaiadr1.tmass_original_valid.tmass_oid'] 379 | - ['where', 'join', 'group by'] 380 | - ['sqrt', 'log', 'count', 'floor', 'power'] 381 | - ['g_min_ks: None.None.g_min_ks_index', 382 | 'g_mag_abs: None.None.g_mag_abs_index', 383 | 'n: None.None.None'] 384 | - ['gaiadr1.tgas_source', 'gaiadr1.tmass_best_neighbour', 385 | 'gaiadr1.tmass_original_valid'] 386 | - 387 | 388 | - 389 | - SELECT ra, sub.qqq, t1.bar 390 | FROM db.tab t1 391 | JOIN ( 392 | SELECT subsub.col1 AS qqq, subsub.col2, subsub.id, bar 393 | FROM ( 394 | SELECT col1, col2, id, foo AS bar 395 | FROM db.blem 396 | LIMIT 10 397 | ) AS subsub 398 | ) sub USING(id); 399 | - ['db.blem.col1', 'db.blem.col2', 'db.blem.id', 'db.blem.foo', 400 | 'db.tab.ra', 'db.tab.bar', 'db.tab.id'] 401 | - ['join', 'limit'] 402 | - 403 | - 404 | - ['db.tab', 'db.blem'] 405 | - 406 | 407 | - 408 | - SELECT t1.a, t2.b, t3.c, t4.z 409 | FROM d.tab t1, db2.tab t2, foo.tab t3, x.y t4 410 | - ['foo.tab.a', 'bar.tab.b', 'bas.tab.c', 'x.y.z'] 411 | - 412 | - 413 | - ['a: foo.tab.a', 'b: bar.tab.b', 'c: bas.tab.c', 'z: x.y.z'] 414 | - ['foo.tab', 'bas.tab', 'bar.tab', 'x.y'] 415 | - 416 | 'd': 'foo' 417 | 'db2': 'bar' 418 | 'foo': 'bas' 419 | - 420 | 421 | - 422 | - SELECT *, AVG(par) as apar FROM db.tab; 423 | - ['db.tab.*'] 424 | - 425 | - ['AVG'] 426 | - ['*: db.tab.*', 'apar: db.tab.par'] 427 | - ['db.tab'] 428 | - 429 | 430 | - 431 | - SELECT q.ra, q.de, tab2.par 432 | FROM ( 433 | SELECT *, MAX(meh) FROM db.tab 434 | ) as q 435 | LEFT OUTER JOIN db.tab2 USING(ra, dist) 436 | JOIN db.undef AS ud ON ud.dist = q.par 437 | - ['db.tab.*', 'db.tab2.ra', 'db.tab2.dist', 438 | 'db.undef.dist', 'db.tab2.par'] 439 | - ['join', '*'] 440 | - ['MAX'] 441 | - ['ra: db.tab.ra', 'de: db.tab.de', 'par: db.tab2.par'] 442 | - ['db.tab', 'db.tab2', 'db.undef'] 443 | - 444 | 445 | - 446 | - SELECT a, b 447 | FROM ( 448 | SELECT * FROM db.tab1 449 | UNION 450 | SELECT c, d FROM db.tab2 451 | ) AS sub 452 | - ['db.tab1.*', 'db.tab2.c', 'db.tab2.d'] 453 | - ['union', '*'] 454 | - 455 | - ['a: db.tab1.a', 'b: db.tab1.b'] 456 | - ['db.tab1', 'db.tab2'] 457 | - 458 | 459 | - 460 | - SELECT a FROM db.tab HAVING b > 0 461 | - ['db.tab.a', 'db.tab.b'] 462 | - ['having'] 463 | - 464 | - ['a: db.tab.a'] 465 | - ['db.tab'] 466 | - 467 | 468 | - 469 | - SELECT a FROM db.tab WHERE EXISTS ( 470 | SELECT b from db.foo WHERE x > y 471 | ) 472 | - ['db.tab.a', 'db.foo.b', 'db.foo.x', 'db.foo.y'] 473 | - ['where'] 474 | - 475 | - ['a: db.tab.a'] 476 | - ['db.tab', 'db.foo'] 477 | - 478 | 479 | - 480 | - SELECT * 481 | FROM ( 482 | SELECT * 483 | FROM db.a, db.b, (SELECT * FROM db.c, db.d) AS q 484 | ) AS p 485 | JOIN (SELECT * FROM db.x, db.y) AS r 486 | - ['db.a.*', 'db.b.*', 'db.c.*', 'db.d.*', 'db.x.*', 'db.y.*'] 487 | - ['*'] 488 | - 489 | - 490 | - ['db.a', 'db.b', 'db.c', 'db.d', 'db.x', 'db.y'] 491 | - 492 | 493 | - 494 | - SELECT * 495 | FROM ( 496 | SELECT a.* 497 | FROM db.a, db.b, (SELECT * FROM db.c, db.d) AS q 498 | ) AS p 499 | JOIN (SELECT * FROM db.x, db.y) AS r 500 | - ['db.a.*', 'db.c.*', 'db.d.*', 'db.x.*', 'db.y.*'] 501 | - ['*'] 502 | - 503 | - 504 | - ['db.a', 'db.b', 'db.c', 'db.d', 'db.x', 'db.y'] 505 | - 506 | 507 | - 508 | - SELECT A.*, B.* 509 | FROM db1.table1 A 510 | LEFT JOIN db2.table1 B 511 | ON A.id = B.id; 512 | - ['db1.table1.*', 'db2.table1.*'] 513 | - ['join', '*'] 514 | - 515 | - ['*: db1.table1.*', '*: db2.table1.*'] 516 | - ['db1.table1', 'db2.table1'] 517 | - 518 | 519 | 520 | common_translation_tests: 521 | - 522 | - SELECT POINT('icrs', ra, de) FROM db.tab 523 | - ['db.tab.ra', 'db.tab.de'] 524 | - 525 | - ['spoint', 'RADIANS'] 526 | - 527 | - 528 | - 529 | - 530 | 531 | 532 | mysql_tests: 533 | - 534 | - SELECT `fi@1`, fi2 535 | FROM db.test_table WHERE foo = '1' 536 | UNION 537 | SELECT fi1, fi2 538 | FROM bd.test_table WHERE bar = '1'; 539 | - ['db.test_table.fi@1', 'db.test_table.fi2', 'bd.test_table.fi1', 540 | 'bd.test_table.fi2', 'db.test_table.foo', 'bd.test_table.bar'] 541 | - ['where', 'union'] 542 | - 543 | - ['fi@1: db.test_table.fi@1', 'fi2: db.test_table.fi2'] 544 | - ['db.test_table', 'bd.test_table'] 545 | - 546 | 547 | - 548 | - SELECT `fi@1`, fi2 549 | FROM db.test_table WHERE foo = '1' 550 | UNION 551 | SELECT fi1, fi2 552 | FROM bd.test_table WHERE bar = '1'; 553 | - ['db.test_table.fi@1', 'db.test_table.fi2', 'bd.test_table.fi1', 554 | 'bd.test_table.fi2', 'db.test_table.foo', 'bd.test_table.bar'] 555 | - ['where', 'union'] 556 | - 557 | - ['fi@1: db.test_table.fi@1', 'fi2: db.test_table.fi2'] 558 | - ['db.test_table', 'bd.test_table'] 559 | - 560 | 561 | - 562 | - SELECT log10(mass)/sqrt(x) AS logM 563 | FROM MDR1.FOF 564 | - ['MDR1.FOF.mass', 'MDR1.FOF.x'] 565 | - 566 | - ['log10', 'sqrt'] 567 | - 568 | - ['MDR1.FOF'] 569 | - 570 | 571 | - 572 | - SELECT log10(ABS(x)) AS log_x 573 | FROM MDR1.FOF 574 | - ['MDR1.FOF.x'] 575 | - 576 | - ['log10', 'ABS'] 577 | - ['log_x: MDR1.FOF.x'] 578 | - ['MDR1.FOF'] 579 | - 580 | 581 | - 582 | - SELECT DEGREES(sdist(spoint(RADIANS(0.0), RADIANS(0.0)), 583 | spoint(RADIANS(`VII/233/xsc`.`RAJ2000`), 584 | RADIANS(`VII/233/xsc`.`DEJ2000`)))) 585 | FROM `db`.`VII/233/xsc` LIMIT 10; 586 | - ['db.VII/233/xsc.DEJ2000', 'db.VII/233/xsc.RAJ2000'] 587 | - ['limit'] 588 | - ['DEGREES', 'RADIANS', 'sdist', 'spoint'] 589 | - 590 | - ['db.VII/233/xsc'] 591 | - 592 | 593 | - 594 | - SELECT Data FROM db.Users 595 | WHERE Name ="" or ""="" AND Pass ="" or ""="" 596 | - ['db.Users.Data', 'db.Users.Name', 'db.Users.Pass'] 597 | - ['where'] 598 | - 599 | - ['Data: db.Users.Data'] 600 | - ['db.Users'] 601 | - 602 | 603 | - 604 | - SELECT CONVERT(ra, DECIMAL(12,9)) as ra2, ra as ra1 605 | FROM GDR1.gaia_source 606 | WHERE dec BETWEEN 51 AND 51.5 607 | AND ra BETWEEN 126.25 AND 127.25 608 | - ['GDR1.gaia_source.ra', 'GDR1.gaia_source.dec'] 609 | - ['where'] 610 | - 611 | - ['ra1: GDR1.gaia_source.ra', 'ra2: GDR1.gaia_source.ra'] 612 | - ['GDR1.gaia_source'] 613 | - 614 | 615 | - 616 | - SELECT DEGREES(sdist(spoint(RADIANS(ra), RADIANS(dec)), 617 | spoint(RADIANS(266.41683), RADIANS(-29.00781)))) 618 | AS dist 619 | FROM GDR1.gaia_source 620 | WHERE 1 = srcontainsl(spoint(RADIANS(ra), RADIANS(dec)), 621 | scircle(spoint(RADIANS(266.41683), 622 | RADIANS(-29.00781)), 623 | RADIANS(0.08333333))) 624 | ORDER BY dist ASC 625 | - ['GDR1.gaia_source.ra', 'GDR1.gaia_source.dec'] 626 | - ['where', 'order by'] 627 | - ['sdist', 'scircle', 'RADIANS', 'spoint', 'srcontainsl', 628 | 'DEGREES'] 629 | - 630 | - ['GDR1.gaia_source'] 631 | - 632 | 633 | - 634 | - SELECT x, y, z, mass 635 | FROM MDR1.FOF 636 | LIMIT 100, 200 637 | - ['MDR1.FOF.x', 'MDR1.FOF.y', 'MDR1.FOF.z', 'MDR1.FOF.mass'] 638 | - ['limit'] 639 | - 640 | - ['x: MDR1.FOF.x', 'y: MDR1.FOF.y', 'z: MDR1.FOF.z', 641 | 'mass: MDR1.FOF.mass'] 642 | - ['MDR1.FOF'] 643 | - 644 | 645 | - 646 | - SELECT bdmId, Rbin, mass, dens FROM Bolshoi.BDMVProf 647 | WHERE bdmId = 648 | (SELECT bdmId FROM Bolshoi.BDMV 649 | WHERE snapnum=416 ORDER BY Mvir DESC LIMIT 1) 650 | OR 651 | bdmId = 652 | (SELECT bdmId FROM Bolshoi.BDMV 653 | WHERE snapnum=416 ORDER BY Mvir DESC LIMIT 1,2) 654 | ORDER BY Rbin 655 | - ['Bolshoi.BDMVProf.bdmId', 'Bolshoi.BDMVProf.Rbin', 656 | 'Bolshoi.BDMVProf.mass', 'Bolshoi.BDMVProf.dens', 657 | 'Bolshoi.BDMV.bdmId', 'Bolshoi.BDMV.snapnum', 658 | 'Bolshoi.BDMV.Mvir'] 659 | - ['where', 'order by', 'limit'] 660 | - 661 | - ['bdmId: Bolshoi.BDMVProf.bdmId', 'Rbin: Bolshoi.BDMVProf.Rbin', 662 | 'mass: Bolshoi.BDMVProf.mass', 'dens: Bolshoi.BDMVProf.dens'] 663 | - ['Bolshoi.BDMVProf', 'Bolshoi.BDMV'] 664 | - 665 | 666 | 667 | postgresql_tests: 668 | - 669 | - SELECT pdist(1000, 10, 11, 12, 11, 11, 12) 670 | - 671 | - 672 | - ['pdist'] 673 | - 674 | - 675 | - 676 | 677 | - 678 | - SELECT DISTINCT ON ("source"."tycho2_id") "tycho2_id", "source"."tycho2_dist" 679 | FROM "applause_dr3"."source_calib" AS "source" 680 | - ['applause_dr3.source_calib.tycho2_id', 'applause_dr3.source_calib.tycho2_dist'] 681 | - 682 | - 683 | - 684 | - 685 | - 686 | 687 | - 688 | - SELECT ra, dec FROM gdr1.gaia_source 689 | WHERE pos @ scircle(spoint(1.44, 0.23), 0.01) 690 | - ['gdr1.gaia_source.ra', 'gdr1.gaia_source.dec', 691 | 'gdr1.gaia_source.pos'] 692 | - ['where'] 693 | - ['scircle', 'spoint'] 694 | - ['ra: gdr1.gaia_source.ra', 'dec: gdr1.gaia_source.dec'] 695 | - ['gdr1.gaia_source'] 696 | - 697 | 698 | - 699 | - SELECT ra, dec FROM gdr1.gaia_source 700 | WHERE pos @ scircle(spoint(1.44, 0.23), 0.01) 701 | - ['gdr1.gaia_source.ra', 'gdr1.gaia_source.dec', 702 | 'gdr1.gaia_source.pos'] 703 | - ['where'] 704 | - ['scircle', 'spoint'] 705 | - ['ra: gdr1.gaia_source.ra', 'dec: gdr1.gaia_source.dec'] 706 | - ['gdr1.gaia_source'] 707 | - 708 | 709 | - 710 | - SELECT * FROM gdr2.vari_cepheid AS v 711 | JOIN gdr2.gaia_source AS g USING(source_id) 712 | WHERE g.pos @ scircle(spoint(4.2917, -0.4629), 0.008) 713 | - ['gdr2.gaia_source.pos', 'gdr2.gaia_source.source_id', 714 | 'gdr2.vari_cepheid.*'] 715 | - ['where', 'join', '*'] 716 | - ['scircle', 'spoint'] 717 | - ['*: gdr2.vari_cepheid.*'] 718 | - ['gdr2.gaia_source', 'gdr2.vari_cepheid'] 719 | - 720 | 721 | - 722 | - SELECT curves.observation_time, 723 | mod(curves.observation_time - rrlyrae.epoch_g, rrlyrae.p1), 724 | rrlyrae.p1 AS phase, 725 | curves.g_magnitude, 726 | 2.5 / log(10) * curves.g_flux_error / curves.g_flux 727 | AS g_magnitude_error 728 | FROM gdr1.phot_variable_time_series_gfov AS curves 729 | INNER JOIN gdr1.rrlyrae AS rrlyrae 730 | ON rrlyrae.source_id = curves.source_id 731 | WHERE rrlyrae.source_id = 5284240582308398080 732 | AND pos @ sbox(spoint(1.44, 0.23), spoint(1.5, 0.3)) 733 | - ['gdr1.phot_variable_time_series_gfov.g_flux', 734 | 'gdr1.phot_variable_time_series_gfov.g_flux_error', 735 | 'gdr1.phot_variable_time_series_gfov.g_magnitude', 736 | 'gdr1.phot_variable_time_series_gfov.observation_time', 737 | 'gdr1.phot_variable_time_series_gfov.pos', 738 | 'gdr1.phot_variable_time_series_gfov.source_id', 739 | 'gdr1.rrlyrae.epoch_g', 740 | 'gdr1.rrlyrae.p1', 741 | 'gdr1.rrlyrae.source_id'] 742 | - ['where', 'join'] 743 | - ['sbox', 'spoint', 'mod', 'log'] 744 | - ['g_magnitude: gdr1.phot_variable_time_series_gfov.g_magnitude', 745 | 'observation_time: gdr1.phot_variable_time_series_gfov.observation_time', 746 | 'phase: gdr1.rrlyrae.p1'] 747 | - ['gdr1.phot_variable_time_series_gfov', 'gdr1.rrlyrae'] 748 | - 749 | 750 | - 751 | - SELECT a 752 | FROM db.tab, 753 | (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter); 754 | - ['db.tab.a'] 755 | - 756 | - 757 | - ['a: db.tab.a'] 758 | - ['db.tab'] 759 | - 760 | 761 | - 762 | - SELECT arr[1:3] FROM db.phot; 763 | - ['db.phot.arr'] 764 | - 765 | - 766 | - ['arr: db.phot.arr'] 767 | - ['db.phot'] 768 | - 769 | 770 | - 771 | - SELECT arr[1:3][1][2][3][4] FROM db.phot; 772 | - ['db.phot.arr'] 773 | - 774 | - 775 | - ['arr: db.phot.arr'] 776 | - ['db.phot'] 777 | - 778 | 779 | - 780 | - SELECT ra, dec FROM gdr1.gaia_source 781 | WHERE pos @ scircle(spoint(1.44, 0.23), 0.01); 782 | - ['gdr1.gaia_source.ra', 'gdr1.gaia_source.dec', 783 | 'gdr1.gaia_source.pos'] 784 | - ['where'] 785 | - ['scircle', 'spoint'] 786 | - ['ra: gdr1.gaia_source.ra', 'dec: gdr1.gaia_source.dec'] 787 | - ['gdr1.gaia_source'] 788 | - 789 | 790 | - 791 | - SELECT q2.c / q1.c FROM ( 792 | SELECT CAST(COUNT(*) AS FLOAT) AS c 793 | FROM gdr1.tgas_source 794 | ) AS q1 795 | CROSS JOIN ( 796 | SELECT COUNT(*) AS c 797 | FROM gdr1.tgas_source 798 | WHERE parallax / parallax_error > 10 799 | ) AS q2 800 | - ['gdr1.tgas_source.parallax', 'gdr1.tgas_source.parallax_error', 801 | 'gdr1.tgas_source.None'] 802 | - ['where'] 803 | - ['COUNT'] 804 | - 805 | - ['gdr1.tgas_source'] 806 | - 807 | 808 | - 809 | - SELECT * FROM gdr2.vari_cepheid AS v 810 | JOIN gdr2.gaia_source AS g USING(source_id) 811 | WHERE g.pos @ scircle(spoint(4.2917, -0.4629), 0.008) 812 | - ['gdr2.gaia_source.pos', 'gdr2.gaia_source.source_id', 813 | 'gdr2.vari_cepheid.*'] 814 | - ['where', 'join', '*'] 815 | - ['scircle', 'spoint'] 816 | - ['*: gdr2.vari_cepheid.*'] 817 | - ['gdr2.gaia_source', 'gdr2.vari_cepheid'] 818 | - 819 | 820 | - 821 | - SELECT ra FROM gdr2.gaia_source AS gaia 822 | WHERE spoint(RADIANS(gaia.ra), RADIANS(gaia.dec)) @ 823 | scircle(spoint(RADIANS(245.8962), RADIANS(-26.5222)), RADIANS(0.5)); 824 | - ['gdr2.gaia_source.dec', 'gdr2.gaia_source.ra'] 825 | - ['where'] 826 | - ['RADIANS', 'spoint', 'scircle'] 827 | - ['ra: gdr2.gaia_source.ra'] 828 | - ['gdr2.gaia_source'] 829 | - 830 | 831 | - 832 | - SELECT specuid, ra, dec FROM dr1.spectrum WHERE QMOST_SPEC_IS_IN_SURVEY(specuid, '04'); 833 | - ['dr1.spectrum.specuid', 'dr1.spectrum.ra', 'dr1.spectrum.dec'] 834 | - [where] 835 | - [QMOST_SPEC_IS_IN_SURVEY] 836 | - ['specuid: dr1.spectrum.specuid', 'ra: dr1.spectrum.ra', 'dec: dr1.spectrum.dec'] 837 | - [dr1.spectrum] 838 | - 839 | - [QMOST_SPEC_IS_IN_SURVEY] 840 | 841 | postgresql_failure_tests: 842 | - 843 | - SELECT specuid, ra, dec FROM dr1.spectrum WHERE QMOST_SPEC_IS_IN_SURVEY(specuid, '04'); 844 | - syntax 845 | - 846 | - [] 847 | 848 | - 849 | - SELECT specuid, ra, dec FROM dr1.spectrum WHERE BLA(specuid, '04'); 850 | - syntax 851 | - 852 | - [QMOST_SPEC_IS_IN_SURVEY] 853 | 854 | - 855 | - SELECT specuid, ra, dec FROM dr1.spectrum WHERE QMOST_SPEC_IS_IN_SURVEY[specuid, '04']; 856 | - syntax 857 | - 858 | - [QMOST_SPEC_IS_IN_SURVEY] 859 | 860 | - 861 | - SELECT specuid, ra, dec FROM dr1.spectrum WHERE a(specuid, '04'); 862 | - value 863 | - 864 | - [a, b, c, d, e, f, g, h, i, j, k, l] 865 | 866 | 867 | adql_mysql_tests: 868 | - 869 | - SELECT POINT('icrs', 10, 10) AS "p" FROM "db".tab 870 | - SELECT spoint(RADIANS(10.0), RADIANS(10.0)) AS `p` FROM `db`.`tab`; 871 | 872 | - 873 | - SELECT TOP 10 AREA(CIRCLE('ICRS', "tab".RA, -2.23, 176.98)) FROM db.tab 874 | - SELECT sarea(scircle(spoint(RADIANS(`tab`.`RA`), RADIANS(-2.23)), RADIANS(176.98))) AS adql_area FROM `db`.`tab` LIMIT 10; 875 | 876 | - 877 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 1=CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', -3.0, 5.0, 4.0, 10.0)); 878 | - SELECT `ra`, `dec` FROM `db`.`tab` WHERE 1 = srcontainsl(spoint(RADIANS(`ra`), RADIANS(`dec`)), sbox(spoint(RADIANS(-5.000000000000),RADIANS(0.000000000000)),spoint(RADIANS(-1.000000000000),RADIANS(10.000000000000)))) LIMIT 10; 879 | 880 | - 881 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 0=CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', -3.0, 5.0, 4.0, 10.0)); 882 | - SELECT `ra`, `dec` FROM `db`.`tab` WHERE 0 = srcontainsl(spoint(RADIANS(`ra`), RADIANS(`dec`)), sbox(spoint(RADIANS(-5.000000000000),RADIANS(0.000000000000)),spoint(RADIANS(-1.000000000000),RADIANS(10.000000000000)))) LIMIT 10; 883 | 884 | - 885 | - SELECT TOP 10 DISTANCE(POINT('ICRS', ra, dec), POINT('ICRS', 13.66, -58.3)) FROM db.tab; 886 | - SELECT DEGREES(sdist(spoint(RADIANS(`ra`), RADIANS(`dec`)), spoint(RADIANS(13.66), RADIANS(-58.3)))) FROM `db`.`tab` LIMIT 10; 887 | 888 | # the coordsys strings are deprecated since adql 2.1. The tests including the 889 | # coordsys will ensure the backward compatibility for adql2.0 but can be removed 890 | # later. 891 | adql_postgresql_tests: 892 | - 893 | - SELECT TOP 10 AREA(CIRCLE('ICRS', "tab".RA, -2.23, 176.98)) FROM db.tab 894 | - SELECT square_degrees(area(scircle(spoint(RADIANS("tab".RA), RADIANS(-2.23)), RADIANS(176.98)))) AS adql_area FROM db.tab LIMIT 10; 895 | 896 | - 897 | - SELECT TOP 10 AREA(CIRCLE("tab".RA, -2.23, 176.98)) FROM db.tab 898 | - SELECT square_degrees(area(scircle(spoint(RADIANS("tab".RA), RADIANS(-2.23)), RADIANS(176.98)))) AS adql_area FROM db.tab LIMIT 10; 899 | 900 | - 901 | - SELECT TOP 10 AREA(POLYGON(10.0, -10.5, 20.0, 20.5, 30.0, 30.5)) FROM db.tab 902 | - SELECT square_degrees(area(spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}'))) AS adql_area FROM db.tab LIMIT 10; 903 | 904 | - 905 | - SELECT TOP 10 area(circle(0.0, 0.0, 1.0)) FROM test.tab; 906 | - SELECT square_degrees(area(scircle(spoint(RADIANS(0.0), RADIANS(0.0)), RADIANS(1.0)))) AS adql_area FROM test.tab LIMIT 10; 907 | 908 | - 909 | - SELECT POINT('icrs', 10, 10) AS "p" FROM "db".tab 910 | - SELECT spoint_to_array_deg(spoint(RADIANS(10.0), RADIANS(10.0))) AS "p" FROM "db".tab; 911 | 912 | - 913 | - SELECT POINT('icrs', 10, 10) FROM "db".tab 914 | - SELECT spoint_to_array_deg(spoint(RADIANS(10.0), RADIANS(10.0))) AS adql_point FROM "db".tab; 915 | 916 | - 917 | - SELECT POINT(10, 10) AS "p" FROM "db".tab 918 | - SELECT spoint_to_array_deg(spoint(RADIANS(10.0), RADIANS(10.0))) AS "p" FROM "db".tab; 919 | 920 | - 921 | - SELECT CIRCLE(10.0, -10.5, 2.0) FROM db.tab; 922 | - SELECT scircle_to_array_deg(scircle(spoint(RADIANS(10.0), RADIANS(-10.5)), RADIANS(2.0))) AS circle FROM db.tab; 923 | 924 | - 925 | - SELECT POLYGON('ICRS', 10.0, -10.5, 20.0, 20.5, 30.0, 30.5) FROM db.tab; 926 | - SELECT spoly_to_array_deg(spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}')) AS adql_polygon FROM db.tab; 927 | 928 | - 929 | - SELECT CIRCLE(POINT(10.0, -10.5), 2.0) FROM db.tab; 930 | - SELECT scircle_to_array_deg(scircle(spoint(RADIANS(10.0), RADIANS(-10.5)), RADIANS(2.0))) AS circle FROM db.tab; 931 | 932 | - 933 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 1=CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', -3.0, 5.0, 4.0, 10.0)); 934 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ sbox(spoint(RADIANS(-5.000000000000),RADIANS(0.000000000000)),spoint(RADIANS(-1.000000000000),RADIANS(10.000000000000))) LIMIT 10; 935 | 936 | - 937 | - SELECT TOP 10 ra, dec FROM db.tab WHERE CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', -3.0, 5.0, 4.0, 10.0)) = 1; 938 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ sbox(spoint(RADIANS(-5.000000000000),RADIANS(0.000000000000)),spoint(RADIANS(-1.000000000000),RADIANS(10.000000000000))) LIMIT 10; 939 | 940 | - 941 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 1=CONTAINS(POINT(ra, dec), BOX(-3.0, 5.0, 4.0, 10.0)); 942 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ sbox(spoint(RADIANS(-5.000000000000),RADIANS(0.000000000000)),spoint(RADIANS(-1.000000000000),RADIANS(10.000000000000))) LIMIT 10; 943 | 944 | - 945 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 1=CONTAINS(POINT(ra, dec), CIRCLE(POINT(-3.0, 4.0), 10.0)); 946 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ scircle(spoint(RADIANS(-3.0), RADIANS(4.0)), RADIANS(10.0)) LIMIT 10; 947 | 948 | - 949 | - SELECT TOP 10 ra, dec FROM db.tab WHERE CONTAINS(POINT(ra, dec), CIRCLE(POINT(-3.0, 4.0), 10.0))=0; 950 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) !@ scircle(spoint(RADIANS(-3.0), RADIANS(4.0)), RADIANS(10.0)) LIMIT 10; 951 | 952 | - 953 | - SELECT TOP 10 ra, dec FROM db.tab WHERE 0=CONTAINS(POINT(ra, dec), CIRCLE(POINT(-3.0, 4.0), 10.0)); 954 | - SELECT ra, dec FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) !@ scircle(spoint(RADIANS(-3.0), RADIANS(4.0)), RADIANS(10.0)) LIMIT 10; 955 | 956 | - 957 | - SELECT TOP 10 LOG10(ra), LOG(dec) FROM db.tab WHERE 1=CONTAINS(POINT('ICRS', ra, dec), POLYGON('ICRS', 10.0, -10.5, 20.0, 20.5, 30.0, 30.5)); 958 | - SELECT LOG(ra), LN(dec) FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}') LIMIT 10; 959 | 960 | - 961 | - SELECT TOP 10 LOG10(ra), LOG(dec) FROM db.tab WHERE CONTAINS(POINT(ra, dec), POLYGON(10.0, -10.5, 20.0, 20.5, 30.0, 30.5))=1; 962 | - SELECT LOG(ra), LN(dec) FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) @ spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}') LIMIT 10; 963 | 964 | - 965 | - SELECT TOP 10 DISTANCE(POINT(ra, dec), POINT(13.66, -58.3)) FROM db.tab; 966 | - SELECT DEGREES(spoint(RADIANS(ra), RADIANS(dec)) <-> spoint(RADIANS(13.66), RADIANS(-58.3))) AS distance FROM db.tab LIMIT 10; 967 | 968 | - 969 | - SELECT TOP 10 DISTANCE(ra, dec, 13.66, -58.3) FROM db.tab; 970 | - SELECT DEGREES(spoint(RADIANS(ra), RADIANS(dec)) <-> spoint(RADIANS(13.66), RADIANS(-58.3))) AS distance FROM db.tab LIMIT 10; 971 | 972 | - 973 | - SELECT INTERSECTS(CIRCLE('ICRS', 0, 0, 10), BOX('ICRS', 2, -3, 4, 4)) FROM db.tab; 974 | - SELECT scircle(spoint(RADIANS(0.0), RADIANS(0.0)), RADIANS(10.0)) && sbox(spoint(RADIANS(0.000000000000),RADIANS(-5.000000000000)),spoint(RADIANS(4.000000000000),RADIANS(-1.000000000000))) FROM db.tab; 975 | 976 | - 977 | - SELECT TOP 10 LOG10(ra), LOG(dec) FROM db.tab WHERE 0=INTERSECTS(POINT('ICRS', ra, dec), POLYGON('ICRS', 10.0, -10.5, 20.0, 20.5, 30.0, 30.5)); 978 | - SELECT LOG(ra), LN(dec) FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) !&& spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}') LIMIT 10; 979 | 980 | - 981 | - SELECT TOP 10 LOG10(ra), LOG(dec) FROM db.tab WHERE INTERSECTS(POINT('ICRS', ra, dec), POLYGON('ICRS', 10.0, -10.5, 20.0, 20.5, 30.0, 30.5)) = 0; 982 | - SELECT LOG(ra), LN(dec) FROM db.tab WHERE spoint(RADIANS(ra), RADIANS(dec)) !&& spoly('{(10.0d,-10.5d),(20.0d,20.5d),(30.0d,30.5d)}') LIMIT 10; 983 | 984 | 985 | # Each test below consists of: 986 | # 987 | # - syntactically incorrect query string 988 | 989 | common_syntax_tests: 990 | - SELECR a FROM db.tab; 991 | 992 | 993 | common_query_tests: 994 | - SELECT a FROM db.tab1, db.tab2; 995 | - SELECT a.b, a.c 996 | FROM ( 997 | SELECT ra, dec 998 | FROM db.tab 999 | ) AS sub 1000 | - SELECT a FROM db.tab 1001 | JOIN ( 1002 | SELECT a FROM db.foo 1003 | ) AS sub USING(a) 1004 | JOIN ( 1005 | SELECT a FROM db.bar 1006 | ) AS sub USING(a) 1007 | - SELECT b 1008 | FROM ( 1009 | SELECT a FROM db.tab 1010 | ) AS sub 1011 | - SELECT sub.b 1012 | FROM ( 1013 | SELECT a FROM db.tab 1014 | ) AS sub 1015 | - SELECT a FROM tab 1016 | - SELECT a, b FROM db.tab1 1017 | JOIN ( 1018 | SELECT id, col AS b FROM db.tab2 1019 | ) AS sub USING(id) 1020 | -------------------------------------------------------------------------------- /src/queryparser/testing/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pytest 3 | 4 | from queryparser.adql import ADQLQueryTranslator 5 | from queryparser.exceptions import QuerySyntaxError 6 | from queryparser.mysql import MySQLQueryProcessor 7 | from queryparser.postgresql import PostgreSQLQueryProcessor 8 | 9 | 10 | def _test_parsing(query_processor, test, translate=False): 11 | if len(test) == 7: 12 | ( 13 | query, 14 | columns, 15 | keywords, 16 | functions, 17 | display_columns, 18 | tables, 19 | replace_function_names, 20 | ) = test 21 | replace_schema_name = None 22 | elif len(test) == 8: 23 | ( 24 | query, 25 | columns, 26 | keywords, 27 | functions, 28 | display_columns, 29 | tables, 30 | replace_schema_name, 31 | replace_function_names, 32 | ) = test 33 | 34 | if translate: 35 | adt = ADQLQueryTranslator() 36 | adt.set_query(query) 37 | if query_processor == MySQLQueryProcessor: 38 | query = adt.to_mysql() 39 | elif query_processor == PostgreSQLQueryProcessor: 40 | query = adt.to_postgresql() 41 | 42 | if replace_function_names is None: 43 | replace_function_names = [] 44 | 45 | qp = query_processor() 46 | qp.set_query(query) 47 | qp.process_query( 48 | replace_schema_name=replace_schema_name, 49 | replace_function_names=replace_function_names, 50 | ) 51 | 52 | qp_columns = [ 53 | '.'.join([str(j) for j in i[:3]]) 54 | for i in qp.columns 55 | if i[0] is not None and i[1] is not None 56 | ] 57 | qp_display_columns = [ 58 | '%s: %s' % (str(i[0]), '.'.join([str(j) for j in i[1]])) 59 | for i in qp.display_columns 60 | ] 61 | qp_tables = [ 62 | '.'.join([str(j) for j in i]) 63 | for i in qp.tables 64 | if i[0] is not None and i[1] is not None 65 | ] 66 | 67 | if columns is not None: 68 | assert set(columns) == set(qp_columns) 69 | 70 | if keywords is not None: 71 | assert set([i.lower() for i in keywords]) == set(qp.keywords) 72 | 73 | if functions is not None: 74 | assert set(functions) == set(qp.functions) 75 | 76 | if display_columns is not None: 77 | assert set(display_columns) == set(qp_display_columns) 78 | 79 | if tables is not None: 80 | assert set(tables) == set(qp_tables) 81 | 82 | 83 | def _test_failure_parsing(query_processor, test, translate=False): 84 | query, errortype, replace_schema_name, replace_function_names = test 85 | 86 | if errortype == 'syntax': 87 | e = QuerySyntaxError 88 | elif errortype == 'value': 89 | e = ValueError 90 | else: 91 | raise ValueError('Invalid error type') 92 | 93 | if translate: 94 | adt = ADQLQueryTranslator() 95 | adt.set_query(query) 96 | if query_processor == MySQLQueryProcessor: 97 | query = adt.to_mysql() 98 | elif query_processor == PostgreSQLQueryProcessor: 99 | query = adt.to_postgresql() 100 | 101 | qp = query_processor() 102 | qp.set_query(query) 103 | 104 | with pytest.raises(e): 105 | qp.process_query( 106 | replace_schema_name=replace_schema_name, 107 | replace_function_names=replace_function_names, 108 | ) 109 | --------------------------------------------------------------------------------