├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── maven.yml │ └── remove-old-artifacts.yml ├── .gitignore ├── LICENSE ├── README.asciidoc ├── checkstyle ├── checkstyle-rules.xml └── suppressions.xml ├── northwind.json ├── pom.xml ├── schema.json └── src ├── assembly ├── distribution.xml └── standalone.xml ├── main ├── java │ └── org │ │ └── twilmes │ │ └── sql │ │ └── gremlin │ │ └── adapter │ │ ├── converter │ │ ├── SqlConverter.java │ │ ├── SqlMetadata.java │ │ ├── SqlTraversalEngine.java │ │ ├── ast │ │ │ └── nodes │ │ │ │ ├── GremlinSqlFactory.java │ │ │ │ ├── GremlinSqlNode.java │ │ │ │ ├── operands │ │ │ │ └── GremlinSqlIdentifier.java │ │ │ │ ├── operator │ │ │ │ ├── GremlinSqlAsOperator.java │ │ │ │ ├── GremlinSqlBasicCall.java │ │ │ │ ├── GremlinSqlOperator.java │ │ │ │ ├── GremlinSqlPostFixOperator.java │ │ │ │ ├── GremlinSqlTraversalAppender.java │ │ │ │ ├── aggregate │ │ │ │ │ └── GremlinSqlAggFunction.java │ │ │ │ └── logic │ │ │ │ │ ├── GremlinSqlBinaryOperator.java │ │ │ │ │ └── GremlinSqlNumericLiteral.java │ │ │ │ └── select │ │ │ │ ├── GremlinSqlSelect.java │ │ │ │ ├── GremlinSqlSelectMulti.java │ │ │ │ ├── GremlinSqlSelectSingle.java │ │ │ │ ├── StepDirection.java │ │ │ │ └── join │ │ │ │ └── GremlinSqlJoinComparison.java │ │ └── schema │ │ │ ├── SqlSchemaGrabber.java │ │ │ ├── calcite │ │ │ ├── GremlinFilter.java │ │ │ ├── GremlinRel.java │ │ │ ├── GremlinRules.java │ │ │ ├── GremlinSchema.java │ │ │ ├── GremlinTableScan.java │ │ │ ├── GremlinToEnumerableConverter.java │ │ │ └── GremlinToEnumerableConverterRule.java │ │ │ └── gremlin │ │ │ ├── GremlinEdgeTable.java │ │ │ ├── GremlinProperty.java │ │ │ ├── GremlinTableBase.java │ │ │ └── GremlinVertexTable.java │ │ └── results │ │ ├── SqlGremlinQueryResult.java │ │ └── pagination │ │ ├── GetRowFromMap.java │ │ ├── JoinDataReader.java │ │ ├── Pagination.java │ │ └── SimpleDataReader.java └── resources │ ├── META-INF │ └── services │ │ └── org.apache.tinkerpop.gremlin.groovy.plugin.GremlinPlugin │ └── log4j.properties └── test └── java └── org └── twilmes └── sql └── gremlin └── adapter ├── GremlinSqlAdvancedSelectTest.java ├── GremlinSqlAggregateTest.java ├── GremlinSqlBaseTest.java ├── GremlinSqlBasicSelectTest.java └── graphs ├── DataTypeGraph.java ├── GraphConstants.java ├── SpaceTestGraph.java ├── TestGraph.java └── TestGraphFactory.java /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | 4 | 5 | ### Description 6 | 7 | 8 | 9 | ### Related Issue 10 | 11 | 12 | 13 | ### Additional Reviewers 14 | @lyndonb-bq 15 | @xiazcy 16 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | name: SQL-Gremlin Maven Build 19 | 20 | on: 21 | # Trigger the workflow on push or pull request, 22 | # but only for the master branch 23 | push: 24 | branches: 25 | - master 26 | - develop 27 | pull_request: 28 | branches: 29 | - master 30 | - develop 31 | jobs: 32 | build: 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Set up JDK 1.8 38 | uses: actions/setup-java@v1 39 | with: 40 | java-version: 1.8 41 | - name: Maven Build 42 | run: mvn -B verify --file pom.xml 43 | - name: Copy Build 44 | run: | 45 | mkdir jarfile 46 | cp target/*.jar jarfile 47 | - name: Upload Build 48 | uses: actions/upload-artifact@v1 49 | with: 50 | name: jarfile 51 | path: jarfile 52 | -------------------------------------------------------------------------------- /.github/workflows/remove-old-artifacts.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # Adapted from https://github.com/marketplace/actions/remove-artifacts (available under MIT license) 19 | 20 | name: Remove Old Artifacts 21 | 22 | on: 23 | schedule: 24 | # Every day at 1am 25 | - cron: '0 1 * * *' 26 | 27 | jobs: 28 | remove-old-artifacts: 29 | runs-on: ubuntu-latest 30 | timeout-minutes: 10 31 | 32 | steps: 33 | - name: Remove Old Artifacts 34 | uses: c-hive/gha-remove-artifacts@v1 35 | with: 36 | age: '1 day' 37 | # skip-tags will prevent tags (i.e releases) from being deleted. 38 | skip-tags: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | *.iml 15 | .idea/ 16 | target/ 17 | ======= 18 | # IntelliJ 19 | *.iml 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | SQL-Gremlin 2 | =========== 3 | The SQL-Gremlin compiler turns your SQL queries into Gremlin traversals and runs them against your favorite TinkerPop 3 enabled graph database. 4 | 5 | Motivation 6 | ---------- 7 | Many projects face the inevitable reporting/business intelligence requirement. This has motivated the creation of the SQL-Gremlin compiler. 8 | 9 | Usage 10 | ----- 11 | To use this, build the module as a shadowJar (run package). From there you must collect the schema of your graph before you can enter sql statments. 12 | Basic usage is shown below: 13 | ``` 14 | void executeSqlStatement(final String sql, final GraphTraversalSource g) throws SQLException { 15 | final GremlinSchema gremlinSchema = SqlSchemaGrabber.getSchema(g, SqlSchemaGrabber.ScanType.All); 16 | final SqlConverter converter = new SqlConverter(gremlinSchema, g); 17 | final SqlGremlinQueryResult sqlGremlinQueryResult = converter.executeSql(sql); 18 | } 19 | ``` 20 | The project is currently under rapid development and interfaces may change quickly with no notice. 21 | 22 | Roadmap 23 | ------- 24 | Current priorities include: 25 | 26 | * Further SQL support 27 | * Increase testing 28 | * Enhancements to schema collection 29 | 30 | Please refer to https://github.com/twilmes/sql-gremlin/issues for the most up to date list of outstanding issues. 31 | 32 | Acknowledgements 33 | ---------------- 34 | Special thanks goes to the http://tinkerpop.incubator.apache.org/[Apache TinkerPop] and https://calcite.apache.org/[Apache Calcite] teams. The depth and breadth of both of these projects is truly astounding. Also, thanks to Daniel Kuppitz. His work on https://github.com/dkuppitz/sparql-gremlin[SPARQL-Gremlin] served as a model and inspiration for SQL-Gremlin. 35 | -------------------------------------------------------------------------------- /checkstyle/checkstyle-rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 71 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /northwind.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "name": "customer", 5 | "columns": [ 6 | {"name": "name", "type": "string"}, 7 | {"name": "title", "type": "string"} 8 | ] 9 | }, 10 | { 11 | "name": "country", 12 | "columns": [ 13 | {"name": "name", "type": "string"} 14 | ] 15 | }, 16 | { 17 | "name": "country", 18 | "columns": [ 19 | {"name": "name", "type": "string"} 20 | ] 21 | }, 22 | { 23 | "name": "region", 24 | "columns": [ 25 | {"name": "name", "type": "string"} 26 | ] 27 | }, 28 | { 29 | "name": "stuff", 30 | "columns": [ 31 | {"name": "shipCity", "type": "string"} 32 | ] 33 | }, 34 | { 35 | "name": "livesInCountryAssocs", 36 | "columns": [ 37 | ] 38 | }, 39 | { 40 | "name": "product", 41 | "columns": [ 42 | {"name": "productName", "propertyName": "name", "type": "string"}, 43 | {"name": "unitsInStock", "type": "integer"}, 44 | {"name": "unitsInOrder", "type": "integer"}, 45 | {"name": "unitPrice", "type": "double"}, 46 | {"name": "discontinued", "type": "boolean"}, 47 | {"name": "reorderLevel", "type": "integer"} 48 | ] 49 | }, 50 | { 51 | "name": "category", 52 | "columns": [ 53 | {"name": "name", "type": "string"}, 54 | {"name": "description", "type": "string"} 55 | ] 56 | }, 57 | { 58 | "name": "livesInCountry", 59 | "columns": [ 60 | {"name": "foo", "type": "string"} 61 | ] 62 | } 63 | ], 64 | "relationships": [ 65 | {"outTable": "customer", "inTable": "country", "edgeLabel": "livesInCountry", "manyToMany": "livesInCountry"}, 66 | {"outTable": "customer", "inTable": "region", "edgeLabel": "livesInRegion"}, 67 | {"outTable": "stuff", "inTable": "customer", "edgeLabel": "ordered"} 68 | ] 69 | } -------------------------------------------------------------------------------- /schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "tables": [ 3 | { 4 | "name": "company", 5 | "columns": [ 6 | {"name": "name", "type": "string"} 7 | ] 8 | }, 9 | { 10 | "name": "country", 11 | "columns": [ 12 | {"name": "name", "type": "string"} 13 | ] 14 | }, 15 | { 16 | "name": "planet", 17 | "columns": [ 18 | {"name": "name", "type": "string"} 19 | ] 20 | }, 21 | { 22 | "name": "person", 23 | "columns": [ 24 | {"name": "name", "type": "string"}, 25 | {"name": "age", "type": "integer"} 26 | ] 27 | }, 28 | { 29 | "name": "spaceship", 30 | "columns": [ 31 | {"name": "name", "type": "string"}, 32 | {"name": "model", "type": "string"} 33 | ] 34 | }, 35 | { 36 | "name": "satellite", 37 | "columns": [ 38 | {"name": "name", "type": "string"} 39 | ] 40 | }, 41 | { 42 | "name": "sensor", 43 | "columns": [ 44 | {"name": "name", "type": "string"}, 45 | {"name": "type", "type": "string"} 46 | ] 47 | }, 48 | { 49 | "name": "sensorReading", 50 | "columns": [ 51 | {"name": "tstamp", "type": "long_timestamp", "propertyName": "timestamp"}, 52 | {"name": "dt", "type": "long_date", "propertyName": "date"}, 53 | {"name": "value", "type": "double"} 54 | ] 55 | }, 56 | { 57 | "name": "fliesTo", 58 | "columns":[ 59 | {"name": "trips", "type": "integer"} 60 | ] 61 | }, 62 | { 63 | "name": "orbits", 64 | "columns":[ 65 | {"name": "launched", "type": "integer"} 66 | ] 67 | } 68 | ], 69 | "relationships": [ 70 | {"outTable": "company", "inTable": "country", "edgeLabel": "baseIn"}, 71 | {"outTable": "person", "inTable": "company", "edgeLabel": "worksFor"}, 72 | {"outTable": "person", "inTable": "planets", "edgeLabel": "travelledTo"}, 73 | {"outTable": "company", "inTable": "spaceship", "edgeLabel": "owns"}, 74 | {"outTable": "person", "inTable": "spaceship", "edgeLabel": "pilots"}, 75 | {"outTable": "sensor", "inTable": "sensorReading", "edgeLabel": "hasReading", "fkTable": "sensorReading"}, 76 | {"outTable": "person", "inTable": "planet", "edgeLabel": "fliesTo"}, 77 | {"outTable": "satellite", "inTable": "planet", "edgeLabel": "orbits"}, 78 | {"outTable": "person", "inTable": "person", "edgeLabel": "friendsWith"} 79 | ] 80 | } -------------------------------------------------------------------------------- /src/assembly/distribution.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | distribution 22 | 23 | zip 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | target/${project.artifactId}-${project.version}-standalone/lib 44 | lib 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/assembly/standalone.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | standalone 22 | 23 | dir 24 | 25 | false 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | target/*.jar 39 | /lib 40 | 41 | 42 | 43 | 44 | 45 | /lib 46 | false 47 | provided 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/SqlConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import lombok.Getter; 24 | import org.apache.calcite.avatica.util.Quoting; 25 | import org.apache.calcite.config.Lex; 26 | import org.apache.calcite.plan.ConventionTraitDef; 27 | import org.apache.calcite.plan.RelTraitDef; 28 | import org.apache.calcite.rel.RelCollationTraitDef; 29 | import org.apache.calcite.schema.SchemaPlus; 30 | import org.apache.calcite.sql.SqlNode; 31 | import org.apache.calcite.sql.SqlSelect; 32 | import org.apache.calcite.sql.parser.SqlParser; 33 | import org.apache.calcite.tools.FrameworkConfig; 34 | import org.apache.calcite.tools.Frameworks; 35 | import org.apache.calcite.tools.Planner; 36 | import org.apache.calcite.tools.Program; 37 | import org.apache.calcite.tools.Programs; 38 | import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslator; 39 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 40 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 41 | import org.slf4j.Logger; 42 | import org.slf4j.LoggerFactory; 43 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlFactory; 44 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.GremlinSqlSelect; 45 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinSchema; 46 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 47 | import java.sql.SQLException; 48 | import java.util.List; 49 | 50 | /** 51 | * This module is the entry point of the SqlGremlin conversion. 52 | * 53 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 54 | */ 55 | public class SqlConverter { 56 | private static final Logger LOGGER = LoggerFactory.getLogger(SqlConverter.class); 57 | private static final List TRAIT_DEFS = 58 | ImmutableList.of(ConventionTraitDef.INSTANCE, RelCollationTraitDef.INSTANCE); 59 | private static final SqlParser.Config PARSER_CONFIG = SqlParser.configBuilder().setLex(Lex.MYSQL).setQuoting( 60 | Quoting.DOUBLE_QUOTE).build(); 61 | private static final Program PROGRAM = 62 | Programs.sequence(Programs.ofRules(Programs.RULE_SET), Programs.CALC_PROGRAM); 63 | private final FrameworkConfig frameworkConfig; 64 | private final GraphTraversalSource g; 65 | private final GremlinSchema gremlinSchema; 66 | 67 | 68 | public SqlConverter(final GremlinSchema gremlinSchema, final GraphTraversalSource g) { 69 | this.gremlinSchema = gremlinSchema; 70 | final SchemaPlus rootSchema = Frameworks.createRootSchema(true); 71 | this.frameworkConfig = Frameworks.newConfigBuilder() 72 | .parserConfig(PARSER_CONFIG) 73 | .defaultSchema(rootSchema.add("gremlin", gremlinSchema)) 74 | .traitDefs(TRAIT_DEFS) 75 | .programs(PROGRAM) 76 | .build(); 77 | this.g = g; 78 | } 79 | 80 | // NOT THREAD SAFE 81 | public SqlGremlinQueryResult executeQuery(final String query) throws SQLException { 82 | final SqlMetadata sqlMetadata = new SqlMetadata(g, gremlinSchema); 83 | GremlinSqlFactory.setSqlMetadata(sqlMetadata); 84 | // Not sure if this can be re-used? 85 | final QueryPlanner queryPlanner = new QueryPlanner(frameworkConfig); 86 | 87 | queryPlanner.plan(query); 88 | final SqlNode sqlNode = queryPlanner.getValidate(); 89 | 90 | if (sqlNode instanceof SqlSelect) { 91 | final GremlinSqlSelect gremlinSqlSelect = GremlinSqlFactory.createSelect((SqlSelect) sqlNode, g); 92 | return gremlinSqlSelect.executeTraversal(); 93 | } else { 94 | throw new SQLException("Only sql select statements are supported right now."); 95 | } 96 | } 97 | 98 | private GraphTraversal getGraphTraversal(final String query) throws SQLException { 99 | final SqlMetadata sqlMetadata = new SqlMetadata(g, gremlinSchema); 100 | GremlinSqlFactory.setSqlMetadata(sqlMetadata); 101 | // Not sure if this can be re-used? 102 | final QueryPlanner queryPlanner = new QueryPlanner(frameworkConfig); 103 | 104 | queryPlanner.plan(query); 105 | final SqlNode sqlNode = queryPlanner.getValidate(); 106 | 107 | if (sqlNode instanceof SqlSelect) { 108 | final GremlinSqlSelect gremlinSqlSelect = GremlinSqlFactory.createSelect((SqlSelect) sqlNode, g); 109 | return gremlinSqlSelect.generateTraversal(); 110 | } else { 111 | throw new SQLException("Only sql select statements are supported right now."); 112 | } 113 | } 114 | 115 | public String getStringTraversal(final String query) throws SQLException { 116 | return GroovyTranslator.of("g").translate(getGraphTraversal(query).asAdmin().getBytecode()); 117 | } 118 | 119 | @Getter 120 | private static class QueryPlanner { 121 | private final Planner planner; 122 | private SqlNode validate; 123 | 124 | QueryPlanner(final FrameworkConfig frameworkConfig) { 125 | this.planner = Frameworks.getPlanner(frameworkConfig); 126 | } 127 | 128 | public void plan(final String sql) throws SQLException { 129 | try { 130 | validate = planner.validate(planner.parse(sql)); 131 | } catch (final Exception e) { 132 | throw new SQLException(String.format("Error parsing: \"%s\". Error: \"%s\".", sql, e), e); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/SqlMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter; 21 | 22 | import lombok.Getter; 23 | import org.apache.calcite.sql.SqlAggFunction; 24 | import org.apache.calcite.sql.SqlCall; 25 | import org.apache.calcite.sql.SqlNode; 26 | import org.apache.calcite.sql.SqlNodeList; 27 | import org.apache.calcite.sql.SqlOperator; 28 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinSchema; 32 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinEdgeTable; 33 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinProperty; 34 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 35 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinVertexTable; 36 | import java.sql.SQLException; 37 | import java.util.ArrayList; 38 | import java.util.HashMap; 39 | import java.util.HashSet; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Optional; 43 | import java.util.Set; 44 | 45 | /** 46 | * This module contains traversal and query metadata used by the adapter. 47 | * 48 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 49 | */ 50 | @Getter 51 | public class SqlMetadata { 52 | private static final Logger LOGGER = LoggerFactory.getLogger(SqlMetadata.class); 53 | private final GraphTraversalSource g; 54 | private final GremlinSchema gremlinSchema; 55 | private final Map tableRenameMap = new HashMap<>(); 56 | private final Map columnRenameMap = new HashMap<>(); 57 | private final Map> columnOutputListMap = new HashMap<>(); 58 | private boolean isAggregate = false; 59 | 60 | public SqlMetadata(final GraphTraversalSource g, final GremlinSchema gremlinSchema) { 61 | this.g = g; 62 | this.gremlinSchema = gremlinSchema; 63 | } 64 | 65 | private static boolean isAggregate(final SqlNode sqlNode) { 66 | if (sqlNode instanceof SqlCall) { 67 | final SqlCall sqlCall = (SqlCall) sqlNode; 68 | if (isAggregate(sqlCall.getOperator())) { 69 | return true; 70 | } 71 | for (final SqlNode tmpSqlNode : sqlCall.getOperandList()) { 72 | if (isAggregate(tmpSqlNode)) { 73 | return true; 74 | } 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | private static boolean isAggregate(final SqlOperator sqlOperator) { 81 | return sqlOperator instanceof SqlAggFunction; 82 | } 83 | 84 | public boolean getIsColumnEdge(final String tableName, final String columnName) throws SQLException { 85 | return getGremlinTable(tableName).getIsVertex() && 86 | (columnName.endsWith(GremlinTableBase.IN_ID) || columnName.endsWith(GremlinTableBase.OUT_ID)); 87 | } 88 | 89 | public String getColumnEdgeLabel(final String column) throws SQLException { 90 | final String columnName = getRenamedColumn(column); 91 | final GremlinTableBase gremlinTableBase; 92 | if (columnName.endsWith(GremlinTableBase.IN_ID)) { 93 | gremlinTableBase = getGremlinTable(column.substring(0, column.length() - GremlinTableBase.IN_ID.length())); 94 | } else if (columnName.endsWith(GremlinTableBase.OUT_ID)) { 95 | gremlinTableBase = getGremlinTable(column.substring(0, column.length() - GremlinTableBase.OUT_ID.length())); 96 | } else { 97 | throw new SQLException(String.format("Error: Edge labels must end with %s or %s.", GremlinTableBase.IN_ID, 98 | GremlinTableBase.OUT_ID)); 99 | } 100 | 101 | if (gremlinTableBase.getIsVertex()) { 102 | throw new SQLException("Error: Expected edge table."); 103 | } 104 | return gremlinTableBase.getLabel(); 105 | } 106 | 107 | public boolean isLeftInRightOut(final String leftVertexLabel, final String rightVertexLabel) { 108 | for (final GremlinVertexTable gremlinVertexTable : gremlinSchema.getVertices()) { 109 | if (gremlinVertexTable.hasInEdge(leftVertexLabel) && gremlinVertexTable.hasOutEdge(rightVertexLabel)) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | public boolean isRightInLeftOut(final String leftVertexLabel, final String rightVertexLabel) { 117 | for (final GremlinVertexTable gremlinVertexTable : gremlinSchema.getVertices()) { 118 | if (gremlinVertexTable.hasInEdge(rightVertexLabel) && gremlinVertexTable.hasOutEdge(leftVertexLabel)) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | public Set getRenamedColumns() { 126 | return new HashSet<>(columnRenameMap.keySet()); 127 | } 128 | 129 | public void setColumnOutputList(final String table, final List columnOutputList) { 130 | columnOutputListMap.put(table, new ArrayList<>(columnOutputList)); 131 | } 132 | 133 | public Set getTables() throws SQLException { 134 | final Set tables = new HashSet<>(); 135 | for (final String table : tableRenameMap.values()) { 136 | tables.add(getGremlinTable(table)); 137 | } 138 | return tables; 139 | } 140 | 141 | public boolean isVertex(final String table) throws SQLException { 142 | final String renamedTableName = getRenamedTable(table); 143 | for (final GremlinVertexTable gremlinVertexTable : gremlinSchema.getVertices()) { 144 | if (gremlinVertexTable.getLabel().equalsIgnoreCase(renamedTableName)) { 145 | return true; 146 | } 147 | } 148 | for (final GremlinEdgeTable gremlinEdgeTable : gremlinSchema.getEdges()) { 149 | if (gremlinEdgeTable.getLabel().equalsIgnoreCase(renamedTableName)) { 150 | return false; 151 | } 152 | } 153 | throw new SQLException("Error: Table {} does not exist.", renamedTableName); 154 | } 155 | 156 | public GremlinTableBase getGremlinTable(final String table) throws SQLException { 157 | final String renamedTableName = getRenamedTable(table); 158 | for (final GremlinTableBase gremlinTableBase : gremlinSchema.getAllTables()) { 159 | if (gremlinTableBase.getLabel().equalsIgnoreCase(renamedTableName)) { 160 | return gremlinTableBase; 161 | } 162 | } 163 | throw new SQLException(String.format("Error: Table %s does not exist.", renamedTableName)); 164 | } 165 | 166 | public void addRenamedTable(final String actualName, final String renameName) { 167 | tableRenameMap.put(renameName, actualName); 168 | } 169 | 170 | public String getRenamedTable(final String table) { 171 | return tableRenameMap.getOrDefault(table, table); 172 | } 173 | 174 | public void addRenamedColumn(final String actualName, final String renameName) { 175 | columnRenameMap.put(renameName, actualName); 176 | } 177 | 178 | public String getRenamedColumn(final String column) { 179 | return columnRenameMap.getOrDefault(column, column); 180 | } 181 | 182 | public String getRenameFromActual(final String actual) { 183 | final Optional> 184 | rename = tableRenameMap.entrySet().stream().filter(t -> t.getValue().equals(actual)).findFirst(); 185 | if (rename.isPresent()) { 186 | return rename.get().getKey(); 187 | } 188 | return actual; 189 | } 190 | 191 | public String getActualColumnName(final GremlinTableBase table, final String column) throws SQLException { 192 | final String actualColumnName = getRenamedColumn(column); 193 | for (final GremlinProperty gremlinProperty : table.getColumns().values()) { 194 | if (gremlinProperty.getName().equalsIgnoreCase(actualColumnName)) { 195 | return gremlinProperty.getName(); 196 | } 197 | } 198 | throw new SQLException( 199 | String.format("Error: Column %s does not exist in table %s.", actualColumnName, table.getLabel())); 200 | } 201 | 202 | public boolean getTableHasColumn(final GremlinTableBase table, final String column) { 203 | final String actualColumnName = getRenamedColumn(column); 204 | for (final GremlinProperty gremlinProperty : table.getColumns().values()) { 205 | if (gremlinProperty.getName().equalsIgnoreCase(actualColumnName)) { 206 | return true; 207 | } 208 | } 209 | return false; 210 | } 211 | 212 | public String getActualTableName(final String table) throws SQLException { 213 | final String renamedTableName = getRenamedTable(table); 214 | for (final GremlinVertexTable gremlinVertexTable : gremlinSchema.getVertices()) { 215 | if (gremlinVertexTable.getLabel().equalsIgnoreCase(renamedTableName)) { 216 | return gremlinVertexTable.getLabel(); 217 | } 218 | } 219 | for (final GremlinEdgeTable gremlinEdgeTable : gremlinSchema.getEdges()) { 220 | if (gremlinEdgeTable.getLabel().equalsIgnoreCase(renamedTableName)) { 221 | return gremlinEdgeTable.getLabel(); 222 | } 223 | } 224 | throw new SQLException(String.format("Error: Table %s.", table)); 225 | } 226 | 227 | public void checkAggregate(final SqlNodeList sqlNodeList) { 228 | isAggregate = sqlNodeList.getList().stream().allMatch(SqlMetadata::isAggregate); 229 | } 230 | 231 | public boolean getIsAggregate() { 232 | return isAggregate; 233 | } 234 | 235 | public GremlinProperty getGremlinProperty(final String table, final String column) throws SQLException { 236 | final String actualColumnName = getActualColumnName(getGremlinTable(table), column); 237 | return getGremlinTable(table).getColumn(actualColumnName); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/SqlTraversalEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter; 21 | 22 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 23 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 24 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.StepDirection; 29 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 30 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 31 | import java.sql.SQLException; 32 | import java.util.ArrayList; 33 | import java.util.List; 34 | 35 | import static org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase.IN_ID; 36 | import static org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase.OUT_ID; 37 | 38 | /** 39 | * Traversal engine for SQL-Gremlin. This module is responsible for generating the gremlin traversals. 40 | * 41 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 42 | */ 43 | public class SqlTraversalEngine { 44 | private static final Logger LOGGER = LoggerFactory.getLogger(SqlTraversalEngine.class); 45 | 46 | public static GraphTraversal generateInitialSql(final List gremlinSqlIdentifiers, 47 | final SqlMetadata sqlMetadata, 48 | final GraphTraversalSource g) throws SQLException { 49 | if (gremlinSqlIdentifiers.size() != 2) { 50 | throw new SQLException("Expected GremlinSqlIdentifier List size to be 2."); 51 | } 52 | final String label = sqlMetadata.getActualTableName(gremlinSqlIdentifiers.get(0).getName(1)); 53 | final GraphTraversal graphTraversal = sqlMetadata.isVertex(label) ? g.V() : g.E(); 54 | graphTraversal.hasLabel(label); 55 | return graphTraversal; 56 | } 57 | 58 | public static void applyAggregateFold(final SqlMetadata sqlMetadata, final GraphTraversal graphTraversal) { 59 | if (sqlMetadata.getIsAggregate()) { 60 | graphTraversal.fold(); 61 | } 62 | } 63 | 64 | public static GraphTraversal getEmptyTraversal(final StepDirection direction, final SqlMetadata sqlMetadata) { 65 | final GraphTraversal graphTraversal = __.unfold(); 66 | if (sqlMetadata.getIsAggregate()) { 67 | graphTraversal.unfold(); 68 | } 69 | switch (direction) { 70 | case Out: 71 | return graphTraversal.outV(); 72 | case In: 73 | return graphTraversal.inV(); 74 | } 75 | return graphTraversal; 76 | } 77 | 78 | public static void addProjection(final List gremlinSqlIdentifiers, 79 | final SqlMetadata sqlMetadata, 80 | final GraphTraversal graphTraversal) throws SQLException { 81 | if (gremlinSqlIdentifiers.size() != 2) { 82 | throw new SQLException("Expected GremlinSqlIdentifier List size to be 2."); 83 | } 84 | final String label = sqlMetadata.getActualTableName(gremlinSqlIdentifiers.get(0).getName(1)); 85 | final String projectLabel = gremlinSqlIdentifiers.get(1).getName(0); 86 | 87 | graphTraversal.project(projectLabel); 88 | sqlMetadata.addRenamedTable(label, projectLabel); 89 | } 90 | 91 | public static GraphTraversal getEmptyTraversal(final SqlMetadata sqlMetadata) { 92 | return getEmptyTraversal(StepDirection.None, sqlMetadata); 93 | } 94 | 95 | 96 | public static void applyTraversal(final GraphTraversal graphTraversal, 97 | final GraphTraversal subGraphTraversal) { 98 | graphTraversal.by(subGraphTraversal); 99 | } 100 | 101 | public static void applySqlIdentifier(final GremlinSqlIdentifier sqlIdentifier, 102 | final SqlMetadata sqlMetadata, 103 | final GraphTraversal graphTraversal) throws SQLException { 104 | // Format of identifier is 'table'.'column => ['table', 'column'] 105 | appendGraphTraversal(sqlIdentifier.getName(0), sqlIdentifier.getName(1), sqlMetadata, graphTraversal); 106 | } 107 | 108 | public static GraphTraversal applyColumnRenames(final List columnsRenamed) throws SQLException { 109 | final String firstColumn = columnsRenamed.remove(0); 110 | final String[] remaining = columnsRenamed.toArray(new String[] {}); 111 | return __.project(firstColumn, remaining); 112 | } 113 | 114 | private static void appendGraphTraversal(final String table, final String column, 115 | final SqlMetadata sqlMetadata, 116 | final GraphTraversal graphTraversal) throws SQLException { 117 | final GremlinTableBase gremlinTableBase = sqlMetadata.getGremlinTable(table); 118 | final String columnName = sqlMetadata.getActualColumnName(gremlinTableBase, column); 119 | 120 | // Primary/foreign key, need to traverse appropriately. 121 | if (!columnName.endsWith(GremlinTableBase.ID)) { 122 | if (sqlMetadata.getIsAggregate()) { 123 | graphTraversal.values(columnName); 124 | } else { 125 | graphTraversal.choose(__.has(columnName), __.values(columnName), 126 | __.constant(SqlGremlinQueryResult.NULL_VALUE)); 127 | } 128 | } else { 129 | // It's this vertex/edge. 130 | if (columnName.toLowerCase().startsWith(gremlinTableBase.getLabel())) { 131 | graphTraversal.id(); 132 | } else { 133 | if (columnName.endsWith(IN_ID)) { 134 | // Vertices can have many connected, edges (thus we need to fold). Edges can only connect to 1 vertex. 135 | if (gremlinTableBase.getIsVertex()) { 136 | graphTraversal.coalesce(__.inE().hasLabel(columnName.replace(IN_ID, "")).id().fold(), 137 | __.constant(new ArrayList<>())); 138 | } else { 139 | graphTraversal.coalesce(__.inV().hasLabel(columnName.replace(IN_ID, "")).id(), 140 | __.constant(new ArrayList<>())); 141 | } 142 | } else if (column.endsWith(OUT_ID)) { 143 | // Vertices can have many connected, edges (thus we need to fold). Edges can only connect to 1 vertex. 144 | if (gremlinTableBase.getIsVertex()) { 145 | graphTraversal.coalesce(__.outE().hasLabel(columnName.replace(OUT_ID, "")).id().fold(), 146 | __.constant(new ArrayList<>())); 147 | } else { 148 | graphTraversal.coalesce(__.outV().hasLabel(columnName.replace(IN_ID, "")).id(), 149 | __.constant(new ArrayList<>())); 150 | } 151 | } else { 152 | graphTraversal.constant(new ArrayList<>()); 153 | } 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/GremlinSqlFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes; 21 | 22 | import org.apache.calcite.sql.SqlAggFunction; 23 | import org.apache.calcite.sql.SqlAsOperator; 24 | import org.apache.calcite.sql.SqlBasicCall; 25 | import org.apache.calcite.sql.SqlBinaryOperator; 26 | import org.apache.calcite.sql.SqlCall; 27 | import org.apache.calcite.sql.SqlIdentifier; 28 | import org.apache.calcite.sql.SqlJoin; 29 | import org.apache.calcite.sql.SqlNode; 30 | import org.apache.calcite.sql.SqlNumericLiteral; 31 | import org.apache.calcite.sql.SqlOperator; 32 | import org.apache.calcite.sql.SqlPostfixOperator; 33 | import org.apache.calcite.sql.SqlSelect; 34 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 35 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 36 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 37 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlAsOperator; 38 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlBasicCall; 39 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlOperator; 40 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlPostFixOperator; 41 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.aggregate.GremlinSqlAggFunction; 42 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlBinaryOperator; 43 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlNumericLiteral; 44 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.GremlinSqlSelect; 45 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.GremlinSqlSelectMulti; 46 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.GremlinSqlSelectSingle; 47 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.join.GremlinSqlJoinComparison; 48 | import java.sql.SQLException; 49 | import java.util.ArrayList; 50 | import java.util.List; 51 | 52 | /** 53 | * This factory converts different types of Calcite's SqlNode/SqlOperator's to SqlGremlin equivalents. 54 | * 55 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 56 | * @author Adapted from implementation by twilmes (https://github.com/twilmes/sql-gremlin) 57 | */ 58 | public class GremlinSqlFactory { 59 | private static SqlMetadata sqlMetadata = null; 60 | 61 | public static void setSqlMetadata(final SqlMetadata sqlMetadata1) { 62 | sqlMetadata = sqlMetadata1; 63 | } 64 | 65 | public static SqlMetadata getGremlinSqlMetadata() throws SQLException { 66 | if (sqlMetadata == null) { 67 | throw new SQLException("Error: Schema must be set."); 68 | } 69 | return sqlMetadata; 70 | } 71 | 72 | public static GremlinSqlJoinComparison createJoinEquality(final SqlNode sqlNode) 73 | throws SQLException { 74 | if (sqlNode instanceof SqlBasicCall) { 75 | final SqlBasicCall sqlBasicCall = (SqlBasicCall) sqlNode; 76 | if (sqlBasicCall.getOperator() instanceof SqlBinaryOperator) { 77 | return new GremlinSqlJoinComparison((SqlBasicCall) sqlNode, 78 | (SqlBinaryOperator) sqlBasicCall.getOperator(), createNodeList(sqlBasicCall.getOperandList()), 79 | getGremlinSqlMetadata()); 80 | } 81 | } 82 | throw new SQLException(String.format("Error: Unknown node: %s.", sqlNode.getClass().getName())); 83 | } 84 | 85 | public static GremlinSqlOperator createOperator(final SqlOperator sqlOperator, final List sqlOperands) 86 | throws SQLException { 87 | if (sqlOperator instanceof SqlAsOperator) { 88 | return new GremlinSqlAsOperator((SqlAsOperator) sqlOperator, createNodeList(sqlOperands), 89 | getGremlinSqlMetadata()); 90 | } else if (sqlOperator instanceof SqlAggFunction) { 91 | return new GremlinSqlAggFunction((SqlAggFunction) sqlOperator, createNodeList(sqlOperands), 92 | getGremlinSqlMetadata()); 93 | } else if (sqlOperator instanceof SqlBinaryOperator) { 94 | return new GremlinSqlBinaryOperator((SqlBinaryOperator) sqlOperator, createNodeList(sqlOperands), 95 | getGremlinSqlMetadata()); 96 | } else if (sqlOperator instanceof SqlPostfixOperator) { 97 | return new GremlinSqlPostFixOperator((SqlPostfixOperator) sqlOperator, createNodeList(sqlOperands), 98 | getGremlinSqlMetadata()); 99 | } 100 | throw new SQLException(String.format("Error: Unknown operator: %s.", sqlOperator.getKind().sql)); 101 | } 102 | 103 | public static GremlinSqlNode createNode(final SqlNode sqlNode) throws SQLException { 104 | if (sqlNode instanceof SqlBasicCall) { 105 | return new GremlinSqlBasicCall((SqlBasicCall) sqlNode, getGremlinSqlMetadata()); 106 | } else if (sqlNode instanceof SqlIdentifier) { 107 | return new GremlinSqlIdentifier((SqlIdentifier) sqlNode, getGremlinSqlMetadata()); 108 | } else if (sqlNode instanceof SqlNumericLiteral) { 109 | return new GremlinSqlNumericLiteral((SqlNumericLiteral) sqlNode, getGremlinSqlMetadata()); 110 | } 111 | throw new SQLException(String.format("Error: Unknown node: %s.", sqlNode.getClass().getName())); 112 | } 113 | 114 | public static List createNodeList(final List sqlNodes) throws SQLException { 115 | final List gremlinSqlNodes = new ArrayList<>(); 116 | for (final SqlNode sqlNode : sqlNodes) { 117 | gremlinSqlNodes.add(createNode(sqlNode)); 118 | } 119 | return gremlinSqlNodes; 120 | } 121 | 122 | @SuppressWarnings("unchecked") 123 | public static T createNodeCheckType(final SqlNode sqlNode, final Class clazz) throws SQLException { 124 | final GremlinSqlNode gremlinSqlNode = createNode(sqlNode); 125 | if (!gremlinSqlNode.getClass().equals(clazz)) { 126 | throw new SQLException("Error: Type mismatch."); 127 | } 128 | return (T) gremlinSqlNode; 129 | } 130 | 131 | public static GremlinSqlSelect createSelect(final SqlSelect selectRoot, final GraphTraversalSource g) 132 | throws SQLException { 133 | if (selectRoot.getFrom() instanceof SqlJoin) { 134 | return new GremlinSqlSelectMulti(selectRoot, (SqlJoin) selectRoot.getFrom(), sqlMetadata, g); 135 | } else if (selectRoot.getFrom() instanceof SqlBasicCall) { 136 | return new GremlinSqlSelectSingle(selectRoot, (SqlBasicCall) selectRoot.getFrom(), sqlMetadata, g); 137 | } 138 | throw new SQLException(String.format("Error: Unknown node for getFrom: %s.", selectRoot.getFrom().getClass().getName())); 139 | } 140 | 141 | public static boolean isTable(final SqlNode sqlNode, final String renamedTable) throws SQLException { 142 | if (sqlNode instanceof SqlIdentifier) { 143 | return (((SqlIdentifier) sqlNode).names.get(0).equalsIgnoreCase(renamedTable)); 144 | } else if (sqlNode instanceof SqlCall) { 145 | for (final SqlNode tmpSqlNode : ((SqlCall) sqlNode).getOperandList()) { 146 | if (isTable(tmpSqlNode, renamedTable)) { 147 | return true; 148 | } 149 | } 150 | } else { 151 | throw new SQLException("Error: Unknown node type for isTable."); 152 | } 153 | return false; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/GremlinSqlNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes; 21 | 22 | import lombok.AllArgsConstructor; 23 | import org.apache.calcite.sql.SqlNode; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 27 | 28 | /** 29 | * This abstract class in the GremlinSql equivalent of SqlNode. 30 | * 31 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 32 | * @author Adapted from implementation by twilmes (https://github.com/twilmes/sql-gremlin) 33 | */ 34 | @AllArgsConstructor 35 | public abstract class GremlinSqlNode { 36 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlNode.class); 37 | private final SqlNode sqlNode; 38 | private final SqlMetadata sqlMetadata; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operands/GremlinSqlIdentifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands; 21 | 22 | import org.apache.calcite.sql.SqlIdentifier; 23 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 24 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 25 | import java.sql.SQLException; 26 | 27 | /** 28 | * This module is a GremlinSql equivalent of Calcite's SqlIdentifier. 29 | * 30 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 31 | */ 32 | public class GremlinSqlIdentifier extends GremlinSqlNode { 33 | private final SqlIdentifier sqlIdentifier; 34 | 35 | public GremlinSqlIdentifier(final SqlIdentifier sqlIdentifier, final SqlMetadata sqlMetadata) { 36 | super(sqlIdentifier, sqlMetadata); 37 | this.sqlIdentifier = sqlIdentifier; 38 | } 39 | 40 | 41 | public String getName(final int idx) throws SQLException { 42 | if (idx >= sqlIdentifier.names.size()) { 43 | throw new SQLException("Index of identifier > size of name list for identifier"); 44 | } 45 | return sqlIdentifier.names.get(idx); 46 | } 47 | 48 | 49 | public String getColumn() throws SQLException { 50 | if (sqlIdentifier.names.size() < 1) { 51 | throw new SQLException("Expected at least one name in list for identifier"); 52 | } 53 | return sqlIdentifier.names.get(sqlIdentifier.names.size() - 1); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/GremlinSqlAsOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator; 21 | 22 | import org.apache.calcite.sql.SqlAsOperator; 23 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 27 | import org.twilmes.sql.gremlin.adapter.converter.SqlTraversalEngine; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 29 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 30 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlNumericLiteral; 31 | import java.sql.SQLException; 32 | import java.util.List; 33 | 34 | /** 35 | * This module is a GremlinSql equivalent of Calcite's SqlAsOperator. 36 | * 37 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 38 | */ 39 | public class GremlinSqlAsOperator extends GremlinSqlOperator { 40 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlAsOperator.class); 41 | private final SqlAsOperator sqlAsOperator; 42 | private final SqlMetadata sqlMetadata; 43 | private final List sqlOperands; 44 | 45 | public GremlinSqlAsOperator(final SqlAsOperator sqlAsOperator, final List gremlinSqlNodes, 46 | final SqlMetadata sqlMetadata) { 47 | super(sqlAsOperator, gremlinSqlNodes, sqlMetadata); 48 | this.sqlAsOperator = sqlAsOperator; 49 | this.sqlMetadata = sqlMetadata; 50 | this.sqlOperands = gremlinSqlNodes; 51 | } 52 | 53 | @Override 54 | protected void appendTraversal(final GraphTraversal graphTraversal) throws SQLException { 55 | if (sqlOperands.get(0) instanceof GremlinSqlBasicCall) { 56 | ((GremlinSqlBasicCall) sqlOperands.get(0)).generateTraversal(graphTraversal); 57 | } else if (!(sqlOperands.get(0) instanceof GremlinSqlIdentifier) && 58 | !(sqlOperands.get(0) instanceof GremlinSqlNumericLiteral)) { 59 | throw new SQLException( 60 | "Error: expected operand to be GremlinSqlBasicCall or GremlinSqlIdentifier in GremlinSqlOperator."); 61 | } 62 | 63 | if (sqlOperands.size() == 1) { 64 | if (sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 65 | SqlTraversalEngine 66 | .applySqlIdentifier((GremlinSqlIdentifier) sqlOperands.get(0), sqlMetadata, graphTraversal); 67 | } 68 | } 69 | if (sqlOperands.size() == 2 && sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 70 | SqlTraversalEngine 71 | .applySqlIdentifier((GremlinSqlIdentifier) sqlOperands.get(0), sqlMetadata, graphTraversal); 72 | } 73 | sqlMetadata.addRenamedColumn(getActual(), getRename()); 74 | } 75 | 76 | public String getName(final int operandIdx, final int nameIdx) throws SQLException { 77 | if (operandIdx >= sqlOperands.size() || !(sqlOperands.get(operandIdx) instanceof GremlinSqlIdentifier)) { 78 | throw new SQLException( 79 | "Error: Expected operand idx less than number of operands and GremlinSqlIdentifier for operand"); 80 | } 81 | return ((GremlinSqlIdentifier) sqlOperands.get(operandIdx)).getName(nameIdx); 82 | } 83 | 84 | public String getActual() throws SQLException { 85 | if (sqlOperands.size() != 2) { 86 | throw new SQLException("Error: Expected two operands for SQL AS statement."); 87 | } 88 | if (sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 89 | return ((GremlinSqlIdentifier) sqlOperands.get(0)).getColumn(); 90 | } else if (sqlOperands.get(0) instanceof GremlinSqlBasicCall) { 91 | return ((GremlinSqlBasicCall) sqlOperands.get(0)).getActual(); 92 | } 93 | throw new SQLException("Error, unable to get actual name in GremlinSqlAsOperator."); 94 | } 95 | 96 | public String getRename() throws SQLException { 97 | if (sqlOperands.size() != 2) { 98 | throw new SQLException("Error: Expected two operands for SQL AS statement."); 99 | } 100 | if (sqlOperands.get(1) instanceof GremlinSqlIdentifier) { 101 | return ((GremlinSqlIdentifier) sqlOperands.get(1)).getColumn(); 102 | } else if (sqlOperands.get(1) instanceof GremlinSqlBasicCall) { 103 | return ((GremlinSqlBasicCall) sqlOperands.get(1)).getRename(); 104 | } 105 | throw new SQLException("Error, unable to get rename name in GremlinSqlAsOperator."); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/GremlinSqlBasicCall.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator; 21 | 22 | import lombok.Getter; 23 | import org.apache.calcite.sql.SqlBasicCall; 24 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlFactory; 29 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 30 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 31 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.aggregate.GremlinSqlAggFunction; 32 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlNumericLiteral; 33 | import java.sql.SQLException; 34 | import java.util.List; 35 | 36 | /** 37 | * This module is a GremlinSql equivalent of Calcite's SqlBasicCall. 38 | * 39 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 40 | */ 41 | @Getter 42 | public class GremlinSqlBasicCall extends GremlinSqlNode { 43 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlBasicCall.class); 44 | private final SqlBasicCall sqlBasicCall; 45 | private final GremlinSqlOperator gremlinSqlOperator; 46 | private final List gremlinSqlNodes; 47 | 48 | public GremlinSqlBasicCall(final SqlBasicCall sqlBasicCall, final SqlMetadata sqlMetadata) 49 | throws SQLException { 50 | super(sqlBasicCall, sqlMetadata); 51 | this.sqlBasicCall = sqlBasicCall; 52 | gremlinSqlOperator = 53 | GremlinSqlFactory.createOperator(sqlBasicCall.getOperator(), sqlBasicCall.getOperandList()); 54 | gremlinSqlNodes = GremlinSqlFactory.createNodeList(sqlBasicCall.getOperandList()); 55 | } 56 | 57 | void validate() throws SQLException { 58 | if (gremlinSqlOperator instanceof GremlinSqlAsOperator) { 59 | if (gremlinSqlNodes.size() != 2) { 60 | throw new SQLException("Error, expected only two sub nodes for GremlinSqlBasicCall."); 61 | } 62 | } else if (gremlinSqlOperator instanceof GremlinSqlAggFunction) { 63 | if (gremlinSqlNodes.size() != 1) { 64 | throw new SQLException("Error, expected only one sub node for GremlinSqlAggFunction."); 65 | } 66 | } 67 | } 68 | 69 | public void generateTraversal(final GraphTraversal graphTraversal) throws SQLException { 70 | validate(); 71 | gremlinSqlOperator.appendOperatorTraversal(graphTraversal); 72 | } 73 | 74 | public String getRename() throws SQLException { 75 | if (gremlinSqlOperator instanceof GremlinSqlAsOperator) { 76 | return ((GremlinSqlAsOperator) gremlinSqlOperator).getRename(); 77 | } else if (gremlinSqlOperator instanceof GremlinSqlAggFunction) { 78 | if (gremlinSqlNodes.size() == 1 && gremlinSqlNodes.get(0) instanceof GremlinSqlIdentifier) { 79 | // returns the formatted column name for aggregations 80 | return ((GremlinSqlAggFunction) gremlinSqlOperator).getNewName(); 81 | } 82 | } 83 | throw new SQLException("Unable to determine column rename."); 84 | } 85 | 86 | public String getActual() throws SQLException { 87 | if (gremlinSqlOperator instanceof GremlinSqlAsOperator) { 88 | return ((GremlinSqlAsOperator) gremlinSqlOperator).getActual(); 89 | } else if (gremlinSqlOperator instanceof GremlinSqlAggFunction) { 90 | if (gremlinSqlNodes.size() == 1 && gremlinSqlNodes.get(0) instanceof GremlinSqlIdentifier) { 91 | return ((GremlinSqlIdentifier) gremlinSqlNodes.get(0)).getColumn(); 92 | } else if (gremlinSqlNodes.size() == 2 && gremlinSqlNodes.get(1) instanceof GremlinSqlIdentifier) { 93 | return ((GremlinSqlIdentifier) gremlinSqlNodes.get(1)).getColumn(); 94 | } else if (gremlinSqlNodes.size() == 1 && gremlinSqlNodes.get(0) instanceof GremlinSqlNumericLiteral) { 95 | return ((GremlinSqlAggFunction) gremlinSqlOperator).getNewName(); 96 | } 97 | } 98 | throw new SQLException("Unable to determine actual column name."); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/GremlinSqlOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator; 21 | 22 | import lombok.AllArgsConstructor; 23 | import org.apache.calcite.sql.SqlOperator; 24 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 29 | import java.sql.SQLException; 30 | import java.util.List; 31 | 32 | /** 33 | * This abstract class is a GremlinSql equivalent of Calcite's SqlOperator. 34 | * 35 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 36 | */ 37 | @AllArgsConstructor 38 | public abstract class GremlinSqlOperator { 39 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlOperator.class); 40 | private final SqlOperator sqlOperator; 41 | private final List sqlOperands; 42 | private final SqlMetadata sqlMetadata; 43 | 44 | protected abstract void appendTraversal(GraphTraversal graphTraversal) throws SQLException; 45 | 46 | public void appendOperatorTraversal(final GraphTraversal graphTraversal) throws SQLException { 47 | if (sqlOperands.size() > 2) { 48 | throw new SQLException("Error: Expected 2 or less operands in operations."); 49 | } else if (sqlOperands.isEmpty()) { 50 | throw new SQLException("Error: Expected at least 1 operand in operations."); 51 | } 52 | 53 | appendTraversal(graphTraversal); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/GremlinSqlPostFixOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator; 21 | 22 | import org.apache.calcite.sql.SqlKind; 23 | import org.apache.calcite.sql.SqlPostfixOperator; 24 | import org.apache.tinkerpop.gremlin.process.traversal.Order; 25 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 29 | import org.twilmes.sql.gremlin.adapter.converter.SqlTraversalEngine; 30 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 31 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 32 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlNumericLiteral; 33 | import java.sql.SQLException; 34 | import java.util.List; 35 | 36 | /** 37 | * This module is a GremlinSql equivalent of Calcite's SqlPostFixOperator. 38 | * 39 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 40 | */ 41 | public class GremlinSqlPostFixOperator extends GremlinSqlOperator { 42 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlAsOperator.class); 43 | private final SqlPostfixOperator sqlPostfixOperator; 44 | private final SqlMetadata sqlMetadata; 45 | private final List sqlOperands; 46 | 47 | public GremlinSqlPostFixOperator(final SqlPostfixOperator sqlPostfixOperator, final List gremlinSqlNodes, 48 | final SqlMetadata sqlMetadata) { 49 | super(sqlPostfixOperator, gremlinSqlNodes, sqlMetadata); 50 | this.sqlPostfixOperator = sqlPostfixOperator; 51 | this.sqlMetadata = sqlMetadata; 52 | this.sqlOperands = gremlinSqlNodes; 53 | } 54 | 55 | public Order getOrder() throws SQLException { 56 | if (sqlPostfixOperator.kind.equals(SqlKind.DESCENDING)) { 57 | return Order.desc; 58 | } 59 | throw new SQLException("Error, no appropriate order for GremlinSqlPostFixOperator of " + sqlPostfixOperator.kind.sql + "."); 60 | } 61 | 62 | @Override 63 | protected void appendTraversal(final GraphTraversal graphTraversal) throws SQLException { 64 | if (sqlOperands.get(0) instanceof GremlinSqlBasicCall) { 65 | ((GremlinSqlBasicCall) sqlOperands.get(0)).generateTraversal(graphTraversal); 66 | } else if (!(sqlOperands.get(0) instanceof GremlinSqlIdentifier) && !(sqlOperands.get(0) instanceof GremlinSqlNumericLiteral)) { 67 | throw new SQLException( 68 | "Error: expected operand to be GremlinSqlBasicCall or GremlinSqlIdentifier in GremlinSqlOperator."); 69 | } 70 | 71 | if (sqlOperands.size() == 1) { 72 | if (sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 73 | SqlTraversalEngine 74 | .applySqlIdentifier((GremlinSqlIdentifier) sqlOperands.get(0), sqlMetadata, graphTraversal); 75 | } 76 | } 77 | if (sqlOperands.size() == 2 && sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 78 | SqlTraversalEngine.applySqlIdentifier((GremlinSqlIdentifier) sqlOperands.get(0), sqlMetadata, graphTraversal); 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/GremlinSqlTraversalAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator; 21 | 22 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 23 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 24 | import java.sql.SQLException; 25 | import java.util.List; 26 | 27 | /** 28 | * Interface for traversal appending function. 29 | * 30 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 31 | */ 32 | public interface GremlinSqlTraversalAppender { 33 | void appendTraversal(GraphTraversal graphTraversal, List operands) throws SQLException; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/aggregate/GremlinSqlAggFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.aggregate; 21 | 22 | import org.apache.calcite.sql.SqlAggFunction; 23 | import org.apache.calcite.sql.SqlKind; 24 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 25 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 26 | import org.twilmes.sql.gremlin.adapter.converter.SqlTraversalEngine; 27 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 29 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlBasicCall; 30 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlOperator; 31 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlTraversalAppender; 32 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic.GremlinSqlNumericLiteral; 33 | import java.sql.SQLException; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | /** 39 | * This module is a GremlinSql equivalent of Calcite's SqlAggFunction. 40 | * 41 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 42 | */ 43 | public class GremlinSqlAggFunction extends GremlinSqlOperator { 44 | // See SqlKind.AGGREGATE for list of aggregate functions in Calcite. 45 | private static final Map AGGREGATE_APPENDERS = 46 | new HashMap() {{ 47 | put(SqlKind.AVG, GremlinSqlAggFunctionImplementations.AVG); 48 | put(SqlKind.COUNT, GremlinSqlAggFunctionImplementations.COUNT); 49 | put(SqlKind.SUM, GremlinSqlAggFunctionImplementations.SUM); 50 | put(SqlKind.MIN, GremlinSqlAggFunctionImplementations.MIN); 51 | put(SqlKind.MAX, GremlinSqlAggFunctionImplementations.MAX); 52 | }}; 53 | private final SqlAggFunction sqlAggFunction; 54 | private final SqlMetadata sqlMetadata; 55 | private final List sqlOperands; 56 | 57 | 58 | public GremlinSqlAggFunction(final SqlAggFunction sqlOperator, 59 | final List gremlinSqlNodes, 60 | final SqlMetadata sqlMetadata) { 61 | super(sqlOperator, gremlinSqlNodes, sqlMetadata); 62 | this.sqlAggFunction = sqlOperator; 63 | this.sqlMetadata = sqlMetadata; 64 | this.sqlOperands = gremlinSqlNodes; 65 | } 66 | 67 | @Override 68 | protected void appendTraversal(final GraphTraversal graphTraversal) throws SQLException { 69 | if (sqlOperands.get(0) instanceof GremlinSqlBasicCall) { 70 | ((GremlinSqlBasicCall) sqlOperands.get(0)).generateTraversal(graphTraversal); 71 | } else if (!(sqlOperands.get(0) instanceof GremlinSqlIdentifier) && 72 | !(sqlOperands.get(0) instanceof GremlinSqlNumericLiteral)) { 73 | throw new SQLException( 74 | "Error: expected operand to be GremlinSqlBasicCall or GremlinSqlIdentifier in GremlinSqlOperator."); 75 | } 76 | 77 | if (sqlOperands.size() == 1) { 78 | if (sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 79 | SqlTraversalEngine 80 | .applySqlIdentifier((GremlinSqlIdentifier) sqlOperands.get(0), sqlMetadata, graphTraversal); 81 | } 82 | } 83 | if (AGGREGATE_APPENDERS.containsKey(sqlAggFunction.kind)) { 84 | AGGREGATE_APPENDERS.get(sqlAggFunction.kind).appendTraversal(graphTraversal, sqlOperands); 85 | } else { 86 | throw new SQLException( 87 | String.format("Error: Aggregate function %s is not supported.", sqlAggFunction.kind.sql)); 88 | } 89 | } 90 | 91 | /** 92 | * Aggregation columns will be named in the form of AGG(xxx) if no rename is specified in SQL 93 | */ 94 | public String getNewName() throws SQLException { 95 | if (sqlOperands.size() == 1 && sqlOperands.get(0) instanceof GremlinSqlIdentifier) { 96 | return String.format("%s(%s)", sqlAggFunction.kind.name(), 97 | ((GremlinSqlIdentifier) sqlOperands.get(0)).getColumn()); 98 | } else if (sqlOperands.size() == 2 && sqlOperands.get(1) instanceof GremlinSqlIdentifier) { 99 | return String.format("%s(%s)", sqlAggFunction.kind.name(), 100 | ((GremlinSqlIdentifier) sqlOperands.get(1)).getColumn()); 101 | } else if (sqlOperands.size() == 1 && sqlOperands.get(0) instanceof GremlinSqlNumericLiteral) { 102 | return String.format("%s(%s)", sqlAggFunction.kind.name(), 103 | ((GremlinSqlNumericLiteral) sqlOperands.get(0)).getValue().toString()); 104 | } 105 | throw new SQLException("Error, unable to get rename name in GremlinSqlAggOperator."); 106 | } 107 | 108 | private static class GremlinSqlAggFunctionImplementations { 109 | public static final GremlinSqlTraversalAppender AVG = 110 | (GraphTraversal graphTraversal, List operands) -> { 111 | graphTraversal.mean(); 112 | }; 113 | public static final GremlinSqlTraversalAppender COUNT = 114 | (GraphTraversal graphTraversal, List operands) -> { 115 | graphTraversal.count(); 116 | }; 117 | public static final GremlinSqlTraversalAppender SUM = 118 | (GraphTraversal graphTraversal, List operands) -> { 119 | graphTraversal.sum(); 120 | }; 121 | public static final GremlinSqlTraversalAppender MIN = 122 | (GraphTraversal graphTraversal, List operands) -> { 123 | graphTraversal.min(); 124 | }; 125 | public static final GremlinSqlTraversalAppender MAX = 126 | (GraphTraversal graphTraversal, List operands) -> { 127 | graphTraversal.max(); 128 | }; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/logic/GremlinSqlBinaryOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic; 21 | 22 | import org.apache.calcite.sql.SqlBinaryOperator; 23 | import org.apache.calcite.sql.SqlKind; 24 | import org.apache.tinkerpop.gremlin.process.traversal.P; 25 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 26 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 30 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 31 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 32 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlBasicCall; 33 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlOperator; 34 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlTraversalAppender; 35 | import java.sql.SQLException; 36 | import java.util.HashMap; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Random; 40 | 41 | /** 42 | * This module is a GremlinSql equivalent of Calcite's GremlinSqlBinaryOperator. 43 | * 44 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 45 | */ 46 | public class GremlinSqlBinaryOperator extends GremlinSqlOperator { 47 | private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; 48 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlBinaryOperator.class); 49 | private static final Map BINARY_APPENDERS = 50 | new HashMap() { 51 | { 52 | put(SqlKind.EQUALS, new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderEquals()); 53 | put(SqlKind.NOT_EQUALS, 54 | new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderNotEquals()); 55 | put(SqlKind.GREATER_THAN, 56 | new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderGreater()); 57 | put(SqlKind.GREATER_THAN_OR_EQUAL, 58 | new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderGreaterEquals()); 59 | put(SqlKind.LESS_THAN, new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderLess()); 60 | put(SqlKind.LESS_THAN_OR_EQUAL, 61 | new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderLessEquals()); 62 | put(SqlKind.AND, new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderAnd()); 63 | put(SqlKind.OR, new GremlinSqlBinaryOperatorAppender.GremlinSqlBinaryOperatorAppenderOr()); 64 | } 65 | }; 66 | private final SqlBinaryOperator sqlBinaryOperator; 67 | private final SqlMetadata sqlMetadata; 68 | private final List sqlOperands; 69 | 70 | public GremlinSqlBinaryOperator(final SqlBinaryOperator sqlBinaryOperator, 71 | final List sqlOperands, 72 | final SqlMetadata sqlMetadata) { 73 | super(sqlBinaryOperator, sqlOperands, sqlMetadata); 74 | this.sqlBinaryOperator = sqlBinaryOperator; 75 | this.sqlMetadata = sqlMetadata; 76 | this.sqlOperands = sqlOperands; 77 | } 78 | 79 | private static String getRandomString() { 80 | final StringBuilder salt = new StringBuilder(); 81 | final Random rnd = new Random(); 82 | while (salt.length() < 10) { // length of the random string. 83 | final int index = (int) (rnd.nextFloat() * CHARS.length()); 84 | salt.append(CHARS.charAt(index)); 85 | } 86 | return salt.toString(); 87 | } 88 | 89 | @Override 90 | protected void appendTraversal(final GraphTraversal graphTraversal) throws SQLException { 91 | if (BINARY_APPENDERS.containsKey(sqlBinaryOperator.kind)) { 92 | BINARY_APPENDERS.get(sqlBinaryOperator.kind).appendTraversal(graphTraversal, sqlOperands); 93 | } else { 94 | throw new SQLException( 95 | String.format("Error: Aggregate function %s is not supported.", sqlBinaryOperator.kind.sql)); 96 | } 97 | } 98 | 99 | private static class GremlinSqlBinaryOperatorAppender { 100 | private static GraphTraversal[] getTraversalsFromOperands(final List operands) 101 | throws SQLException { 102 | if (operands.size() != 2) { 103 | throw new SQLException("Error: Binary operator without 2 operands received."); 104 | } 105 | final GraphTraversal[] graphTraversals = new GraphTraversal[2]; 106 | for (int i = 0; i < operands.size(); i++) { 107 | graphTraversals[i] = __.unfold(); 108 | if (operands.get(i) instanceof GremlinSqlIdentifier) { 109 | ((GraphTraversal) graphTraversals[i]) 110 | .values(((GremlinSqlIdentifier) operands.get(i)).getColumn()); 111 | } else if (operands.get(i) instanceof GremlinSqlBasicCall) { 112 | ((GremlinSqlBasicCall) operands.get(i)) 113 | .generateTraversal((GraphTraversal) graphTraversals[i]); 114 | } else if (operands.get(i) instanceof GremlinSqlNumericLiteral) { 115 | ((GremlinSqlNumericLiteral) operands.get(i)).appendTraversal(graphTraversals[i]); 116 | } 117 | } 118 | return graphTraversals; 119 | } 120 | 121 | public static class GremlinSqlBinaryOperatorAppenderEquals implements GremlinSqlTraversalAppender { 122 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 123 | throws SQLException { 124 | final String randomString = getRandomString(); 125 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 126 | graphTraversal.as(randomString).where(randomString, P.eq(randomString)) 127 | .by(graphTraversals[0]).by(graphTraversals[1]); 128 | } 129 | } 130 | 131 | public static class GremlinSqlBinaryOperatorAppenderNotEquals implements GremlinSqlTraversalAppender { 132 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 133 | throws SQLException { 134 | final String randomString = getRandomString(); 135 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 136 | graphTraversal.as(randomString).where(randomString, P.neq(randomString)) 137 | .by(graphTraversals[0]).by(graphTraversals[1]); 138 | } 139 | } 140 | 141 | public static class GremlinSqlBinaryOperatorAppenderGreater implements GremlinSqlTraversalAppender { 142 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 143 | throws SQLException { 144 | final String randomString = getRandomString(); 145 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 146 | graphTraversal.as(randomString).where(randomString, P.gt(randomString)) 147 | .by(graphTraversals[0]).by(graphTraversals[1]); 148 | } 149 | } 150 | 151 | public static class GremlinSqlBinaryOperatorAppenderGreaterEquals implements GremlinSqlTraversalAppender { 152 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 153 | throws SQLException { 154 | final String randomString = getRandomString(); 155 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 156 | graphTraversal.as(randomString).where(randomString, P.gte(randomString)) 157 | .by(graphTraversals[0]).by(graphTraversals[1]); 158 | } 159 | } 160 | 161 | public static class GremlinSqlBinaryOperatorAppenderLess implements GremlinSqlTraversalAppender { 162 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 163 | throws SQLException { 164 | final String randomString = getRandomString(); 165 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 166 | graphTraversal. 167 | as(randomString). 168 | where(randomString, P.lt(randomString)).by(graphTraversals[0]).by(graphTraversals[1]); 169 | } 170 | } 171 | 172 | public static class GremlinSqlBinaryOperatorAppenderLessEquals implements GremlinSqlTraversalAppender { 173 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 174 | throws SQLException { 175 | final String randomString = getRandomString(); 176 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 177 | graphTraversal.as(randomString).where(randomString, P.lte(randomString)) 178 | .by(graphTraversals[0]).by(graphTraversals[1]); 179 | } 180 | } 181 | 182 | public static class GremlinSqlBinaryOperatorAppenderAnd implements GremlinSqlTraversalAppender { 183 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 184 | throws SQLException { 185 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 186 | __.and(graphTraversals[0], graphTraversals[1]); 187 | } 188 | } 189 | 190 | public static class GremlinSqlBinaryOperatorAppenderOr implements GremlinSqlTraversalAppender { 191 | public void appendTraversal(final GraphTraversal graphTraversal, final List operands) 192 | throws SQLException { 193 | final GraphTraversal[] graphTraversals = getTraversalsFromOperands(operands); 194 | __.or(graphTraversals[0], graphTraversals[1]); 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/operator/logic/GremlinSqlNumericLiteral.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.logic; 21 | 22 | import org.apache.calcite.sql.SqlNumericLiteral; 23 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 24 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 25 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 26 | import java.sql.SQLException; 27 | 28 | /** 29 | * This module is a GremlinSql equivalent of Calcite's GremlinSqlNumericLiteral. 30 | * 31 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 32 | */ 33 | public class GremlinSqlNumericLiteral extends GremlinSqlNode { 34 | private final SqlNumericLiteral sqlNumericLiteral; 35 | 36 | public GremlinSqlNumericLiteral(final SqlNumericLiteral sqlNumericLiteral, 37 | final SqlMetadata sqlMetadata) { 38 | super(sqlNumericLiteral, sqlMetadata); 39 | this.sqlNumericLiteral = sqlNumericLiteral; 40 | } 41 | 42 | public void appendTraversal(final GraphTraversal graphTraversal) throws SQLException { 43 | graphTraversal.constant(getValue()); 44 | } 45 | 46 | public Object getValue() throws SQLException { 47 | return sqlNumericLiteral.getValue(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/select/GremlinSqlSelect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select; 21 | 22 | import org.apache.calcite.sql.SqlNumericLiteral; 23 | import org.apache.calcite.sql.SqlSelect; 24 | import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslator; 25 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 26 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 30 | import org.twilmes.sql.gremlin.adapter.converter.SqlTraversalEngine; 31 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 32 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 33 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlBasicCall; 34 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 35 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 36 | import java.sql.SQLException; 37 | import java.util.ArrayList; 38 | import java.util.List; 39 | 40 | /** 41 | * This abstract class is a GremlinSql equivalent of Calcite's SqlSelect. 42 | * 43 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 44 | */ 45 | public abstract class GremlinSqlSelect extends GremlinSqlNode { 46 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlSelect.class); 47 | private final GraphTraversalSource g; 48 | private final SqlSelect sqlSelect; 49 | private final SqlMetadata sqlMetadata; 50 | 51 | public GremlinSqlSelect(final SqlSelect sqlSelect, final SqlMetadata sqlMetadata) { 52 | super(sqlSelect, sqlMetadata); 53 | this.sqlSelect = sqlSelect; 54 | this.g = sqlMetadata.getG(); 55 | this.sqlMetadata = sqlMetadata; 56 | } 57 | 58 | public SqlGremlinQueryResult executeTraversal() throws SQLException { 59 | sqlMetadata.checkAggregate(sqlSelect.getSelectList()); 60 | final GraphTraversal graphTraversal = generateTraversal(); 61 | applyDistinct(graphTraversal); 62 | applyLimit(graphTraversal); 63 | final SqlGremlinQueryResult sqlGremlinQueryResult = generateSqlGremlinQueryResult(); 64 | runTraversalExecutor(graphTraversal, sqlGremlinQueryResult); 65 | return sqlGremlinQueryResult; 66 | } 67 | 68 | private SqlGremlinQueryResult generateSqlGremlinQueryResult() throws SQLException { 69 | final List tables = new ArrayList<>(); 70 | final List columns = new ArrayList<>(); 71 | for (final String table : sqlMetadata.getColumnOutputListMap().keySet()) { 72 | tables.add(sqlMetadata.getGremlinTable(table)); 73 | } 74 | sqlMetadata.getColumnOutputListMap().forEach((key, value) -> columns.addAll(value)); 75 | return new SqlGremlinQueryResult(columns, tables, sqlMetadata); 76 | 77 | } 78 | 79 | protected abstract void runTraversalExecutor(GraphTraversal traversal, 80 | SqlGremlinQueryResult sqlGremlinQueryResult) throws SQLException; 81 | 82 | public String getStringTraversal() throws SQLException { 83 | return GroovyTranslator.of("g").translate(generateTraversal().asAdmin().getBytecode()); 84 | } 85 | 86 | public abstract GraphTraversal generateTraversal() throws SQLException; 87 | 88 | protected void applyColumnRetrieval(final GraphTraversal graphTraversal, final String table, 89 | final List sqlNodeList, final StepDirection stepDirection) 90 | throws SQLException { 91 | // If there are no nodes, we should simply append a by and exit. 92 | if (sqlNodeList.isEmpty()) { 93 | graphTraversal.by(); 94 | return; 95 | } 96 | 97 | // Determine what the names should be for renaming. 98 | final List columnsRenamed = new ArrayList<>(); 99 | for (final GremlinSqlNode gremlinSqlNode : sqlNodeList) { 100 | if (gremlinSqlNode instanceof GremlinSqlIdentifier) { 101 | columnsRenamed.add(((GremlinSqlIdentifier) gremlinSqlNode).getName(1)); 102 | } else if (gremlinSqlNode instanceof GremlinSqlBasicCall) { 103 | columnsRenamed.add(((GremlinSqlBasicCall) gremlinSqlNode).getRename()); 104 | } else { 105 | throw new SQLException(String.format( 106 | "Error: Unknown sql node type for select list %s.", gremlinSqlNode.getClass().getName())); 107 | } 108 | } 109 | 110 | final List renamedColumnsTemp = new ArrayList<>(columnsRenamed); 111 | final GraphTraversal subGraphTraversal = SqlTraversalEngine.applyColumnRenames(renamedColumnsTemp); 112 | sqlMetadata.setColumnOutputList(table, columnsRenamed); 113 | for (final GremlinSqlNode gremlinSqlNode : sqlNodeList) { 114 | if (gremlinSqlNode instanceof GremlinSqlIdentifier) { 115 | final GraphTraversal subSubGraphTraversal = 116 | SqlTraversalEngine.getEmptyTraversal(stepDirection, sqlMetadata); 117 | SqlTraversalEngine 118 | .applySqlIdentifier((GremlinSqlIdentifier) gremlinSqlNode, sqlMetadata, subSubGraphTraversal); 119 | SqlTraversalEngine.applyTraversal(subGraphTraversal, subSubGraphTraversal); 120 | } else if (gremlinSqlNode instanceof GremlinSqlBasicCall) { 121 | final GraphTraversal subSubGraphTraversal = 122 | SqlTraversalEngine.getEmptyTraversal(stepDirection, sqlMetadata); 123 | ((GremlinSqlBasicCall) gremlinSqlNode).generateTraversal(subSubGraphTraversal); 124 | SqlTraversalEngine.applyTraversal(subGraphTraversal, subSubGraphTraversal); 125 | } else { 126 | throw new SQLException(String.format( 127 | "Error: Unknown sql node type for select list %s.", gremlinSqlNode.getClass().getName())); 128 | } 129 | } 130 | SqlTraversalEngine.applyTraversal(graphTraversal, subGraphTraversal); 131 | } 132 | 133 | protected void applyColumnRetrieval(final GraphTraversal graphTraversal, final String table, 134 | final List sqlNodeList) throws SQLException { 135 | applyColumnRetrieval(graphTraversal, table, sqlNodeList, StepDirection.None); 136 | } 137 | 138 | private void applyLimit(final GraphTraversal graphTraversal) { 139 | if (sqlSelect.getFetch() instanceof SqlNumericLiteral) { 140 | final SqlNumericLiteral limit = (SqlNumericLiteral) sqlSelect.getFetch(); 141 | final Long limitValue = limit.getValueAs(Long.class); 142 | graphTraversal.limit(limitValue); 143 | } 144 | } 145 | 146 | private void applyDistinct(final GraphTraversal graphTraversal) { 147 | if (sqlSelect.isDistinct()) { 148 | graphTraversal.dedup(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/select/GremlinSqlSelectSingle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select; 21 | 22 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 23 | import org.apache.calcite.sql.SqlBasicCall; 24 | import org.apache.calcite.sql.SqlNode; 25 | import org.apache.calcite.sql.SqlSelect; 26 | import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslator; 27 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 28 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 29 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; 30 | import org.apache.tinkerpop.gremlin.structure.Column; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 34 | import org.twilmes.sql.gremlin.adapter.converter.SqlTraversalEngine; 35 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlFactory; 36 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 37 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 38 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlAsOperator; 39 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlBasicCall; 40 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlOperator; 41 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operator.GremlinSqlPostFixOperator; 42 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 43 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 44 | import org.twilmes.sql.gremlin.adapter.results.pagination.Pagination; 45 | import org.twilmes.sql.gremlin.adapter.results.pagination.SimpleDataReader; 46 | import java.sql.SQLException; 47 | import java.util.ArrayList; 48 | import java.util.List; 49 | import java.util.concurrent.ExecutorService; 50 | import java.util.concurrent.Executors; 51 | 52 | /** 53 | * This module is a GremlinSql equivalent of Calcite's SqlSelect for a non-JOIN operation. 54 | * 55 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 56 | */ 57 | public class GremlinSqlSelectSingle extends GremlinSqlSelect { 58 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlSelectSingle.class); 59 | private final SqlSelect sqlSelect; 60 | private final SqlMetadata sqlMetadata; 61 | private final GraphTraversalSource g; 62 | private final SqlBasicCall sqlBasicCall; 63 | 64 | public GremlinSqlSelectSingle(final SqlSelect sqlSelect, 65 | final SqlBasicCall sqlBasicCall, 66 | final SqlMetadata sqlMetadata, final GraphTraversalSource g) { 67 | super(sqlSelect, sqlMetadata); 68 | this.sqlSelect = sqlSelect; 69 | this.sqlMetadata = sqlMetadata; 70 | this.g = g; 71 | this.sqlBasicCall = sqlBasicCall; 72 | } 73 | 74 | @Override 75 | protected void runTraversalExecutor(final GraphTraversal graphTraversal, 76 | final SqlGremlinQueryResult sqlGremlinQueryResult) throws SQLException { 77 | // Launch thread to continue grabbing results. 78 | final ExecutorService executor = Executors.newSingleThreadExecutor( 79 | new ThreadFactoryBuilder().setNameFormat("Data-Insert-Thread-%d").setDaemon(true).build()); 80 | final List> columns = new ArrayList<>(sqlMetadata.getColumnOutputListMap().values()); 81 | if (columns.size() != 1) { 82 | throw new SQLException("Error: Single select has multi-table return."); 83 | } 84 | executor.execute(new Pagination(new SimpleDataReader( 85 | sqlMetadata.getRenameFromActual(sqlMetadata.getTables().iterator().next().getLabel()), columns.get(0)), 86 | graphTraversal, sqlGremlinQueryResult)); 87 | executor.shutdown(); 88 | } 89 | 90 | @Override 91 | public GraphTraversal generateTraversal() throws SQLException { 92 | if (sqlSelect.getSelectList() == null) { 93 | throw new SQLException("Error: GremlinSqlSelect expects select list component."); 94 | } 95 | 96 | final GremlinSqlOperator gremlinSqlOperator = 97 | GremlinSqlFactory.createOperator(sqlBasicCall.getOperator(), sqlBasicCall.getOperandList()); 98 | if (!(gremlinSqlOperator instanceof GremlinSqlAsOperator)) { 99 | throw new SQLException("Unexpected format for FROM."); 100 | } 101 | final List gremlinSqlOperands = GremlinSqlFactory.createNodeList(sqlBasicCall.getOperandList()); 102 | final List gremlinSqlIdentifiers = new ArrayList<>(); 103 | for (final GremlinSqlNode gremlinSqlOperand : gremlinSqlOperands) { 104 | if (!(gremlinSqlOperand instanceof GremlinSqlIdentifier)) { 105 | throw new SQLException("Unexpected format for FROM."); 106 | } 107 | gremlinSqlIdentifiers.add((GremlinSqlIdentifier) gremlinSqlOperand); 108 | } 109 | 110 | final GraphTraversal graphTraversal = 111 | SqlTraversalEngine.generateInitialSql(gremlinSqlIdentifiers, sqlMetadata, g); 112 | final String label = sqlMetadata.getActualTableName(gremlinSqlIdentifiers.get(0).getName(1)); 113 | applyGroupBy(graphTraversal, label); 114 | applySelectValues(graphTraversal); 115 | applyOrderBy(graphTraversal, label); 116 | applyHaving(graphTraversal); 117 | applyWhere(graphTraversal); 118 | SqlTraversalEngine.applyAggregateFold(sqlMetadata, graphTraversal); 119 | SqlTraversalEngine.addProjection(gremlinSqlIdentifiers, sqlMetadata, graphTraversal); 120 | final String projectLabel = gremlinSqlIdentifiers.get(1).getName(0); 121 | applyColumnRetrieval(graphTraversal, projectLabel, 122 | GremlinSqlFactory.createNodeList(sqlSelect.getSelectList().getList())); 123 | 124 | if (sqlMetadata.getRenamedColumns() == null) { 125 | throw new SQLException("Error: Column rename list is empty."); 126 | } 127 | if (sqlMetadata.getTables().size() != 1) { 128 | throw new SQLException("Error: Expected one table for traversal execution."); 129 | } 130 | return graphTraversal; 131 | } 132 | 133 | public String getStringTraversal() throws SQLException { 134 | return GroovyTranslator.of("g").translate(generateTraversal().asAdmin().getBytecode()); 135 | } 136 | 137 | private void applySelectValues(final GraphTraversal graphTraversal) { 138 | graphTraversal.select(Column.values); 139 | } 140 | 141 | protected void applyGroupBy(final GraphTraversal graphTraversal, final String table) throws SQLException { 142 | if ((sqlSelect.getGroup() == null) || (sqlSelect.getGroup().getList().isEmpty())) { 143 | // If we group bys but we have aggregates, we need to shove things into groups by ourselves.- 144 | graphTraversal.group().unfold(); 145 | } else { 146 | final List gremlinSqlNodes = new ArrayList<>(); 147 | for (final SqlNode sqlNode : sqlSelect.getGroup().getList()) { 148 | gremlinSqlNodes.add(GremlinSqlFactory.createNodeCheckType(sqlNode, GremlinSqlIdentifier.class)); 149 | } 150 | graphTraversal.group(); 151 | final List byUnion = new ArrayList<>(); 152 | for (final GremlinSqlNode gremlinSqlNode : gremlinSqlNodes) { 153 | final GraphTraversal graphTraversal1 = __.__(); 154 | toAppendToByGraphTraversal(gremlinSqlNode, table, graphTraversal1); 155 | byUnion.add(graphTraversal1); 156 | } 157 | graphTraversal.by(__.union(byUnion.toArray(new GraphTraversal[0])).fold()).unfold(); 158 | } 159 | } 160 | 161 | protected void applyOrderBy(final GraphTraversal graphTraversal, final String table) throws SQLException { 162 | graphTraversal.order(); 163 | if (sqlSelect.getOrderList() == null || sqlSelect.getOrderList().getList().isEmpty()) { 164 | graphTraversal.by(__.unfold().id()); 165 | return; 166 | } 167 | final List gremlinSqlIdentifiers = new ArrayList<>(); 168 | for (final SqlNode sqlNode : sqlSelect.getOrderList().getList()) { 169 | gremlinSqlIdentifiers.add(GremlinSqlFactory.createNode(sqlNode)); 170 | } 171 | for (final GremlinSqlNode gremlinSqlNode : gremlinSqlIdentifiers) { 172 | appendByGraphTraversal(gremlinSqlNode, table, graphTraversal); 173 | } 174 | } 175 | 176 | private void toAppendToByGraphTraversal(final GremlinSqlNode gremlinSqlNode, final String table, 177 | final GraphTraversal graphTraversal) 178 | throws SQLException { 179 | if (gremlinSqlNode instanceof GremlinSqlIdentifier) { 180 | final String column = sqlMetadata 181 | .getActualColumnName(sqlMetadata.getGremlinTable(table), 182 | ((GremlinSqlIdentifier) gremlinSqlNode).getColumn()); 183 | if (column.endsWith(GremlinTableBase.IN_ID) || column.endsWith(GremlinTableBase.OUT_ID)) { 184 | // TODO: Grouping edges that are not the edge that the vertex are connected - needs to be implemented. 185 | throw new SQLException("Error, cannot group by edges."); 186 | } else { 187 | graphTraversal.values(sqlMetadata.getActualColumnName(sqlMetadata.getGremlinTable(table), column)); 188 | } 189 | } else if (gremlinSqlNode instanceof GremlinSqlBasicCall) { 190 | final GremlinSqlBasicCall gremlinSqlBasicCall = (GremlinSqlBasicCall) gremlinSqlNode; 191 | gremlinSqlBasicCall.generateTraversal(graphTraversal); 192 | } 193 | } 194 | 195 | private void appendByGraphTraversal(final GremlinSqlNode gremlinSqlNode, final String table, 196 | final GraphTraversal graphTraversal) 197 | throws SQLException { 198 | final GraphTraversal graphTraversal1 = __.unfold(); 199 | if (gremlinSqlNode instanceof GremlinSqlIdentifier) { 200 | final String column = sqlMetadata 201 | .getActualColumnName(sqlMetadata.getGremlinTable(table), 202 | ((GremlinSqlIdentifier) gremlinSqlNode).getColumn()); 203 | if (column.endsWith(GremlinTableBase.IN_ID) || column.endsWith(GremlinTableBase.OUT_ID)) { 204 | // TODO: Grouping edges that are not the edge that the vertex are connected - needs to be implemented. 205 | throw new SQLException("Error, cannot group by edges."); 206 | } else { 207 | graphTraversal1.values(sqlMetadata.getActualColumnName(sqlMetadata.getGremlinTable(table), column)); 208 | } 209 | graphTraversal.by(graphTraversal1); 210 | } else if (gremlinSqlNode instanceof GremlinSqlBasicCall) { 211 | final GremlinSqlBasicCall gremlinSqlBasicCall = (GremlinSqlBasicCall) gremlinSqlNode; 212 | gremlinSqlBasicCall.generateTraversal(graphTraversal1); 213 | if (gremlinSqlBasicCall.getGremlinSqlOperator() instanceof GremlinSqlPostFixOperator) { 214 | final GremlinSqlPostFixOperator gremlinSqlPostFixOperator = 215 | (GremlinSqlPostFixOperator) gremlinSqlBasicCall.getGremlinSqlOperator(); 216 | graphTraversal.by(graphTraversal1, gremlinSqlPostFixOperator.getOrder()); 217 | } else { 218 | graphTraversal.by(graphTraversal1); 219 | } 220 | } 221 | } 222 | 223 | protected void applyHaving(final GraphTraversal graphTraversal) throws SQLException { 224 | if (sqlSelect.getHaving() == null) { 225 | return; 226 | } 227 | final GremlinSqlBasicCall gremlinSqlBasicCall = GremlinSqlFactory.createNodeCheckType(sqlSelect.getHaving(), 228 | GremlinSqlBasicCall.class); 229 | gremlinSqlBasicCall.generateTraversal(graphTraversal); 230 | } 231 | 232 | protected void applyWhere(final GraphTraversal graphTraversal) throws SQLException { 233 | if (sqlSelect.getWhere() == null) { 234 | return; 235 | } 236 | final GremlinSqlBasicCall gremlinSqlBasicCall = GremlinSqlFactory.createNodeCheckType(sqlSelect.getWhere(), 237 | GremlinSqlBasicCall.class); 238 | gremlinSqlBasicCall.generateTraversal(graphTraversal); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/select/StepDirection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select; 21 | 22 | /** 23 | * This enum just holds the direction of a given step in Gremlin. 24 | * 25 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 26 | */ 27 | public enum StepDirection { 28 | In, 29 | Out, 30 | None 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/ast/nodes/select/join/GremlinSqlJoinComparison.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.ast.nodes.select.join; 21 | 22 | import org.apache.calcite.sql.SqlBasicCall; 23 | import org.apache.calcite.sql.SqlBinaryOperator; 24 | import org.apache.calcite.sql.SqlKind; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 28 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.GremlinSqlNode; 29 | import org.twilmes.sql.gremlin.adapter.converter.ast.nodes.operands.GremlinSqlIdentifier; 30 | import java.sql.SQLException; 31 | import java.util.List; 32 | 33 | /** 34 | * This module is a GremlinSql equivalent of Calcite's SqlBinaryOperator in the context of a comparison of a JOIN. 35 | * 36 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 37 | */ 38 | public class GremlinSqlJoinComparison { 39 | private static final Logger LOGGER = LoggerFactory.getLogger(GremlinSqlJoinComparison.class); 40 | // See SqlKind.BINARY_COMPARISON for list of aggregate functions in Calcite. 41 | 42 | private final SqlBasicCall sqlBasicCall; 43 | private final SqlBinaryOperator sqlBinaryOperator; 44 | private final SqlMetadata sqlMetadata; 45 | private final List gremlinSqlNodes; 46 | 47 | 48 | public GremlinSqlJoinComparison(final SqlBasicCall sqlBasicCall, 49 | final SqlBinaryOperator sqlBinaryOperator, 50 | final List gremlinSqlNodes, 51 | final SqlMetadata sqlMetadata) { 52 | this.sqlBasicCall = sqlBasicCall; 53 | this.sqlBinaryOperator = sqlBinaryOperator; 54 | this.sqlMetadata = sqlMetadata; 55 | this.gremlinSqlNodes = gremlinSqlNodes; 56 | } 57 | 58 | public boolean isEquals() { 59 | return sqlBinaryOperator.kind.sql.equals(SqlKind.EQUALS.sql); 60 | } 61 | 62 | public String getColumn(final String renamedTable) throws SQLException { 63 | for (final GremlinSqlNode gremlinSqlNode : gremlinSqlNodes) { 64 | if (!(gremlinSqlNode instanceof GremlinSqlIdentifier)) { 65 | throw new SQLException("Error: Expected nodes in join comparison to be GremlinSqlIdentifiers."); 66 | } 67 | final GremlinSqlIdentifier gremlinSqlIdentifier = (GremlinSqlIdentifier) gremlinSqlNode; 68 | if (gremlinSqlIdentifier.getName(0).equals(renamedTable)) { 69 | return gremlinSqlIdentifier.getName(1); 70 | } 71 | } 72 | throw new SQLException("Error: Expected to find join column for renamed table."); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/SqlSchemaGrabber.java: -------------------------------------------------------------------------------- 1 | package org.twilmes.sql.gremlin.adapter.converter.schema; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import lombok.AllArgsConstructor; 5 | import lombok.NonNull; 6 | import org.apache.calcite.util.Pair; 7 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 8 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 9 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinSchema; 13 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinEdgeTable; 14 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinProperty; 15 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinVertexTable; 16 | import java.sql.SQLException; 17 | import java.util.ArrayList; 18 | import java.util.Date; 19 | import java.util.HashMap; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Set; 24 | import java.util.concurrent.Callable; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Executors; 28 | import java.util.concurrent.Future; 29 | 30 | public final class SqlSchemaGrabber { 31 | private static final Logger LOGGER = LoggerFactory.getLogger(SqlSchemaGrabber.class); 32 | private static final Map, String> TYPE_MAP = new HashMap<>(); 33 | private static final String VERTEX_EDGES_LABEL_QUERY = "g.V().hasLabel('%s').%sE().label().dedup()"; 34 | private static final String PROPERTIES_VALUE_QUERY = "g.%s().hasLabel('%s').values('%s').%s"; 35 | private static final String PROPERTY_KEY_QUERY = "g.%s().hasLabel('%s').properties().key().dedup()"; 36 | private static final String LABELS_QUERY = "g.%s().label().dedup()"; 37 | private static final String IN_OUT_VERTEX_QUERY = 38 | "g.E().hasLabel('%s').project('in','out').by(inV().label()).by(outV().label()).dedup()"; 39 | 40 | static { 41 | TYPE_MAP.put(String.class, "String"); 42 | TYPE_MAP.put(Boolean.class, "Boolean"); 43 | TYPE_MAP.put(Byte.class, "Byte"); 44 | TYPE_MAP.put(Short.class, "Short"); 45 | TYPE_MAP.put(Integer.class, "Integer"); 46 | TYPE_MAP.put(Long.class, "Long"); 47 | TYPE_MAP.put(Float.class, "Float"); 48 | TYPE_MAP.put(Double.class, "Double"); 49 | TYPE_MAP.put(Date.class, "Date"); 50 | } 51 | 52 | private SqlSchemaGrabber() { 53 | } 54 | 55 | public static GremlinSchema getSchema(final GraphTraversalSource g, final ScanType scanType) throws SQLException { 56 | final ExecutorService executor = Executors.newFixedThreadPool(96, 57 | new ThreadFactoryBuilder().setNameFormat("RxSessionRunner-%d").setDaemon(true).build()); 58 | try { 59 | final Future> gremlinVertexTablesFuture = 60 | executor.submit(new RunGremlinQueryVertices(g, executor, scanType)); 61 | final Future> gremlinEdgeTablesFuture = 62 | executor.submit(new RunGremlinQueryEdges(g, executor, scanType)); 63 | final GremlinSchema gremlinSchema = 64 | new GremlinSchema(gremlinVertexTablesFuture.get(), gremlinEdgeTablesFuture.get()); 65 | executor.shutdown(); 66 | return gremlinSchema; 67 | } catch (final ExecutionException | InterruptedException e) { 68 | e.printStackTrace(); 69 | executor.shutdown(); 70 | throw new SQLException("Error occurred during schema collection. '" + e.getMessage() + "'."); 71 | } 72 | } 73 | 74 | private static String getType(final Set data) { 75 | final Set types = new HashSet<>(); 76 | for (final Object d : data) { 77 | types.add(TYPE_MAP.getOrDefault(d.getClass(), "String")); 78 | } 79 | if (types.size() == 1) { 80 | return types.iterator().next(); 81 | } else if (types.size() > 1) { 82 | if (types.contains("String") || types.contains("Date")) { 83 | return "String"; 84 | } else if (types.contains("Double")) { 85 | return "Double"; 86 | } else if (types.contains("Float")) { 87 | return "Float"; 88 | } else if (types.contains("Long")) { 89 | return "Long"; 90 | } else if (types.contains("Integer")) { 91 | return "Integer"; 92 | } else if (types.contains("Short")) { 93 | return "Short"; 94 | } else if (types.contains("Byte")) { 95 | return "Byte"; 96 | } 97 | } 98 | return "String"; 99 | } 100 | 101 | public enum ScanType { 102 | First("First"), 103 | All("All"); 104 | 105 | private final String stringValue; 106 | 107 | ScanType(@NonNull final String stringValue) { 108 | this.stringValue = stringValue; 109 | } 110 | 111 | /** 112 | * Converts case-insensitive string to enum value. 113 | * 114 | * @param in The case-insensitive string to be converted to enum. 115 | * @return The enum value if string is recognized as a valid value, otherwise null. 116 | */ 117 | public static ScanType fromString(@NonNull final String in) { 118 | for (final ScanType scheme : ScanType.values()) { 119 | if (scheme.stringValue.equalsIgnoreCase(in)) { 120 | return scheme; 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | @Override 127 | public java.lang.String toString() { 128 | return this.stringValue; 129 | } 130 | } 131 | 132 | @AllArgsConstructor 133 | static 134 | class RunGremlinQueryVertices implements Callable> { 135 | private final GraphTraversalSource g; 136 | private final ExecutorService service; 137 | private final ScanType scanType; 138 | 139 | @Override 140 | public List call() throws Exception { 141 | final List>> gremlinProperties = new ArrayList<>(); 142 | final List>> gremlinVertexInEdgeLabels = new ArrayList<>(); 143 | final List>> gremlinVertexOutEdgeLabels = new ArrayList<>(); 144 | final List labels = service.submit(new RunGremlinQueryLabels(true, g)).get(); 145 | 146 | for (final String label : labels) { 147 | gremlinProperties.add(service.submit( 148 | new RunGremlinQueryPropertiesList(true, label, g, scanType, service))); 149 | gremlinVertexInEdgeLabels.add(service.submit(new RunGremlinQueryVertexEdges(g, label, "in"))); 150 | gremlinVertexOutEdgeLabels.add(service.submit(new RunGremlinQueryVertexEdges(g, label, "out"))); 151 | } 152 | 153 | final List gremlinVertexTables = new ArrayList<>(); 154 | for (int i = 0; i < labels.size(); i++) { 155 | gremlinVertexTables.add(new GremlinVertexTable(labels.get(i), gremlinProperties.get(i).get(), 156 | gremlinVertexInEdgeLabels.get(i).get(), gremlinVertexOutEdgeLabels.get(i).get())); 157 | } 158 | return gremlinVertexTables; 159 | } 160 | } 161 | 162 | @AllArgsConstructor 163 | static class RunGremlinQueryEdges implements Callable> { 164 | private final GraphTraversalSource g; 165 | private final ExecutorService service; 166 | private final ScanType scanType; 167 | 168 | @Override 169 | public List call() throws Exception { 170 | final List>> futureTableColumns = new ArrayList<>(); 171 | final List>>> inOutLabels = new ArrayList<>(); 172 | final List labels = service.submit(new RunGremlinQueryLabels(false, g)).get(); 173 | 174 | for (final String label : labels) { 175 | futureTableColumns.add(service.submit( 176 | new RunGremlinQueryPropertiesList(false, label, g, scanType, service))); 177 | inOutLabels.add(service.submit(new RunGremlinQueryInOutV(g, label))); 178 | } 179 | 180 | final List gremlinEdgeTables = new ArrayList<>(); 181 | for (int i = 0; i < labels.size(); i++) { 182 | gremlinEdgeTables.add(new GremlinEdgeTable(labels.get(i), futureTableColumns.get(i).get(), 183 | inOutLabels.get(i).get())); 184 | } 185 | return gremlinEdgeTables; 186 | } 187 | } 188 | 189 | @AllArgsConstructor 190 | static class RunGremlinQueryVertexEdges implements Callable> { 191 | private final GraphTraversalSource g; 192 | private final String label; 193 | private final String direction; 194 | 195 | @Override 196 | public List call() throws Exception { 197 | final String query = String.format(VERTEX_EDGES_LABEL_QUERY, label, direction); 198 | LOGGER.debug(String.format("Start %s%n", query)); 199 | final List labels = "in".equals(direction) ? g.V().hasLabel(label).inE().label().dedup().toList() : 200 | g.V().hasLabel(label).outE().label().dedup().toList(); 201 | LOGGER.debug(String.format("End %s%n", query)); 202 | return labels; 203 | } 204 | } 205 | 206 | @AllArgsConstructor 207 | static class RunGremlinQueryPropertyType implements Callable { 208 | private final boolean isVertex; 209 | private final String label; 210 | private final String property; 211 | private final GraphTraversalSource g; 212 | private final ScanType strategy; 213 | 214 | @Override 215 | public String call() { 216 | final String query = String.format(PROPERTIES_VALUE_QUERY, isVertex ? "V" : "E", label, property, 217 | strategy.equals(ScanType.First) ? "next(1)" : "toSet()"); 218 | LOGGER.debug(String.format("Start %s%n", query)); 219 | final GraphTraversal graphTraversal = isVertex ? g.V() : g.E(); 220 | graphTraversal.hasLabel(label).values(property); 221 | final HashSet data = 222 | new HashSet<>(strategy.equals(ScanType.First) ? graphTraversal.next(1) : graphTraversal.toList()); 223 | LOGGER.debug(String.format("End %s%n", query)); 224 | return getType(data); 225 | } 226 | } 227 | 228 | @AllArgsConstructor 229 | static class RunGremlinQueryPropertiesList implements Callable> { 230 | private final boolean isVertex; 231 | private final String label; 232 | private final GraphTraversalSource g; 233 | private final ScanType scanType; 234 | private final ExecutorService service; 235 | 236 | @Override 237 | public List call() throws ExecutionException, InterruptedException { 238 | final String query = String.format(PROPERTY_KEY_QUERY, isVertex ? "V" : "E", label); 239 | LOGGER.debug(String.format("Start %s%n", query)); 240 | final List properties = isVertex ? 241 | g.V().hasLabel(label).properties().key().dedup().toList() : 242 | g.E().hasLabel(label).properties().key().dedup().toList(); 243 | 244 | final List> propertyTypes = new ArrayList<>(); 245 | for (final String property : properties) { 246 | propertyTypes.add(service 247 | .submit(new RunGremlinQueryPropertyType(isVertex, label, property, g, scanType))); 248 | } 249 | 250 | final List columns = new ArrayList<>(); 251 | for (int i = 0; i < properties.size(); i++) { 252 | columns.add(new GremlinProperty(properties.get(i), propertyTypes.get(i).get().toLowerCase())); 253 | } 254 | 255 | LOGGER.debug(String.format("End %s%n", query)); 256 | return columns; 257 | } 258 | } 259 | 260 | @AllArgsConstructor 261 | static class RunGremlinQueryLabels implements Callable> { 262 | private final boolean isVertex; 263 | private final GraphTraversalSource g; 264 | 265 | @Override 266 | public List call() { 267 | final String query = String.format(LABELS_QUERY, isVertex ? "V" : "E"); 268 | LOGGER.debug(String.format("Start %s%n", query)); 269 | final List labels = isVertex ? g.V().label().dedup().toList() : g.E().label().dedup().toList(); 270 | LOGGER.debug(String.format("End %s%n", query)); 271 | return labels; 272 | } 273 | } 274 | 275 | @AllArgsConstructor 276 | static class RunGremlinQueryInOutV implements Callable>> { 277 | private final GraphTraversalSource g; 278 | private final String label; 279 | 280 | @Override 281 | public List> call() { 282 | final String query = String.format(IN_OUT_VERTEX_QUERY, label); 283 | LOGGER.debug(String.format("Start %s%n", query)); 284 | final List> result = g.E().hasLabel(label). 285 | project("in", "out"). 286 | by(__.inV().label()). 287 | by(__.outV().label()). 288 | dedup().toList(); 289 | final List> labels = new ArrayList<>(); 290 | result.stream().iterator().forEachRemaining(map -> map.forEach((key, value) -> { 291 | labels.add(new Pair<>(map.get("in").toString(), map.get("out").toString())); 292 | })); 293 | LOGGER.debug(String.format("End %s%n", query)); 294 | return labels; 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import org.apache.calcite.plan.RelOptCluster; 23 | import org.apache.calcite.plan.RelTraitSet; 24 | import org.apache.calcite.rel.RelNode; 25 | import org.apache.calcite.rel.core.Filter; 26 | import org.apache.calcite.rex.RexNode; 27 | 28 | /** 29 | * @author Lyndon Bauto (lyndonb@bitquilltech.com) 30 | * @author Adapted from implementation by twilmes (https://github.com/twilmes/sql-gremlin) 31 | * 32 | * 33 | */ 34 | public class GremlinFilter extends Filter implements GremlinRel { 35 | public GremlinFilter( 36 | final RelOptCluster cluster, 37 | final RelTraitSet traitSet, 38 | final RelNode child, 39 | final RexNode condition) { 40 | super(cluster, traitSet, child, condition); 41 | } 42 | 43 | @Override 44 | public Filter copy(final RelTraitSet traitSet, final RelNode input, final RexNode condition) { 45 | return new GremlinFilter(getCluster(), traitSet, input, condition); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinRel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import org.apache.calcite.plan.Convention; 23 | import org.apache.calcite.rel.RelNode; 24 | 25 | /** 26 | * Created by twilmes on 9/25/15. 27 | * Modified by lyndonb-bq on 05/17/21. 28 | */ 29 | public interface GremlinRel extends RelNode { 30 | /** 31 | * Calling convention for relational operations that occur in Gremlin. 32 | */ 33 | Convention CONVENTION = new Convention.Impl("GREMLIN", GremlinRel.class); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinRules.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import org.apache.calcite.plan.Convention; 23 | import org.apache.calcite.plan.RelOptRule; 24 | import org.apache.calcite.plan.RelTrait; 25 | import org.apache.calcite.plan.RelTraitSet; 26 | import org.apache.calcite.rel.RelNode; 27 | import org.apache.calcite.rel.convert.ConverterRule; 28 | import org.apache.calcite.rel.logical.LogicalFilter; 29 | 30 | /** 31 | * List of rules that get pushed down and converted into GremlinTraversals. Right now 32 | * only filter is pushed down using rules. Joins are converted, but handled the by RelWalker 33 | * utilities. 34 | *

35 | * Created by twilmes on 11/14/15. 36 | * Modified by lyndonb-bq on 05/17/21. 37 | */ 38 | public class GremlinRules { 39 | public static final RelOptRule[] RULES = { 40 | GremlinFilterRule.INSTANCE 41 | }; 42 | 43 | abstract static class GremlinConverterRule extends ConverterRule { 44 | private final Convention out; 45 | 46 | GremlinConverterRule( 47 | final Class clazz, 48 | final RelTrait in, 49 | final Convention out, 50 | final String description) { 51 | super(clazz, in, out, description); 52 | this.out = out; 53 | } 54 | 55 | protected Convention getOut() { 56 | return out; 57 | } 58 | } 59 | 60 | private static final class GremlinFilterRule extends GremlinConverterRule { 61 | private static final GremlinFilterRule INSTANCE = new GremlinFilterRule(); 62 | 63 | private GremlinFilterRule() { 64 | super(LogicalFilter.class, Convention.NONE, GremlinRel.CONVENTION, "GremlinFilterRule"); 65 | } 66 | 67 | public RelNode convert(final RelNode rel) { 68 | final LogicalFilter filter = (LogicalFilter) rel; 69 | final RelTraitSet traitSet = filter.getTraitSet().replace(getOut()); 70 | return new GremlinFilter(rel.getCluster(), traitSet, convert(filter.getInput(), getOut()), 71 | filter.getCondition()); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import com.google.common.collect.ImmutableMap; 23 | import lombok.AllArgsConstructor; 24 | import org.apache.calcite.schema.Table; 25 | import org.apache.calcite.schema.impl.AbstractSchema; 26 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinEdgeTable; 27 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 28 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinVertexTable; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.stream.Collectors; 33 | 34 | /** 35 | * Created by twilmes on 9/22/15. 36 | * Modified by lyndonb-bq on 05/17/21. 37 | */ 38 | @AllArgsConstructor 39 | public class GremlinSchema extends AbstractSchema { 40 | private final List vertices; 41 | private final List edges; 42 | 43 | @Override 44 | protected Map getTableMap() { 45 | final ImmutableMap.Builder builder = ImmutableMap.builder(); 46 | builder.putAll(vertices.stream().collect(Collectors.toMap(GremlinTableBase::getLabel, t -> t))); 47 | builder.putAll(edges.stream().collect(Collectors.toMap(GremlinTableBase::getLabel, t -> t))); 48 | final Map tableMap = builder.build(); 49 | return tableMap; 50 | } 51 | 52 | public List getVertices() { 53 | return new ArrayList<>(vertices); 54 | } 55 | 56 | public List getEdges() { 57 | return new ArrayList<>(edges); 58 | } 59 | 60 | public List getAllTables() { 61 | final List gremlinTableBases = new ArrayList<>(); 62 | gremlinTableBases.addAll(vertices); 63 | gremlinTableBases.addAll(edges); 64 | return gremlinTableBases; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinTableScan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import org.apache.calcite.plan.RelOptCluster; 23 | import org.apache.calcite.plan.RelOptPlanner; 24 | import org.apache.calcite.plan.RelOptRule; 25 | import org.apache.calcite.plan.RelOptTable; 26 | import org.apache.calcite.plan.RelTraitSet; 27 | import org.apache.calcite.rel.RelNode; 28 | import org.apache.calcite.rel.core.TableScan; 29 | import org.apache.calcite.rel.type.RelDataType; 30 | import org.apache.calcite.rel.type.RelDataTypeFactory; 31 | import org.apache.calcite.rel.type.RelDataTypeField; 32 | import java.util.List; 33 | 34 | /** 35 | * Created by twilmes on 9/25/15. 36 | * Modified by lyndonb-bq on 05/17/21. 37 | */ 38 | public class GremlinTableScan extends TableScan implements GremlinRel { 39 | /** 40 | * Calling convention for relational operations that occur in Gremlin. 41 | */ 42 | private final int[] fields; 43 | 44 | public GremlinTableScan(final RelOptCluster cluster, final RelTraitSet traitSet, 45 | final RelOptTable table, final int[] fields) { 46 | super(cluster, traitSet, table); 47 | this.fields = fields; 48 | } 49 | 50 | @Override 51 | public RelNode copy(final RelTraitSet traitSet, final List inputs) { 52 | assert inputs.isEmpty(); 53 | return this; 54 | } 55 | 56 | @Override 57 | public RelDataType deriveRowType() { 58 | final List fieldList = table.getRowType().getFieldList(); 59 | final RelDataTypeFactory.FieldInfoBuilder builder = 60 | getCluster().getTypeFactory().builder(); 61 | for (final int field : fields) { 62 | builder.add(fieldList.get(field)); 63 | } 64 | return builder.build(); 65 | } 66 | 67 | @Override 68 | public void register(final RelOptPlanner planner) { 69 | planner.addRule(GremlinToEnumerableConverterRule.INSTANCE); 70 | for (final RelOptRule rule : GremlinRules.RULES) { 71 | planner.addRule(rule); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinToEnumerableConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | import org.apache.calcite.adapter.enumerable.EnumerableRel; 23 | import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor; 24 | import org.apache.calcite.plan.ConventionTraitDef; 25 | import org.apache.calcite.plan.RelOptCluster; 26 | import org.apache.calcite.plan.RelTraitSet; 27 | import org.apache.calcite.rel.RelNode; 28 | import org.apache.calcite.rel.convert.ConverterImpl; 29 | import java.util.List; 30 | 31 | /** 32 | * Created by twilmes on 9/25/15. 33 | * Modified by lyndonb-bq on 05/17/21. 34 | * Relational expression representing a scan of a table in a TinkerPop data source. 35 | */ 36 | public class GremlinToEnumerableConverter 37 | extends ConverterImpl 38 | implements EnumerableRel { 39 | protected GremlinToEnumerableConverter( 40 | final RelOptCluster cluster, 41 | final RelTraitSet traits, 42 | final RelNode input) { 43 | super(cluster, ConventionTraitDef.INSTANCE, traits, input); 44 | } 45 | 46 | @Override 47 | public RelNode copy(final RelTraitSet traitSet, final List inputs) { 48 | return new GremlinToEnumerableConverter( 49 | getCluster(), traitSet, sole(inputs)); 50 | } 51 | 52 | @Override 53 | public Result implement(final EnumerableRelImplementor implementor, final Prefer pref) { 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/calcite/GremlinToEnumerableConverterRule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.calcite; 21 | 22 | /** 23 | * Created by twilmes on 9/25/15. 24 | * Modified by lyndonb-bq on 05/17/21. 25 | */ 26 | 27 | import org.apache.calcite.adapter.enumerable.EnumerableConvention; 28 | import org.apache.calcite.plan.RelTraitSet; 29 | import org.apache.calcite.rel.RelNode; 30 | import org.apache.calcite.rel.convert.ConverterRule; 31 | 32 | /** 33 | * Rule to convert a relational expression from 34 | * {@link GremlinRel#CONVENTION} to {@link org.apache.calcite.adapter.enumerable.EnumerableConvention}. 35 | */ 36 | public final class GremlinToEnumerableConverterRule extends ConverterRule { 37 | public static final ConverterRule INSTANCE = 38 | new GremlinToEnumerableConverterRule(); 39 | 40 | private GremlinToEnumerableConverterRule() { 41 | super(RelNode.class, GremlinRel.CONVENTION, EnumerableConvention.INSTANCE,"GremlinToEnumerableConverterRule"); 42 | } 43 | 44 | @Override 45 | public RelNode convert(final RelNode rel) { 46 | final RelTraitSet newTraitSet = rel.getTraitSet().replace(getOutConvention()); 47 | return new GremlinToEnumerableConverter(rel.getCluster(), newTraitSet, rel); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/gremlin/GremlinEdgeTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.gremlin; 21 | 22 | import lombok.Getter; 23 | import org.apache.calcite.util.Pair; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * Created by twilmes on 10/10/15. 30 | * Modified by lyndonb-bq on 05/17/21. 31 | */ 32 | @Getter 33 | public class GremlinEdgeTable extends GremlinTableBase { 34 | private final List> inOutVertexPairs; 35 | 36 | public GremlinEdgeTable(final String label, final List columns, 37 | final List> inOutVertexPairs) { 38 | super(label, false, convert(label, columns, inOutVertexPairs)); 39 | this.inOutVertexPairs = inOutVertexPairs; 40 | } 41 | 42 | private static Map convert( 43 | final String label, final List columns, 44 | final List> inOutTablePairs) { 45 | final Map columnsWithPKFK = 46 | columns.stream().collect(Collectors.toMap(GremlinProperty::getName, t -> t)); 47 | 48 | // Uppercase edge label appended with '_ID' represents an edge, this is a string type. 49 | final GremlinProperty pk = new GremlinProperty(label + GremlinTableBase.ID, "string"); 50 | columnsWithPKFK.put(pk.getName(), pk); 51 | 52 | // Get in and out foreign keys of edge. 53 | inOutTablePairs.forEach(inOutPair -> { 54 | // Uppercase vertex label appended with 'IN_ID'/'OUT_ID' represents a connected vertex, this is a string type. 55 | final GremlinProperty inFk = new GremlinProperty(inOutPair.getKey() + GremlinTableBase.IN_ID, "string"); 56 | final GremlinProperty outFk = new GremlinProperty(inOutPair.getValue() + GremlinTableBase.OUT_ID, "string"); 57 | columnsWithPKFK.put(inFk.getName(), inFk); 58 | columnsWithPKFK.put(outFk.getName(), inFk); 59 | }); 60 | return columnsWithPKFK; 61 | } 62 | 63 | public boolean isEdgeBetween(final String in, final String out) { 64 | for (final Pair inOutPair : inOutVertexPairs) { 65 | if (inOutPair.getKey().equalsIgnoreCase(in + GremlinTableBase.IN_ID) 66 | && inOutPair.getValue().equalsIgnoreCase(out + GremlinTableBase.OUT_ID)) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | public boolean hasInVertex(final String inVertexLabel) { 74 | final String label = inVertexLabel.replace(IN_ID, ""); 75 | for (final Pair pair : inOutVertexPairs) { 76 | if (pair.getKey().equalsIgnoreCase(label)) { 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | public boolean hasOutVertex(final String outVertexLabel) { 84 | final String label = outVertexLabel.replace(IN_ID, ""); 85 | for (final Pair pair : inOutVertexPairs) { 86 | if (pair.getValue().equalsIgnoreCase(label)) { 87 | return true; 88 | } 89 | } 90 | return false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/gremlin/GremlinProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.gremlin; 21 | 22 | import lombok.AllArgsConstructor; 23 | import lombok.Getter; 24 | 25 | /** 26 | * Created by twilmes on 10/10/15. 27 | * Modified by lyndonb-bq on 05/17/21. 28 | */ 29 | @Getter 30 | @AllArgsConstructor 31 | public class GremlinProperty { 32 | private final String name; 33 | private final String type; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/gremlin/GremlinTableBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.gremlin; 21 | 22 | import lombok.Getter; 23 | import org.apache.calcite.adapter.java.AbstractQueryableTable; 24 | import org.apache.calcite.linq4j.QueryProvider; 25 | import org.apache.calcite.linq4j.Queryable; 26 | import org.apache.calcite.plan.RelOptTable; 27 | import org.apache.calcite.rel.RelNode; 28 | import org.apache.calcite.rel.type.RelDataType; 29 | import org.apache.calcite.rel.type.RelDataTypeFactory; 30 | import org.apache.calcite.schema.SchemaPlus; 31 | import org.apache.calcite.schema.TranslatableTable; 32 | import org.apache.calcite.util.Pair; 33 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinRel; 34 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinTableScan; 35 | import java.sql.SQLException; 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Map; 39 | 40 | @Getter 41 | public class GremlinTableBase extends AbstractQueryableTable implements TranslatableTable { 42 | private final String label; 43 | private final Boolean isVertex; 44 | private final Map columns; 45 | public static final String ID = "_ID"; 46 | public static final String IN_ID = "_IN" + ID; 47 | public static final String OUT_ID = "_OUT" + ID; 48 | 49 | public GremlinTableBase(final String label, final Boolean isVertex, 50 | final Map columns) { 51 | super(Object[].class); 52 | this.label = label; 53 | this.isVertex = isVertex; 54 | this.columns = columns; 55 | } 56 | 57 | public GremlinProperty getColumn(final String column) throws SQLException { 58 | for (final String key : columns.keySet()) { 59 | if (key.equalsIgnoreCase(column)) { 60 | return columns.get(key); 61 | } 62 | } 63 | throw new SQLException(String.format( 64 | "Error: Could not find column '%s' on %s with label '%s'.", column, isVertex ? "vertex" : "edge", label)); 65 | } 66 | 67 | @Override 68 | public Queryable asQueryable(final QueryProvider queryProvider, final SchemaPlus schema, final String tableName) { 69 | return null; 70 | } 71 | 72 | @Override 73 | public RelNode toRel(final RelOptTable.ToRelContext context, final RelOptTable relOptTable) { 74 | final int[] fields = new int[columns.size()]; 75 | for (int i = 0; i < fields.length; i++) { 76 | fields[i] = i; 77 | } 78 | return new GremlinTableScan(context.getCluster(), context.getCluster().traitSetOf(GremlinRel.CONVENTION), relOptTable, fields); 79 | } 80 | 81 | @Override 82 | public RelDataType getRowType(final RelDataTypeFactory relDataTypeFactory) { 83 | final List names = new ArrayList<>(); 84 | final List types = new ArrayList<>(); 85 | for (final Map.Entry entry : columns.entrySet()) { 86 | names.add(entry.getKey()); 87 | types.add(relDataTypeFactory.createJavaType(getType(entry.getValue().getType()))); 88 | } 89 | return relDataTypeFactory.createStructType(Pair.zip(names, types)); 90 | } 91 | 92 | private Class getType(final String className) { 93 | if ("string".equalsIgnoreCase(className)) { 94 | return String.class; 95 | } else if ("integer".equalsIgnoreCase(className)) { 96 | return Integer.class; 97 | } else if ("float".equalsIgnoreCase(className)) { 98 | return Float.class; 99 | } else if ("byte".equalsIgnoreCase(className)) { 100 | return Byte.class; 101 | } else if ("short".equalsIgnoreCase(className)) { 102 | return Short.class; 103 | } else if ("double".equalsIgnoreCase(className)) { 104 | return Double.class; 105 | } else if ("long".equalsIgnoreCase(className)) { 106 | return Long.class; 107 | } else if ("boolean".equalsIgnoreCase(className)) { 108 | return Boolean.class; 109 | } else if ("date".equalsIgnoreCase(className) || "long_date".equalsIgnoreCase(className)) { 110 | return java.sql.Date.class; 111 | } else if ("timestamp".equalsIgnoreCase(className) || "long_timestamp".equalsIgnoreCase(className)) { 112 | return java.sql.Timestamp.class; 113 | } else { 114 | return null; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/converter/schema/gremlin/GremlinVertexTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.converter.schema.gremlin; 21 | 22 | import lombok.Getter; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.stream.Collectors; 26 | 27 | @Getter 28 | public class GremlinVertexTable extends GremlinTableBase { 29 | private final List inEdges; 30 | private final List outEdges; 31 | 32 | public GremlinVertexTable(final String label, final List columns, final List inEdges, final List outEdges) { 33 | super(label, true, convert(label, columns, inEdges, outEdges)); 34 | this.inEdges = inEdges; 35 | this.outEdges = outEdges; 36 | } 37 | 38 | // String for edges because 1 vertex can be connected to many edges (may required representation like '[1, 2, 3]" 39 | // Long type for vertices because an edge can only be connected to one vertex (on each side). 40 | private static Map convert( 41 | final String label, final List columns, 42 | final List inEdges, final List outEdges) { 43 | final Map columnsWithPKFK = 44 | columns.stream().collect(Collectors.toMap(GremlinProperty::getName, t -> t)); 45 | 46 | // Uppercase vertex label appended with '_ID' represents an vertex, this is a string type. 47 | final GremlinProperty pk = new GremlinProperty(label + ID, "string"); 48 | columnsWithPKFK.put(pk.getName(), pk); 49 | 50 | // Get in and out foreign keys of edge. 51 | inEdges.forEach(inEdgeLabel -> { 52 | // Uppercase edge label appended with 'IN_ID'/'OUT_ID' represents a connected edge, this is a string type. 53 | final GremlinProperty inFk = new GremlinProperty(inEdgeLabel + IN_ID, "string"); 54 | columnsWithPKFK.put(inFk.getName(), inFk); 55 | }); 56 | outEdges.forEach(outEdgeLabel -> { 57 | // Uppercase edge label appended with 'IN_ID'/'OUT_ID' represents a connected edge, this is a string type. 58 | final GremlinProperty inFk = new GremlinProperty(outEdgeLabel + OUT_ID, "string"); 59 | columnsWithPKFK.put(inFk.getName(), inFk); 60 | }); 61 | return columnsWithPKFK; 62 | } 63 | 64 | public boolean hasInEdge(final String label) { 65 | return inEdges.stream().anyMatch(e -> e.equalsIgnoreCase(label.replace(IN_ID, ""))); 66 | } 67 | 68 | public boolean hasOutEdge(final String label) { 69 | return outEdges.stream().anyMatch(e -> e.equalsIgnoreCase(label.replace(OUT_ID, ""))); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/results/SqlGremlinQueryResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.results; 21 | 22 | import lombok.Getter; 23 | import org.twilmes.sql.gremlin.adapter.converter.SqlMetadata; 24 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinProperty; 25 | import org.twilmes.sql.gremlin.adapter.converter.schema.gremlin.GremlinTableBase; 26 | import java.sql.SQLException; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.concurrent.BlockingQueue; 30 | import java.util.concurrent.LinkedBlockingQueue; 31 | 32 | @Getter 33 | public class SqlGremlinQueryResult { 34 | public static final String EMPTY_MESSAGE = "No more results."; 35 | public static final String NULL_VALUE = "$%#NULL#%$"; 36 | private final List columns; 37 | private final List columnTypes = new ArrayList<>(); 38 | private final Object assertEmptyLock = new Object(); 39 | private final BlockingQueue> blockingQueueRows = new LinkedBlockingQueue<>(); 40 | private boolean isEmpty = false; 41 | private SQLException paginationException = null; 42 | private Thread currThread = null; 43 | 44 | public SqlGremlinQueryResult(final List columns, final List gremlinTableBases, 45 | final SqlMetadata sqlMetadata) throws SQLException { 46 | this.columns = columns; 47 | for (final String column : columns) { 48 | GremlinProperty col = null; 49 | for (final GremlinTableBase gremlinTableBase : gremlinTableBases) { 50 | if (sqlMetadata.getTableHasColumn(gremlinTableBase, column)) { 51 | col = sqlMetadata.getGremlinProperty(gremlinTableBase.getLabel(), column); 52 | break; 53 | } 54 | } 55 | columnTypes.add((col == null || col.getType() == null) ? "string" : col.getType()); 56 | } 57 | } 58 | 59 | public void setPaginationException(final SQLException e) { 60 | synchronized (assertEmptyLock) { 61 | paginationException = e; 62 | if (currThread != null && blockingQueueRows.size() == 0) { 63 | currThread.interrupt(); 64 | } 65 | } 66 | } 67 | 68 | public boolean getIsEmpty() throws SQLException { 69 | if (paginationException == null) { 70 | return isEmpty; 71 | } 72 | throw paginationException; 73 | } 74 | 75 | public void assertIsEmpty() { 76 | synchronized (assertEmptyLock) { 77 | if (currThread != null && blockingQueueRows.size() == 0) { 78 | currThread.interrupt(); 79 | } 80 | isEmpty = true; 81 | } 82 | } 83 | 84 | public void addResults(final List> rows) { 85 | for (final List row : rows) { 86 | for (int i = 0; i < row.size(); i++) { 87 | if (row.get(i) instanceof String && row.get(i).toString().equals(NULL_VALUE)) { 88 | row.set(i, null); 89 | } 90 | } 91 | } 92 | blockingQueueRows.addAll(rows); 93 | } 94 | 95 | private boolean getShouldExit() throws SQLException { 96 | synchronized (assertEmptyLock) { 97 | return (getIsEmpty() && blockingQueueRows.size() == 0); 98 | } 99 | } 100 | 101 | public Object getResult() throws SQLException { 102 | synchronized (assertEmptyLock) { 103 | this.currThread = Thread.currentThread(); 104 | } 105 | while (!getShouldExit()) { 106 | try { 107 | return this.blockingQueueRows.take(); 108 | } catch (final InterruptedException ignored) { 109 | if (paginationException != null) { 110 | throw paginationException; 111 | } 112 | } 113 | } 114 | throw new SQLException(EMPTY_MESSAGE); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/results/pagination/GetRowFromMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.results.pagination; 21 | 22 | import java.util.Map; 23 | 24 | interface GetRowFromMap { 25 | Object[] execute(Map input); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/results/pagination/JoinDataReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.results.pagination; 21 | 22 | import org.apache.calcite.util.Pair; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | 28 | public class JoinDataReader implements GetRowFromMap { 29 | private final List> tableColumnList = new ArrayList<>(); 30 | 31 | public JoinDataReader(final Map> tablesColumns) { 32 | tablesColumns.forEach((key, value) -> value.forEach(column -> tableColumnList.add(new Pair<>(key, column)))); 33 | } 34 | 35 | @Override 36 | public Object[] execute(final Map map) { 37 | final Object[] row = new Object[tableColumnList.size()]; 38 | int i = 0; 39 | for (final Pair tableColumn : tableColumnList) { 40 | final Optional tableKey = 41 | map.keySet().stream().filter(key -> key.equalsIgnoreCase(tableColumn.left)).findFirst(); 42 | if (!tableKey.isPresent()) { 43 | row[i++] = null; 44 | continue; 45 | } 46 | 47 | final Optional columnKey = ((Map) map.get(tableKey.get())).keySet().stream() 48 | .filter(key -> key.equalsIgnoreCase(tableColumn.right)).findFirst(); 49 | if (!columnKey.isPresent()) { 50 | row[i++] = null; 51 | continue; 52 | } 53 | row[i++] = ((Map) map.get(tableKey.get())).getOrDefault(columnKey.get(), null); 54 | } 55 | return row; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/results/pagination/Pagination.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.results.pagination; 21 | 22 | import lombok.AllArgsConstructor; 23 | import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyTranslator; 24 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 28 | import java.io.PrintWriter; 29 | import java.io.StringWriter; 30 | import java.sql.SQLException; 31 | import java.util.ArrayList; 32 | import java.util.Arrays; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | @AllArgsConstructor 37 | public class Pagination implements Runnable { 38 | private static final Logger LOGGER = LoggerFactory.getLogger(Pagination.class); 39 | private static final int DEFAULT_PAGE_SIZE = 1000; 40 | private final int pageSize = DEFAULT_PAGE_SIZE; 41 | private final GetRowFromMap getRowFromMap; 42 | private final GraphTraversal traversal; 43 | private final SqlGremlinQueryResult sqlGremlinQueryResult; 44 | 45 | @Override 46 | public void run() { 47 | try { 48 | LOGGER.info("Graph traversal: " + 49 | GroovyTranslator.of("g").translate(traversal.asAdmin().getBytecode())); 50 | while (traversal.hasNext()) { 51 | final List rows = new ArrayList<>(); 52 | traversal.next(pageSize).forEach(map -> rows.add(getRowFromMap.execute((Map) map))); 53 | convertAndInsertResult(sqlGremlinQueryResult, rows); 54 | } 55 | // If we run out of traversal data (or hit our limit), stop and signal to the result that it is done. 56 | sqlGremlinQueryResult.assertIsEmpty(); 57 | } catch (final Exception e) { 58 | final StringWriter sw = new StringWriter(); 59 | final PrintWriter pw = new PrintWriter(sw); 60 | e.printStackTrace(pw); 61 | LOGGER.error("Encountered exception", e); 62 | sqlGremlinQueryResult.setPaginationException(new SQLException(e + pw.toString())); 63 | } 64 | } 65 | 66 | /** 67 | * converts input row results and insert them into sqlGremlinQueryResult 68 | */ 69 | void convertAndInsertResult(final SqlGremlinQueryResult sqlGremlinQueryResult, final List rows) { 70 | final List> finalRowResult = new ArrayList<>(); 71 | for (final Object row : rows) { 72 | final List convertedRow = new ArrayList<>(); 73 | if (row instanceof Object[]) { 74 | convertedRow.addAll(Arrays.asList((Object[]) row)); 75 | } else { 76 | convertedRow.add(row); 77 | } 78 | finalRowResult.add(convertedRow); 79 | } 80 | sqlGremlinQueryResult.addResults(finalRowResult); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/twilmes/sql/gremlin/adapter/results/pagination/SimpleDataReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.results.pagination; 21 | 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | public class SimpleDataReader implements GetRowFromMap { 27 | private final String label; 28 | private final List columnNames; 29 | 30 | public SimpleDataReader(final String label, final List columnNames) { 31 | this.label = label; 32 | this.columnNames = columnNames; 33 | } 34 | 35 | @Override 36 | public Object[] execute(final Map map) { 37 | final Object[] row = new Object[columnNames.size()]; 38 | int i = 0; 39 | for (final String column : columnNames) { 40 | final Optional tableKey = 41 | map.keySet().stream().filter(key -> key.equalsIgnoreCase(label)).findFirst(); 42 | if (!tableKey.isPresent()) { 43 | row[i++] = null; 44 | continue; 45 | } 46 | 47 | final Optional columnKey = ((Map) map.get(tableKey.get())).keySet().stream() 48 | .filter(key -> key.equalsIgnoreCase(column)).findFirst(); 49 | if (!columnKey.isPresent()) { 50 | row[i++] = null; 51 | continue; 52 | } 53 | row[i++] = ((Map) map.get(tableKey.get())).getOrDefault(columnKey.get(), null); 54 | } 55 | return row; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.apache.tinkerpop.gremlin.groovy.plugin.GremlinPlugin: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | 20 | org.twilmes.sql.gremlin.plugin.SqlGremlinPlugin -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # Set root logger level to INFO, and add two appenders: consoleAppender and rollingFile. 19 | log4j.rootLogger=INFO, consoleAppender, rollingFile 20 | 21 | # First appender is set to be a ConsoleAppender. 22 | log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender 23 | log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout 24 | log4j.appender.consoleAppender.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} [%t] %p %c{2}: %m%n 25 | 26 | # Second appender is set to be a RollingFileAppender. 27 | log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender 28 | log4j.appender.rollingFile.File=./logs/neptune-jdbc.log 29 | log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout 30 | log4j.appender.rollingFile.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} [%t] %p %c{2}: %m%n 31 | log4j.appender.rollingFile.MaxFileSize=10MB 32 | log4j.appender.rollingFile.MaxBackupIndex=5 33 | log4j.appender.rollingFile.append=true 34 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/GremlinSqlAdvancedSelectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import java.sql.SQLException; 24 | 25 | /** 26 | * Created by twilmes on 12/7/15. 27 | */ 28 | public class GremlinSqlAdvancedSelectTest extends GremlinSqlBaseTest { 29 | 30 | GremlinSqlAdvancedSelectTest() throws SQLException { 31 | } 32 | 33 | @Override 34 | protected DataSet getDataSet() { 35 | return DataSet.SPACE; 36 | } 37 | 38 | @Test 39 | public void testProject() throws SQLException { 40 | runQueryTestResults("select name from person", columns("name"), 41 | rows(r("Tom"), r("Patty"), r("Phil"), r("Susan"), r("Juanita"), r("Pavel"))); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/GremlinSqlAggregateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import java.sql.SQLException; 24 | 25 | public class GremlinSqlAggregateTest extends GremlinSqlBaseTest { 26 | 27 | GremlinSqlAggregateTest() throws SQLException { 28 | } 29 | 30 | @Override 31 | protected DataSet getDataSet() { 32 | return DataSet.SPACE; 33 | } 34 | 35 | @Test 36 | public void testAggregateFunctions() throws SQLException { 37 | runQueryTestResults("select count(age), min(age), max(age), avg(age) from person", 38 | columns("COUNT(age)", "MIN(age)", "MAX(age)", "AVG(age)"), 39 | rows(r(6L, 29, 50, 36.5))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/GremlinSqlBaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter; 21 | 22 | import lombok.Getter; 23 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; 24 | import org.apache.tinkerpop.gremlin.structure.Graph; 25 | import org.junit.jupiter.api.Assertions; 26 | import org.twilmes.sql.gremlin.adapter.converter.SqlConverter; 27 | import org.twilmes.sql.gremlin.adapter.converter.schema.SqlSchemaGrabber; 28 | import org.twilmes.sql.gremlin.adapter.converter.schema.calcite.GremlinSchema; 29 | import org.twilmes.sql.gremlin.adapter.graphs.TestGraphFactory; 30 | import org.twilmes.sql.gremlin.adapter.results.SqlGremlinQueryResult; 31 | import java.sql.SQLException; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.List; 35 | 36 | /** 37 | * Created by twilmes on 12/4/15. 38 | */ 39 | public abstract class GremlinSqlBaseTest { 40 | private final Graph graph; 41 | private final GraphTraversalSource g; 42 | private final SqlConverter converter; 43 | 44 | GremlinSqlBaseTest() throws SQLException { 45 | graph = TestGraphFactory.createGraph(getDataSet()); 46 | g = graph.traversal(); 47 | final GremlinSchema gremlinSchema = SqlSchemaGrabber.getSchema(g, SqlSchemaGrabber.ScanType.All); 48 | converter = new SqlConverter(gremlinSchema, g); 49 | } 50 | 51 | protected abstract DataSet getDataSet(); 52 | 53 | protected void runQueryTestResults(final String query, final List columnNames, 54 | final List> rows) 55 | throws SQLException { 56 | final SqlGremlinTestResult result = new SqlGremlinTestResult(converter.executeQuery(query)); 57 | assertRows(result.getRows(), rows); 58 | assertColumns(result.getColumns(), columnNames); 59 | } 60 | 61 | public List> rows(final List... rows) { 62 | return new ArrayList<>(Arrays.asList(rows)); 63 | } 64 | 65 | public List columns(final String... columns) { 66 | return new ArrayList<>(Arrays.asList(columns)); 67 | } 68 | 69 | public List r(final Object... row) { 70 | return new ArrayList<>(Arrays.asList(row)); 71 | } 72 | 73 | public void assertRows(final List> actual, final List> expected) { 74 | Assertions.assertEquals(expected.size(), actual.size()); 75 | for (int i = 0; i < actual.size(); i++) { 76 | Assertions.assertEquals(expected.get(i).size(), actual.get(i).size()); 77 | for (int j = 0; j < actual.get(i).size(); j++) { 78 | Assertions.assertEquals(expected.get(i).get(j), actual.get(i).get(j)); 79 | } 80 | } 81 | } 82 | 83 | public void assertColumns(final List actual, final List expected) { 84 | Assertions.assertEquals(expected.size(), actual.size()); 85 | for (int i = 0; i < actual.size(); i++) { 86 | Assertions.assertEquals(expected.get(i), actual.get(i)); 87 | } 88 | } 89 | 90 | public enum DataSet { 91 | SPACE, 92 | DATA_TYPES 93 | } 94 | 95 | @Getter 96 | static class SqlGremlinTestResult { 97 | private final List> rows = new ArrayList<>(); 98 | private final List columns; 99 | 100 | SqlGremlinTestResult(final SqlGremlinQueryResult sqlGremlinQueryResult) throws SQLException { 101 | columns = sqlGremlinQueryResult.getColumns(); 102 | Object res; 103 | do { 104 | try { 105 | res = sqlGremlinQueryResult.getResult(); 106 | } catch (final SQLException e) { 107 | if (e.getMessage().equals(SqlGremlinQueryResult.EMPTY_MESSAGE)) { 108 | break; 109 | } else { 110 | throw e; 111 | } 112 | } 113 | this.rows.add((List) res); 114 | } while (true); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/GremlinSqlBasicSelectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import org.junit.jupiter.api.Test; 24 | import org.twilmes.sql.gremlin.adapter.graphs.GraphConstants; 25 | import java.sql.SQLException; 26 | import java.util.List; 27 | 28 | public class GremlinSqlBasicSelectTest extends GremlinSqlBaseTest { 29 | 30 | GremlinSqlBasicSelectTest() throws SQLException { 31 | } 32 | 33 | @Override 34 | protected DataSet getDataSet() { 35 | return DataSet.DATA_TYPES; 36 | } 37 | 38 | @Test 39 | void testStringQuery() throws SQLException { 40 | runQueryTestResults("SELECT \"key\" FROM stringtype", columns("key"), rows(r(GraphConstants.STRING_VALUE))); 41 | } 42 | 43 | @Test 44 | void testByteQuery() throws SQLException { 45 | runQueryTestResults("SELECT \"key\" FROM bytetype", columns("key"), rows(r(GraphConstants.BYTE_VALUE))); 46 | } 47 | 48 | @Test 49 | void testShortQuery() throws SQLException { 50 | runQueryTestResults("SELECT \"key\" FROM shorttype", columns("key"), rows(r(GraphConstants.SHORT_VALUE))); 51 | } 52 | 53 | @Test 54 | void testIntegerQuery() throws SQLException { 55 | runQueryTestResults("SELECT \"key\" FROM inttype", columns("key"), rows(r(GraphConstants.INTEGER_VALUE))); 56 | } 57 | 58 | @Test 59 | void testLongQuery() throws SQLException { 60 | runQueryTestResults("SELECT \"key\" FROM longtype", columns("key"), rows(r(GraphConstants.LONG_VALUE))); 61 | } 62 | 63 | @Test 64 | void testFloatQuery() throws SQLException { 65 | runQueryTestResults("SELECT \"key\" FROM floattype", columns("key"), rows(r(GraphConstants.FLOAT_VALUE))); 66 | } 67 | 68 | @Test 69 | void testDoubleQuery() throws SQLException { 70 | runQueryTestResults("SELECT \"key\" FROM doubletype", columns("key"), rows(r(GraphConstants.DOUBLE_VALUE))); 71 | } 72 | 73 | @Test 74 | void testDateQuery() throws SQLException { 75 | runQueryTestResults("SELECT \"key\" FROM datetype", columns("key"), rows(r(GraphConstants.DATE_VALUE))); 76 | } 77 | 78 | String getAsOperatorQuery(final String column, final String asColumn, final String table) { 79 | return String.format("SELECT %s AS %s FROM %s", column, asColumn, table); 80 | } 81 | 82 | String getAsOperatorQuery(final String column, final String asColumn, final String table, final String asTable) { 83 | return String.format("SELECT %s.%s AS %s FROM %s %s", asTable, column, asColumn, table, asTable); 84 | } 85 | 86 | @Test 87 | void testAsOperator() throws SQLException { 88 | final List columns = ImmutableList.of("key", "\"key\""); 89 | final List asColumns = ImmutableList.of("key", "\"key\"", "k", "\"k\""); 90 | final List tables = ImmutableList.of("stringtype", "\"stringtype\""); 91 | final List asTables = ImmutableList.of("st", "\"st\""); 92 | for (final String column : columns) { 93 | for (final String asColumn : asColumns) { 94 | for (final String table : tables) { 95 | runQueryTestResults(getAsOperatorQuery(column, asColumn, table), 96 | columns(asColumn.replace("\"", "")), rows(r(GraphConstants.STRING_VALUE))); 97 | for (final String asTable : asTables) { 98 | runQueryTestResults(getAsOperatorQuery(column, asColumn, table, asTable), 99 | columns(asColumn.replace("\"", "")), rows(r(GraphConstants.STRING_VALUE))); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/graphs/DataTypeGraph.java: -------------------------------------------------------------------------------- 1 | package org.twilmes.sql.gremlin.adapter.graphs; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Graph; 4 | import org.apache.tinkerpop.gremlin.structure.Vertex; 5 | 6 | import static org.apache.tinkerpop.gremlin.structure.T.label; 7 | 8 | public class DataTypeGraph implements TestGraph { 9 | @Override 10 | public void populate(final Graph graph) { 11 | // Create vertices for each data type. 12 | final Vertex stringtype = graph.addVertex(label, "stringtype", "key", GraphConstants.STRING_VALUE); 13 | final Vertex bytetype = graph.addVertex(label, "bytetype", "key", GraphConstants.BYTE_VALUE); 14 | final Vertex shorttype = graph.addVertex(label, "shorttype", "key", GraphConstants.SHORT_VALUE); 15 | final Vertex inttype = graph.addVertex(label, "inttype", "key", GraphConstants.INTEGER_VALUE); 16 | final Vertex longtype = graph.addVertex(label, "longtype", "key", GraphConstants.LONG_VALUE); 17 | final Vertex floattype = graph.addVertex(label, "floattype", "key", GraphConstants.FLOAT_VALUE); 18 | final Vertex doubletype = graph.addVertex(label, "doubletype", "key", GraphConstants.DOUBLE_VALUE); 19 | final Vertex datetype = graph.addVertex(label, "datetype", "key", GraphConstants.DATE_VALUE); 20 | 21 | // Create edge from vertices to themselves with data types so they can be tested. 22 | stringtype.addEdge("stringtypeedge", stringtype, "key", GraphConstants.STRING_VALUE); 23 | bytetype.addEdge("bytetypeedge", bytetype, "key", GraphConstants.BYTE_VALUE); 24 | shorttype.addEdge("shorttypeedge", shorttype, "key", GraphConstants.SHORT_VALUE); 25 | inttype.addEdge("inttypeedge", inttype, "key", GraphConstants.INTEGER_VALUE); 26 | longtype.addEdge("longtypeedge", longtype, "key", GraphConstants.LONG_VALUE); 27 | floattype.addEdge("floattypeedge", floattype, "key", GraphConstants.FLOAT_VALUE); 28 | doubletype.addEdge("doubletypeedge", doubletype, "key", GraphConstants.DOUBLE_VALUE); 29 | datetype.addEdge("datetypeedge", datetype, "key", GraphConstants.DATE_VALUE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/graphs/GraphConstants.java: -------------------------------------------------------------------------------- 1 | package org.twilmes.sql.gremlin.adapter.graphs; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class GraphConstants { 8 | public static final String STRING_VALUE = "value"; 9 | public static final Long LONG_VALUE = 100L; 10 | public static final Integer INTEGER_VALUE = LONG_VALUE.intValue(); 11 | public static final Short SHORT_VALUE = LONG_VALUE.shortValue(); 12 | public static final Byte BYTE_VALUE = LONG_VALUE.byteValue(); 13 | public static final Double DOUBLE_VALUE = LONG_VALUE.doubleValue(); 14 | public static final Float FLOAT_VALUE = LONG_VALUE.floatValue(); 15 | public static final Date DATE_VALUE; 16 | private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-mm-dd"); 17 | 18 | static { 19 | Date date = null; 20 | try { 21 | date = DATE_FORMATTER.parse("1993-03-30"); 22 | } catch (final ParseException e) { 23 | e.printStackTrace(); 24 | } 25 | DATE_VALUE = date; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/graphs/SpaceTestGraph.java: -------------------------------------------------------------------------------- 1 | package org.twilmes.sql.gremlin.adapter.graphs; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Graph; 4 | import org.apache.tinkerpop.gremlin.structure.Vertex; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import static org.apache.tinkerpop.gremlin.structure.T.label; 8 | 9 | public class SpaceTestGraph implements TestGraph { 10 | @Override 11 | public void populate(final Graph graph) { 12 | // companies 13 | final Vertex acmeSpaceCo = graph.addVertex(label, "company", "name", "Acme Space"); 14 | final Vertex newFrontiers = graph.addVertex(label, "company", "name", "New Frontiers"); 15 | final Vertex tomorrowUnlimited = graph.addVertex(label, "company", "name", "Tomorrow Unlimited"); 16 | final Vertex spaceTruckers = graph.addVertex(label, "company", "name", "Space Truckers"); 17 | final Vertex bankruptCo = graph.addVertex(label, "company", "name", "Bankrupt Co."); 18 | 19 | 20 | // planets 21 | final Vertex earth = graph.addVertex(label, "planet", "name", "earth"); 22 | final Vertex mars = graph.addVertex(label, "planet", "name", "mars"); 23 | final Vertex saturn = graph.addVertex(label, "planet", "name", "saturn"); 24 | final Vertex jupiter = graph.addVertex(label, "planet", "name", "jupiter"); 25 | 26 | // astronauts 27 | final Vertex tom = graph.addVertex(label, "person", "name", "Tom", "age", 35); 28 | final Vertex patty = graph.addVertex(label, "person", "name", "Patty", "age", 29); 29 | final Vertex phil = graph.addVertex(label, "person", "name", "Phil", "age", 30); 30 | final Vertex susan = graph.addVertex(label, "person", "name", "Susan", "age", 45); 31 | final Vertex juanita = graph.addVertex(label, "person", "name", "Juanita", "age", 50); 32 | final Vertex pavel = graph.addVertex(label, "person", "name", "Pavel", "age", 30); 33 | 34 | // spaceships 35 | final Vertex spaceship1 = graph.addVertex(label, "spaceship", "name", "Ship 1", "model", "delta 1"); 36 | final Vertex spaceship2 = graph.addVertex(label, "spaceship", "name", "Ship 2", "model", "delta 1"); 37 | final Vertex spaceship3 = graph.addVertex(label, "spaceship", "name", "Ship 3", "model", "delta 2"); 38 | final Vertex spaceship4 = graph.addVertex(label, "spaceship", "name", "Ship 4", "model", "delta 3"); 39 | 40 | // satellite 41 | final Vertex satellite1 = graph.addVertex(label, "satellite", "name", "sat1"); 42 | final Vertex satellite2 = graph.addVertex(label, "satellite", "name", "sat2"); 43 | final Vertex satellite3 = graph.addVertex(label, "satellite", "name", "sat3"); 44 | 45 | // rocket fuel 46 | final Vertex s1Fuel = graph.addVertex(label, "sensor", "type", "rocket fuel"); 47 | 48 | // astronaut company relationships 49 | tom.addEdge("worksFor", acmeSpaceCo, "yearsWorked", 5); 50 | patty.addEdge("worksFor", acmeSpaceCo, "yearsWorked", 1); 51 | phil.addEdge("worksFor", newFrontiers, "yearsWorked", 9); 52 | susan.addEdge("worksFor", tomorrowUnlimited, "yearsWorked", 4); 53 | juanita.addEdge("worksFor", spaceTruckers, "yearsWorked", 4); 54 | pavel.addEdge("worksFor", spaceTruckers, "yearsWorked", 10); 55 | 56 | // astronaut spaceship 57 | tom.addEdge("pilots", spaceship1); 58 | patty.addEdge("pilots", spaceship1); 59 | phil.addEdge("pilots", spaceship2); 60 | susan.addEdge("pilots", spaceship3); 61 | juanita.addEdge("pilots", spaceship4); 62 | pavel.addEdge("pilots", spaceship4); 63 | 64 | // astronauts to planets 65 | tom.addEdge("fliesTo", earth).property("trips", 10); 66 | tom.addEdge("fliesTo", mars).property("trips", 3); 67 | patty.addEdge("fliesTo", mars).property("trips", 1); 68 | phil.addEdge("fliesTo", saturn).property("trips", 9); 69 | phil.addEdge("fliesTo", earth).property("trips", 4); 70 | susan.addEdge("fliesTo", jupiter).property("trips", 20); 71 | juanita.addEdge("fliesTo", earth).property("trips", 4); 72 | juanita.addEdge("fliesTo", saturn).property("trips", 7); 73 | juanita.addEdge("fliesTo", jupiter).property("trips", 9); 74 | pavel.addEdge("fliesTo", mars).property("trips", 0); 75 | 76 | // astronaut friends 77 | tom.addEdge("friendsWith", patty); 78 | patty.addEdge("friendsWith", juanita); 79 | phil.addEdge("friendsWith", susan); 80 | susan.addEdge("friendsWith", pavel); 81 | 82 | // satellites to planets 83 | satellite1.addEdge("orbits", earth).property("launched", 1995); 84 | satellite2.addEdge("orbits", mars).property("launched", 2020); 85 | satellite3.addEdge("orbits", jupiter).property("launched", 2005); 86 | 87 | // fuel sensor readings 88 | long timestamp = 1765258774000L; 89 | for (int i = 0; i < 10; i++) { 90 | final Vertex s1Reading = 91 | graph.addVertex(label, "sensorReading", "timestamp", timestamp, "date", timestamp, "value", 10.0); 92 | s1Fuel.addEdge("hasReading", s1Reading); 93 | timestamp += TimeUnit.MINUTES.toMillis(5); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/graphs/TestGraph.java: -------------------------------------------------------------------------------- 1 | package org.twilmes.sql.gremlin.adapter.graphs; 2 | 3 | import org.apache.tinkerpop.gremlin.structure.Graph; 4 | 5 | public interface TestGraph { 6 | void populate(Graph graph); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/org/twilmes/sql/gremlin/adapter/graphs/TestGraphFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | package org.twilmes.sql.gremlin.adapter.graphs; 21 | 22 | import org.apache.tinkerpop.gremlin.structure.Graph; 23 | import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; 24 | import org.twilmes.sql.gremlin.adapter.GremlinSqlBaseTest; 25 | 26 | public class TestGraphFactory { 27 | private static final TestGraph SPACE_GRAPH = new SpaceTestGraph(); 28 | private static final TestGraph DATA_TYPE_GRAPH = new DataTypeGraph(); 29 | 30 | public static Graph createGraph(final GremlinSqlBaseTest.DataSet dataSet) { 31 | final Graph graph = TinkerGraph.open(); 32 | switch (dataSet) { 33 | case SPACE: 34 | SPACE_GRAPH.populate(graph); 35 | break; 36 | case DATA_TYPES: 37 | DATA_TYPE_GRAPH.populate(graph); 38 | break; 39 | default: 40 | throw new UnsupportedOperationException("Unsupported graph " + dataSet.name()); 41 | } 42 | return graph; 43 | } 44 | } 45 | --------------------------------------------------------------------------------