├── age ├── gen │ ├── __init__.py │ ├── AgtypeVisitor.py │ ├── AgtypeListener.py │ ├── AgtypeLexer.py │ └── AgtypeParser.py ├── VERSION.py ├── __init__.py ├── exceptions.py ├── builder.py ├── age.py └── models.py ├── samples ├── __init__.py ├── apache-age-agtypes.ipynb ├── apache-age-basic.ipynb └── apache-age-note.ipynb ├── requirements.txt ├── register_pypi.sh ├── antlr ├── README.md └── Agtype.g4 ├── __init__.py ├── .github └── workflows │ └── unittest.yaml ├── .gitignore ├── setup.py ├── README.md ├── test_agtypes.py ├── LICENSE └── test_age_py.py /age/gen/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | psycopg2 --no-binary :all: psycopg2 2 | antlr4-python3-runtime==4.11.1 3 | setuptools 4 | wheel -------------------------------------------------------------------------------- /register_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 setup.py clean --all 4 | rm -Rf build dist 5 | 6 | python3 setup.py install 7 | python3 setup.py bdist_wheel 8 | 9 | #python3 -m pip install --upgrade twine 10 | 11 | #python3 -m twine upload dist/*.whl 12 | -------------------------------------------------------------------------------- /antlr/README.md: -------------------------------------------------------------------------------- 1 | # ANTLR4 Python3 Query Result data parser generation rules for apache-age-python 2 | Python driver for Apache AGE, graph extention for PostgreSQL. 3 | 4 | 5 | ### Build 6 | #### 1) Generate query result data parser with ANTLR4 7 | ``` 8 | # prerequisites : 9 | # - java over 8 10 | # - download ANTLR4 from https://www.antlr.org/download/antlr-4.11.1-complete.jar 11 | # - java -cp antlr-4.11.1-complete.jar org.antlr.v4.Tool -Dlanguage=Python3 -visitor -o ../age/gen Agtype.g4 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | -------------------------------------------------------------------------------- /.github/workflows/unittest.yaml: -------------------------------------------------------------------------------- 1 | name: Run unit tests 2 | run-name: Testing ${{ github.actor }}'s changes 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | jobs: 11 | unit-test: 12 | runs-on: ubuntu-latest 13 | 14 | services: 15 | age: 16 | image: apache/age 17 | env: 18 | POSTGRES_PASSWORD: agens 19 | POSTGRES_USER: postgres 20 | POSTGRES_DB: postgres 21 | ports: 22 | - 5432:5432 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-python@v4 27 | with: 28 | python-version: "3.11" 29 | - run: python -m pip install antlr4-python3-runtime==4.11.1 psycopg2 30 | - name: Run tests 31 | run: python -m unittest -v test_age_py test_agtypes.py 32 | -------------------------------------------------------------------------------- /age/VERSION.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # Unless required by applicable law or agreed to in writing, 11 | # software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | # KIND, either express or implied. See the License for the 14 | # specific language governing permissions and limitations 15 | # under the License. 16 | 17 | VER_MAJOR = 0 18 | VER_MINOR = 0 19 | VER_MICRO = 6 20 | 21 | VERSION = '.'.join([str(VER_MAJOR),str(VER_MINOR),str(VER_MICRO)]) 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | 16 | # Local python directory 17 | **/.ipynb_checkpoints/ 18 | **/__pycache__/ 19 | apache_age_py.egg-info/ 20 | apache_age_python.egg-info/ 21 | dist/ 22 | build/ 23 | .DS_Store 24 | .idea/ 25 | 26 | # antlr generated files 27 | **/.antlr/ 28 | **/*.interp 29 | **/*.tokens -------------------------------------------------------------------------------- /age/__init__.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | from . import age 16 | from .age import * 17 | from .models import * 18 | from .builder import ResultHandler, DummyResultHandler, parseAgeValue, newResultHandler 19 | from . import VERSION 20 | 21 | def version(): 22 | return VERSION.VERSION 23 | 24 | 25 | def connect(dsn=None, graph=None, connection_factory=None, cursor_factory=None, **kwargs): 26 | ag = Age() 27 | ag.connect(dsn=dsn, graph=graph, connection_factory=connection_factory, cursor_factory=cursor_factory, **kwargs) 28 | return ag 29 | 30 | # Dummy ResultHandler 31 | rawPrinter = DummyResultHandler() 32 | 33 | __name__="age" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from setuptools import setup, find_packages 17 | from age import VERSION 18 | 19 | with open("README.md", "r", encoding='utf8') as fh: 20 | long_description = fh.read() 21 | 22 | setup( 23 | name = 'apache-age-python', 24 | version = VERSION.VERSION, 25 | description = 'Python driver support for Apache AGE', 26 | long_description=long_description, 27 | long_description_content_type="text/markdown", 28 | author = 'Ikchan Kwon', 29 | author_email = 'rhizome.ai@gmail.com', 30 | url = 'https://github.com/rhizome-ai/apache-age-python', 31 | download_url = 'https://github.com/rhizome-ai/apache-age-python/releases/' , 32 | license = 'Apache2.0', 33 | install_requires = [ 'psycopg2', 'antlr4-tools' ], 34 | packages = ['age', 'age.gen'], 35 | keywords = ['Graph Database', 'Apache AGE', 'PostgreSQL'], 36 | python_requires = '>=3.9', 37 | # package_data = {}, 38 | # zip_safe=False, 39 | classifiers = [ 40 | 'Programming Language :: Python :: 3.9' 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /age/exceptions.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # Unless required by applicable law or agreed to in writing, 11 | # software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | # KIND, either express or implied. See the License for the 14 | # specific language governing permissions and limitations 15 | # under the License. 16 | from psycopg2.errors import * 17 | 18 | class AgeNotSet(Exception): 19 | def __init__(self, name): 20 | self.name = name 21 | 22 | def __repr__(self) : 23 | return 'AGE extension is not set.' 24 | 25 | class GraphNotFound(Exception): 26 | def __init__(self, name): 27 | self.name = name 28 | 29 | def __repr__(self) : 30 | return 'Graph[' + self.name + '] does not exist.' 31 | 32 | 33 | class GraphAlreadyExists(Exception): 34 | def __init__(self, name): 35 | self.name = name 36 | 37 | def __repr__(self) : 38 | return 'Graph[' + self.name + '] already exists.' 39 | 40 | 41 | class GraphNotSet(Exception): 42 | def __repr__(self) : 43 | return 'Graph name is not set.' 44 | 45 | 46 | class NoConnection(Exception): 47 | def __repr__(self) : 48 | return 'No Connection' 49 | 50 | class NoCursor(Exception): 51 | def __repr__(self) : 52 | return 'No Cursor' 53 | 54 | class SqlExcutionError(Exception): 55 | def __init__(self, msg, cause): 56 | self.msg = msg 57 | self.cause = cause 58 | super().__init__(msg, cause) 59 | 60 | def __repr__(self) : 61 | return 'SqlExcution [' + self.msg + ']' 62 | 63 | class AGTypeError(Exception): 64 | def __init__(self, msg, cause): 65 | self.msg = msg 66 | self.cause = cause 67 | super().__init__(msg, cause) 68 | 69 | 70 | -------------------------------------------------------------------------------- /antlr/Agtype.g4: -------------------------------------------------------------------------------- 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 | grammar Agtype; 21 | 22 | agType 23 | : agValue EOF 24 | ; 25 | 26 | agValue 27 | : value typeAnnotation? 28 | ; 29 | 30 | value 31 | : STRING #StringValue 32 | | INTEGER #IntegerValue 33 | | floatLiteral #FloatValue 34 | | 'true' #TrueBoolean 35 | | 'false' #FalseBoolean 36 | | 'null' #NullValue 37 | | obj #ObjectValue 38 | | array #ArrayValue 39 | ; 40 | 41 | obj 42 | : '{' pair (',' pair)* '}' 43 | | '{' '}' 44 | ; 45 | 46 | pair 47 | : STRING ':' agValue 48 | ; 49 | 50 | array 51 | : '[' agValue (',' agValue)* ']' 52 | | '[' ']' 53 | ; 54 | 55 | typeAnnotation 56 | : '::' IDENT 57 | ; 58 | 59 | IDENT 60 | : [A-Z_a-z][$0-9A-Z_a-z]* 61 | ; 62 | 63 | STRING 64 | : '"' (ESC | SAFECODEPOINT)* '"' 65 | ; 66 | 67 | fragment ESC 68 | : '\\' (["\\/bfnrt] | UNICODE) 69 | ; 70 | 71 | fragment UNICODE 72 | : 'u' HEX HEX HEX HEX 73 | ; 74 | 75 | fragment HEX 76 | : [0-9a-fA-F] 77 | ; 78 | 79 | fragment SAFECODEPOINT 80 | : ~ ["\\\u0000-\u001F] 81 | ; 82 | 83 | INTEGER 84 | : '-'? INT 85 | ; 86 | 87 | fragment INT 88 | : '0' | [1-9] [0-9]* 89 | ; 90 | 91 | floatLiteral 92 | : RegularFloat 93 | | ExponentFloat 94 | | '-'? 'Infinity' 95 | | 'NaN' 96 | ; 97 | 98 | RegularFloat 99 | : '-'? INT DECIMAL 100 | ; 101 | 102 | ExponentFloat 103 | : '-'? INT DECIMAL? SCIENTIFIC 104 | ; 105 | 106 | fragment DECIMAL 107 | : '.' [0-9]+ 108 | ; 109 | 110 | fragment SCIENTIFIC 111 | : [Ee][+-]? [0-9]+ 112 | ; 113 | 114 | WS 115 | : [ \t\n\r] + -> skip 116 | ; 117 | -------------------------------------------------------------------------------- /age/gen/AgtypeVisitor.py: -------------------------------------------------------------------------------- 1 | # Generated from antlr/Agtype.g4 by ANTLR 4.11.1 2 | from antlr4 import * 3 | if __name__ is not None and "." in __name__: 4 | from .AgtypeParser import AgtypeParser 5 | else: 6 | from AgtypeParser import AgtypeParser 7 | 8 | # This class defines a complete generic visitor for a parse tree produced by AgtypeParser. 9 | 10 | class AgtypeVisitor(ParseTreeVisitor): 11 | 12 | # Visit a parse tree produced by AgtypeParser#agType. 13 | def visitAgType(self, ctx:AgtypeParser.AgTypeContext): 14 | return self.visitChildren(ctx) 15 | 16 | 17 | # Visit a parse tree produced by AgtypeParser#agValue. 18 | def visitAgValue(self, ctx:AgtypeParser.AgValueContext): 19 | return self.visitChildren(ctx) 20 | 21 | 22 | # Visit a parse tree produced by AgtypeParser#StringValue. 23 | def visitStringValue(self, ctx:AgtypeParser.StringValueContext): 24 | return self.visitChildren(ctx) 25 | 26 | 27 | # Visit a parse tree produced by AgtypeParser#IntegerValue. 28 | def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext): 29 | return self.visitChildren(ctx) 30 | 31 | 32 | # Visit a parse tree produced by AgtypeParser#FloatValue. 33 | def visitFloatValue(self, ctx:AgtypeParser.FloatValueContext): 34 | return self.visitChildren(ctx) 35 | 36 | 37 | # Visit a parse tree produced by AgtypeParser#TrueBoolean. 38 | def visitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext): 39 | return self.visitChildren(ctx) 40 | 41 | 42 | # Visit a parse tree produced by AgtypeParser#FalseBoolean. 43 | def visitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext): 44 | return self.visitChildren(ctx) 45 | 46 | 47 | # Visit a parse tree produced by AgtypeParser#NullValue. 48 | def visitNullValue(self, ctx:AgtypeParser.NullValueContext): 49 | return self.visitChildren(ctx) 50 | 51 | 52 | # Visit a parse tree produced by AgtypeParser#ObjectValue. 53 | def visitObjectValue(self, ctx:AgtypeParser.ObjectValueContext): 54 | return self.visitChildren(ctx) 55 | 56 | 57 | # Visit a parse tree produced by AgtypeParser#ArrayValue. 58 | def visitArrayValue(self, ctx:AgtypeParser.ArrayValueContext): 59 | return self.visitChildren(ctx) 60 | 61 | 62 | # Visit a parse tree produced by AgtypeParser#obj. 63 | def visitObj(self, ctx:AgtypeParser.ObjContext): 64 | return self.visitChildren(ctx) 65 | 66 | 67 | # Visit a parse tree produced by AgtypeParser#pair. 68 | def visitPair(self, ctx:AgtypeParser.PairContext): 69 | return self.visitChildren(ctx) 70 | 71 | 72 | # Visit a parse tree produced by AgtypeParser#array. 73 | def visitArray(self, ctx:AgtypeParser.ArrayContext): 74 | return self.visitChildren(ctx) 75 | 76 | 77 | # Visit a parse tree produced by AgtypeParser#typeAnnotation. 78 | def visitTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext): 79 | return self.visitChildren(ctx) 80 | 81 | 82 | # Visit a parse tree produced by AgtypeParser#floatLiteral. 83 | def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext): 84 | return self.visitChildren(ctx) 85 | 86 | 87 | 88 | del AgtypeParser -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unofficial Python Driver for Apache AGE 2 | 3 | This driver project is not official. 4 | Please visit Official Apache AGE Git Project : https://github.com/apache/age/tree/master/drivers/python 5 | 6 | 7 | [Apache AGE](https://age.apache.org/) is a PostgreSQL extension that provides graph database functionality. The goal of the Apache AGE project is to create single storage that can handle both relational and graph model data so that users can use standard ANSI SQL along with openCypher, the Graph query language. This repository hosts the development of the Python driver for this Apache extension (currently in Incubator status). Thanks for checking it out. 8 | 9 | A graph consists of a set of vertices (also called nodes) and edges, where each individual vertex and edge possesses a map of properties. A vertex is the basic object of a graph, that can exist independently of everything else in the graph. An edge creates a directed connection between two vertices. A graph database is simply composed of vertices and edges. This type of database is useful when the meaning is in the relationships between the data. Relational databases can easily handle direct relationships, but indirect relationships are more difficult to deal with in relational databases. A graph database stores relationship information as a first-class entity. Apache AGE gives you the best of both worlds, simultaneously. 10 | 11 | Apache AGE is: 12 | 13 | - **Powerful** -- AGE adds graph database support to the already popular PostgreSQL database: PostgreSQL is used by organizations including Apple, Spotify, and NASA. 14 | - **Flexible** -- AGE allows you to perform openCypher queries, which make complex queries much easier to write. 15 | - **Intelligent** -- AGE allows you to perform graph queries that are the basis for many next level web services such as fraud & intrustion detection, master data management, product recommendations, identity and relationship management, experience personalization, knowledge management and more. 16 | 17 | ### Features 18 | * Cypher query support for Psycopg2 PostreSQL driver (enables cypher queries directly) 19 | * Deserialize AGE result (AGType) to Vertex, Edge, Path 20 | 21 | ### Requirements 22 | * Python 3.9 or higher 23 | * This module runs on [psycopg2](https://www.psycopg.org/) and [antlr4-python3](https://pypi.org/project/antlr4-python3-runtime/) 24 | ``` 25 | sudo apt-get update 26 | sudo apt-get install python3-dev libpq-dev 27 | pip install --no-binary :all: psycopg2 28 | pip install antlr4-python3-runtime==4.11.1 29 | ``` 30 | 31 | ### Install via PIP 32 | ``` 33 | pip install apache-age-python 34 | ``` 35 | 36 | ### Build from Source 37 | ``` 38 | git clone https://github.com/rhizome-ai/apache-age-python 39 | cd apache-age-python 40 | 41 | python setup.py install 42 | ``` 43 | 44 | ### Getting Started 45 | * [Jupyter Notebook: Basic Examples](samples/apache-age-basic.ipynb) 46 | * [Jupyter Notebook: More Complex Examples](samples/apache-age-note.ipynb) 47 | 48 | ### Tests 49 | ``` 50 | python -m unittest -v test_age_py.py 51 | python -m unittest -v test_agtypes.py 52 | ``` 53 | [Agtype data type conversion tests](samples/apache-age-agtypes.ipynb) 54 | 55 | ### For more information about [Apache AGE](https://age.apache.org/) 56 | * Apache Incubator Age: https://age.apache.org/ 57 | * Github: https://github.com/apache/incubator-age 58 | * Documentation: https://age.incubator.apache.org/docs/ 59 | * apache-age-python GitHub: https://github.com/rhizome-ai/apache-age-python 60 | 61 | ### Troubleshooting 62 | 1. Make sure AGE is installed. 63 | 2. Check to see if AGE is loaded in PostgreSQL. 64 | ``` 65 | # psql 66 | CREATE EXTENSION age; 67 | LOAD 'age'; 68 | SET search_path = ag_catalog, "$user", public; 69 | ``` 70 | ### License 71 | [Apache-2.0 License](https://www.apache.org/licenses/LICENSE-2.0) 72 | -------------------------------------------------------------------------------- /age/gen/AgtypeListener.py: -------------------------------------------------------------------------------- 1 | # Generated from antlr/Agtype.g4 by ANTLR 4.11.1 2 | from antlr4 import * 3 | if __name__ is not None and "." in __name__: 4 | from .AgtypeParser import AgtypeParser 5 | else: 6 | from AgtypeParser import AgtypeParser 7 | 8 | # This class defines a complete listener for a parse tree produced by AgtypeParser. 9 | class AgtypeListener(ParseTreeListener): 10 | 11 | # Enter a parse tree produced by AgtypeParser#agType. 12 | def enterAgType(self, ctx:AgtypeParser.AgTypeContext): 13 | pass 14 | 15 | # Exit a parse tree produced by AgtypeParser#agType. 16 | def exitAgType(self, ctx:AgtypeParser.AgTypeContext): 17 | pass 18 | 19 | 20 | # Enter a parse tree produced by AgtypeParser#agValue. 21 | def enterAgValue(self, ctx:AgtypeParser.AgValueContext): 22 | pass 23 | 24 | # Exit a parse tree produced by AgtypeParser#agValue. 25 | def exitAgValue(self, ctx:AgtypeParser.AgValueContext): 26 | pass 27 | 28 | 29 | # Enter a parse tree produced by AgtypeParser#StringValue. 30 | def enterStringValue(self, ctx:AgtypeParser.StringValueContext): 31 | pass 32 | 33 | # Exit a parse tree produced by AgtypeParser#StringValue. 34 | def exitStringValue(self, ctx:AgtypeParser.StringValueContext): 35 | pass 36 | 37 | 38 | # Enter a parse tree produced by AgtypeParser#IntegerValue. 39 | def enterIntegerValue(self, ctx:AgtypeParser.IntegerValueContext): 40 | pass 41 | 42 | # Exit a parse tree produced by AgtypeParser#IntegerValue. 43 | def exitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext): 44 | pass 45 | 46 | 47 | # Enter a parse tree produced by AgtypeParser#FloatValue. 48 | def enterFloatValue(self, ctx:AgtypeParser.FloatValueContext): 49 | pass 50 | 51 | # Exit a parse tree produced by AgtypeParser#FloatValue. 52 | def exitFloatValue(self, ctx:AgtypeParser.FloatValueContext): 53 | pass 54 | 55 | 56 | # Enter a parse tree produced by AgtypeParser#TrueBoolean. 57 | def enterTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext): 58 | pass 59 | 60 | # Exit a parse tree produced by AgtypeParser#TrueBoolean. 61 | def exitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext): 62 | pass 63 | 64 | 65 | # Enter a parse tree produced by AgtypeParser#FalseBoolean. 66 | def enterFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext): 67 | pass 68 | 69 | # Exit a parse tree produced by AgtypeParser#FalseBoolean. 70 | def exitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext): 71 | pass 72 | 73 | 74 | # Enter a parse tree produced by AgtypeParser#NullValue. 75 | def enterNullValue(self, ctx:AgtypeParser.NullValueContext): 76 | pass 77 | 78 | # Exit a parse tree produced by AgtypeParser#NullValue. 79 | def exitNullValue(self, ctx:AgtypeParser.NullValueContext): 80 | pass 81 | 82 | 83 | # Enter a parse tree produced by AgtypeParser#ObjectValue. 84 | def enterObjectValue(self, ctx:AgtypeParser.ObjectValueContext): 85 | pass 86 | 87 | # Exit a parse tree produced by AgtypeParser#ObjectValue. 88 | def exitObjectValue(self, ctx:AgtypeParser.ObjectValueContext): 89 | pass 90 | 91 | 92 | # Enter a parse tree produced by AgtypeParser#ArrayValue. 93 | def enterArrayValue(self, ctx:AgtypeParser.ArrayValueContext): 94 | pass 95 | 96 | # Exit a parse tree produced by AgtypeParser#ArrayValue. 97 | def exitArrayValue(self, ctx:AgtypeParser.ArrayValueContext): 98 | pass 99 | 100 | 101 | # Enter a parse tree produced by AgtypeParser#obj. 102 | def enterObj(self, ctx:AgtypeParser.ObjContext): 103 | pass 104 | 105 | # Exit a parse tree produced by AgtypeParser#obj. 106 | def exitObj(self, ctx:AgtypeParser.ObjContext): 107 | pass 108 | 109 | 110 | # Enter a parse tree produced by AgtypeParser#pair. 111 | def enterPair(self, ctx:AgtypeParser.PairContext): 112 | pass 113 | 114 | # Exit a parse tree produced by AgtypeParser#pair. 115 | def exitPair(self, ctx:AgtypeParser.PairContext): 116 | pass 117 | 118 | 119 | # Enter a parse tree produced by AgtypeParser#array. 120 | def enterArray(self, ctx:AgtypeParser.ArrayContext): 121 | pass 122 | 123 | # Exit a parse tree produced by AgtypeParser#array. 124 | def exitArray(self, ctx:AgtypeParser.ArrayContext): 125 | pass 126 | 127 | 128 | # Enter a parse tree produced by AgtypeParser#typeAnnotation. 129 | def enterTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext): 130 | pass 131 | 132 | # Exit a parse tree produced by AgtypeParser#typeAnnotation. 133 | def exitTypeAnnotation(self, ctx:AgtypeParser.TypeAnnotationContext): 134 | pass 135 | 136 | 137 | # Enter a parse tree produced by AgtypeParser#floatLiteral. 138 | def enterFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext): 139 | pass 140 | 141 | # Exit a parse tree produced by AgtypeParser#floatLiteral. 142 | def exitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext): 143 | pass 144 | 145 | 146 | 147 | del AgtypeParser -------------------------------------------------------------------------------- /samples/apache-age-agtypes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "6301516b-a3fa-48e2-a95f-0f79903a6cdd", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import unittest\n", 11 | "from decimal import Decimal\n", 12 | "import age \n", 13 | "\n", 14 | "resultHandler = age.newResultHandler()\n", 15 | " \n", 16 | "def evalExp(exp):\n", 17 | " value = resultHandler.parse(exp) \n", 18 | " print(type(value), \"|\", exp, \" --> \" ,value )\n", 19 | " \n", 20 | " " 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "adf14fe9-692c-4701-86d4-8c84fd53d966", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "\n", 31 | "mapStr = '{\"name\": \"Smith\", \"num\":123, \"yn\":true, \"bigInt\":123456789123456789123456789123456789::numeric}' \n", 32 | "arrStr = '[\"name\", \"Smith\", \"num\", 123, \"yn\", true, 123456789123456789123456789123456789.8888::numeric]' \n", 33 | "strStr = '\"abcd\"' \n", 34 | "intStr = '1234' \n", 35 | "floatStr = '1234.56789' \n", 36 | "numericStr1 = '12345678901234567890123456789123456789.789::numeric' \n", 37 | "numericStr2 = '12345678901234567890123456789123456789::numeric' \n", 38 | "boolStr = 'true' \n", 39 | "\n", 40 | "evalExp(mapStr)\n", 41 | "evalExp(arrStr)\n", 42 | "evalExp(strStr)\n", 43 | "evalExp(intStr)\n", 44 | "evalExp(floatStr)\n", 45 | "evalExp(numericStr1)\n", 46 | "evalExp(numericStr2)\n", 47 | "evalExp(boolStr)\n", 48 | "\n", 49 | "evalExp('-6.45161290322581e+46') \n", 50 | "evalExp('-123456789.99::numeric') \n", 51 | "evalExp('-6.45161290322581e+46::numeric') \n", 52 | "evalExp('1234') \n", 53 | "evalExp('NaN') \n", 54 | "evalExp('-Infinity') \n", 55 | "evalExp('Infinity') " 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "id": "8881804e-d71d-4704-b74c-376ed55be808", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | " vertexExp = '''{\"id\": 2251799813685425, \"label\": \"Person\", \n", 66 | " \"properties\": {\"name\": \"Smith\", \"numInt\":123, \"numFloat\": 384.23424, \n", 67 | " \"bigInt\":123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789::numeric, \n", 68 | " \"bigFloat\":123456789123456789123456789123456789.12345::numeric, \n", 69 | " \"yn\":true, \"nullVal\": null}}::vertex'''\n", 70 | "\n", 71 | "vertex = age.parseAgeValue(vertexExp)\n", 72 | "print(type(vertex.id), vertex.id)\n", 73 | "print(type(vertex.label), vertex.label)\n", 74 | "print(type(vertex[\"name\"]), vertex[\"name\"])\n", 75 | "print(type(vertex[\"numInt\"]), vertex[\"numInt\"])\n", 76 | "print(type(vertex[\"numFloat\"]), vertex[\"numFloat\"])\n", 77 | "print(type(vertex[\"bigInt\"]), vertex[\"bigInt\"])\n", 78 | "print(type(vertex[\"bigFloat\"]), vertex[\"bigFloat\"])\n", 79 | "print(type(vertex[\"yn\"]), vertex[\"yn\"])\n", 80 | "print(type(vertex[\"nullVal\"]), vertex[\"nullVal\"])" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "id": "0a70e467-9587-4eb7-b97d-895223009bcc", 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "\n", 91 | "pathExp = '''[{\"id\": 2251799813685425, \"label\": \"Person\", \"properties\": {\"name\": \"Smith\"}}::vertex, \n", 92 | " {\"id\": 2533274790396576, \"label\": \"workWith\", \"end_id\": 2251799813685425, \"start_id\": 2251799813685424, \n", 93 | " \"properties\": {\"weight\": 3, \"bigFloat\":123456789123456789123456789.12345::numeric}}::edge, \n", 94 | " {\"id\": 2251799813685424, \"label\": \"Person\", \"properties\": {\"name\": \"Joe\"}}::vertex]::path'''\n", 95 | "\n", 96 | "path = age.parseAgeValue(pathExp)\n", 97 | "vertexStart = path[0]\n", 98 | "edge = path[1]\n", 99 | "vertexEnd = path[2]\n", 100 | "\n", 101 | "print(type(vertexStart.id), vertexStart.id)\n", 102 | "print(type(vertexStart.label), vertexStart.label)\n", 103 | "print(type(vertexStart[\"name\"]), vertexStart[\"name\"])\n", 104 | "\n", 105 | "print(type(edge.id), edge.id)\n", 106 | "print(type(edge.label), edge.label)\n", 107 | "print(type(edge[\"weight\"]), edge[\"weight\"])\n", 108 | "print(type(edge[\"bigFloat\"]), edge[\"bigFloat\"])\n", 109 | "\n", 110 | "print(type(vertexEnd.id), vertexEnd.id)\n", 111 | "print(type(vertexEnd.label), vertexEnd.label)\n", 112 | "print(type(vertexEnd[\"name\"]), vertexEnd[\"name\"])\n", 113 | "\n" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "id": "62b7273f-ac6c-4dec-af7e-0467daa140f4", 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 3", 128 | "language": "python", 129 | "name": "python3" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 3 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython3", 141 | "version": "3.9.2" 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 5 146 | } 147 | -------------------------------------------------------------------------------- /test_agtypes.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import unittest 17 | from decimal import Decimal 18 | import math 19 | import age 20 | 21 | class TestAgtype(unittest.TestCase): 22 | resultHandler = None 23 | 24 | def __init__(self, methodName: str) -> None: 25 | super().__init__(methodName=methodName) 26 | self.resultHandler = age.newResultHandler() 27 | 28 | def parse(self, exp): 29 | return self.resultHandler.parse(exp) 30 | 31 | def test_scalar(self): 32 | mapStr = '{"name": "Smith", "num":123, "yn":true, "bigInt":123456789123456789123456789123456789::numeric}' 33 | arrStr = '["name", "Smith", "num", 123, "yn", true, 123456789123456789123456789123456789.8888::numeric]' 34 | strStr = '"abcd"' 35 | intStr = '1234' 36 | floatStr = '1234.56789' 37 | floatStr2 = '6.45161290322581e+46' 38 | numericStr1 = '12345678901234567890123456789123456789.789::numeric' 39 | numericStr2 = '12345678901234567890123456789123456789::numeric' 40 | boolStr = 'true' 41 | nullStr = '' 42 | nanStr = "NaN" 43 | infpStr = "Infinity" 44 | infnStr = "-Infinity" 45 | 46 | mapVal = self.parse(mapStr) 47 | arrVal = self.parse(arrStr) 48 | str = self.parse(strStr) 49 | intVal = self.parse(intStr) 50 | floatVal = self.parse(floatStr) 51 | floatVal2 = self.parse(floatStr2) 52 | bigFloat = self.parse(numericStr1) 53 | bigInt = self.parse(numericStr2) 54 | boolVal = self.parse(boolStr) 55 | nullVal = self.parse(nullStr) 56 | nanVal = self.parse(nanStr) 57 | infpVal = self.parse(infpStr) 58 | infnVal = self.parse(infnStr) 59 | 60 | print("map", type(mapVal), mapVal) 61 | print("arr", type(arrVal), arrVal) 62 | print("str", type(str), str) 63 | print("intVal", type(intVal), intVal) 64 | print("floatVal", type(floatVal), floatVal) 65 | print("floatVal", type(floatVal2), floatVal2) 66 | print("bigFloat", type(bigFloat), bigFloat) 67 | print("bigInt", type(bigInt), bigInt) 68 | print("bool", type(boolVal), boolVal) 69 | print("null", type(nullVal), nullVal) 70 | print("nanVal", type(nanVal), nanVal) 71 | print("infpVal", type(infpVal), infpVal) 72 | print("infnVal", type(infnVal), infnVal) 73 | 74 | self.assertEqual(mapVal, {'name': 'Smith', 'num': 123, 'yn': True, 'bigInt': Decimal('123456789123456789123456789123456789')}) 75 | self.assertEqual(arrVal, ["name", "Smith", "num", 123, "yn", True, Decimal("123456789123456789123456789123456789.8888")] ) 76 | self.assertEqual(str, "abcd") 77 | self.assertEqual(intVal, 1234) 78 | self.assertEqual(floatVal, 1234.56789) 79 | self.assertEqual(floatVal2, 6.45161290322581e+46) 80 | self.assertEqual(bigFloat, Decimal("12345678901234567890123456789123456789.789")) 81 | self.assertEqual(bigInt, Decimal("12345678901234567890123456789123456789")) 82 | self.assertEqual(boolVal, True) 83 | self.assertTrue(math.isnan(nanVal)) 84 | self.assertTrue(math.isinf(infpVal)) 85 | self.assertTrue(math.isinf(infnVal)) 86 | 87 | def test_vertex(self): 88 | vertexExp = '''{"id": 2251799813685425, "label": "Person", 89 | "properties": {"name": "Smith", "numInt":123, "numFloat": 384.23424, 90 | "bigInt":123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789::numeric, 91 | "bigFloat":123456789123456789123456789123456789.12345::numeric, 92 | "yn":true, "nullVal": null}}::vertex''' 93 | 94 | vertex = self.parse(vertexExp) 95 | self.assertEqual(vertex.id, 2251799813685425) 96 | self.assertEqual(vertex.label, "Person") 97 | self.assertEqual(vertex["name"], "Smith") 98 | self.assertEqual(vertex["numInt"], 123) 99 | self.assertEqual(vertex["numFloat"], 384.23424) 100 | self.assertEqual(vertex["bigInt"], Decimal("123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789")) 101 | self.assertEqual(vertex["bigFloat"], Decimal("123456789123456789123456789123456789.12345")) 102 | self.assertEqual(vertex["yn"], True) 103 | self.assertEqual(vertex["nullVal"], None) 104 | 105 | def test_path(self): 106 | pathExp = '''[{"id": 2251799813685425, "label": "Person", "properties": {"name": "Smith"}}::vertex, 107 | {"id": 2533274790396576, "label": "workWith", "end_id": 2251799813685425, "start_id": 2251799813685424, 108 | "properties": {"weight": 3, "bigFloat":123456789123456789123456789.12345::numeric}}::edge, 109 | {"id": 2251799813685424, "label": "Person", "properties": {"name": "Joe"}}::vertex]::path''' 110 | 111 | path = self.parse(pathExp) 112 | vertexStart = path[0] 113 | edge = path[1] 114 | vertexEnd = path[2] 115 | self.assertEqual(vertexStart.id, 2251799813685425) 116 | self.assertEqual(vertexStart.label, "Person") 117 | self.assertEqual(vertexStart["name"], "Smith") 118 | 119 | self.assertEqual(edge.id, 2533274790396576) 120 | self.assertEqual(edge.label, "workWith") 121 | self.assertEqual(edge["weight"], 3) 122 | self.assertEqual(edge["bigFloat"], Decimal("123456789123456789123456789.12345")) 123 | 124 | self.assertEqual(vertexEnd.id, 2251799813685424) 125 | self.assertEqual(vertexEnd.label, "Person") 126 | self.assertEqual(vertexEnd["name"], "Joe") 127 | 128 | 129 | if __name__ == '__main__': 130 | unittest.main() -------------------------------------------------------------------------------- /age/gen/AgtypeLexer.py: -------------------------------------------------------------------------------- 1 | # Generated from antlr/Agtype.g4 by ANTLR 4.11.1 2 | from antlr4 import * 3 | from io import StringIO 4 | import sys 5 | if sys.version_info[1] > 5: 6 | from typing import TextIO 7 | else: 8 | from typing.io import TextIO 9 | 10 | 11 | def serializedATN(): 12 | return [ 13 | 4,0,19,183,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5, 14 | 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2, 15 | 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7, 16 | 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,1, 17 | 0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1, 18 | 3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1, 19 | 10,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1, 20 | 12,1,13,1,13,5,13,102,8,13,10,13,12,13,105,9,13,1,14,1,14,1,14,5, 21 | 14,110,8,14,10,14,12,14,113,9,14,1,14,1,14,1,15,1,15,1,15,3,15,120, 22 | 8,15,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,18,1,18,1,19,3,19, 23 | 133,8,19,1,19,1,19,1,20,1,20,1,20,5,20,140,8,20,10,20,12,20,143, 24 | 9,20,3,20,145,8,20,1,21,3,21,148,8,21,1,21,1,21,1,21,1,22,3,22,154, 25 | 8,22,1,22,1,22,3,22,158,8,22,1,22,1,22,1,23,1,23,4,23,164,8,23,11, 26 | 23,12,23,165,1,24,1,24,3,24,170,8,24,1,24,4,24,173,8,24,11,24,12, 27 | 24,174,1,25,4,25,178,8,25,11,25,12,25,179,1,25,1,25,0,0,26,1,1,3, 28 | 2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14, 29 | 29,15,31,0,33,0,35,0,37,0,39,16,41,0,43,17,45,18,47,0,49,0,51,19, 30 | 1,0,10,3,0,65,90,95,95,97,122,5,0,36,36,48,57,65,90,95,95,97,122, 31 | 8,0,34,34,47,47,92,92,98,98,102,102,110,110,114,114,116,116,3,0, 32 | 48,57,65,70,97,102,3,0,0,31,34,34,92,92,1,0,49,57,1,0,48,57,2,0, 33 | 69,69,101,101,2,0,43,43,45,45,3,0,9,10,13,13,32,32,189,0,1,1,0,0, 34 | 0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0, 35 | 13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0, 36 | 23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,39,1,0,0,0,0, 37 | 43,1,0,0,0,0,45,1,0,0,0,0,51,1,0,0,0,1,53,1,0,0,0,3,58,1,0,0,0,5, 38 | 64,1,0,0,0,7,69,1,0,0,0,9,71,1,0,0,0,11,73,1,0,0,0,13,75,1,0,0,0, 39 | 15,77,1,0,0,0,17,79,1,0,0,0,19,81,1,0,0,0,21,84,1,0,0,0,23,86,1, 40 | 0,0,0,25,95,1,0,0,0,27,99,1,0,0,0,29,106,1,0,0,0,31,116,1,0,0,0, 41 | 33,121,1,0,0,0,35,127,1,0,0,0,37,129,1,0,0,0,39,132,1,0,0,0,41,144, 42 | 1,0,0,0,43,147,1,0,0,0,45,153,1,0,0,0,47,161,1,0,0,0,49,167,1,0, 43 | 0,0,51,177,1,0,0,0,53,54,5,116,0,0,54,55,5,114,0,0,55,56,5,117,0, 44 | 0,56,57,5,101,0,0,57,2,1,0,0,0,58,59,5,102,0,0,59,60,5,97,0,0,60, 45 | 61,5,108,0,0,61,62,5,115,0,0,62,63,5,101,0,0,63,4,1,0,0,0,64,65, 46 | 5,110,0,0,65,66,5,117,0,0,66,67,5,108,0,0,67,68,5,108,0,0,68,6,1, 47 | 0,0,0,69,70,5,123,0,0,70,8,1,0,0,0,71,72,5,44,0,0,72,10,1,0,0,0, 48 | 73,74,5,125,0,0,74,12,1,0,0,0,75,76,5,58,0,0,76,14,1,0,0,0,77,78, 49 | 5,91,0,0,78,16,1,0,0,0,79,80,5,93,0,0,80,18,1,0,0,0,81,82,5,58,0, 50 | 0,82,83,5,58,0,0,83,20,1,0,0,0,84,85,5,45,0,0,85,22,1,0,0,0,86,87, 51 | 5,73,0,0,87,88,5,110,0,0,88,89,5,102,0,0,89,90,5,105,0,0,90,91,5, 52 | 110,0,0,91,92,5,105,0,0,92,93,5,116,0,0,93,94,5,121,0,0,94,24,1, 53 | 0,0,0,95,96,5,78,0,0,96,97,5,97,0,0,97,98,5,78,0,0,98,26,1,0,0,0, 54 | 99,103,7,0,0,0,100,102,7,1,0,0,101,100,1,0,0,0,102,105,1,0,0,0,103, 55 | 101,1,0,0,0,103,104,1,0,0,0,104,28,1,0,0,0,105,103,1,0,0,0,106,111, 56 | 5,34,0,0,107,110,3,31,15,0,108,110,3,37,18,0,109,107,1,0,0,0,109, 57 | 108,1,0,0,0,110,113,1,0,0,0,111,109,1,0,0,0,111,112,1,0,0,0,112, 58 | 114,1,0,0,0,113,111,1,0,0,0,114,115,5,34,0,0,115,30,1,0,0,0,116, 59 | 119,5,92,0,0,117,120,7,2,0,0,118,120,3,33,16,0,119,117,1,0,0,0,119, 60 | 118,1,0,0,0,120,32,1,0,0,0,121,122,5,117,0,0,122,123,3,35,17,0,123, 61 | 124,3,35,17,0,124,125,3,35,17,0,125,126,3,35,17,0,126,34,1,0,0,0, 62 | 127,128,7,3,0,0,128,36,1,0,0,0,129,130,8,4,0,0,130,38,1,0,0,0,131, 63 | 133,5,45,0,0,132,131,1,0,0,0,132,133,1,0,0,0,133,134,1,0,0,0,134, 64 | 135,3,41,20,0,135,40,1,0,0,0,136,145,5,48,0,0,137,141,7,5,0,0,138, 65 | 140,7,6,0,0,139,138,1,0,0,0,140,143,1,0,0,0,141,139,1,0,0,0,141, 66 | 142,1,0,0,0,142,145,1,0,0,0,143,141,1,0,0,0,144,136,1,0,0,0,144, 67 | 137,1,0,0,0,145,42,1,0,0,0,146,148,5,45,0,0,147,146,1,0,0,0,147, 68 | 148,1,0,0,0,148,149,1,0,0,0,149,150,3,41,20,0,150,151,3,47,23,0, 69 | 151,44,1,0,0,0,152,154,5,45,0,0,153,152,1,0,0,0,153,154,1,0,0,0, 70 | 154,155,1,0,0,0,155,157,3,41,20,0,156,158,3,47,23,0,157,156,1,0, 71 | 0,0,157,158,1,0,0,0,158,159,1,0,0,0,159,160,3,49,24,0,160,46,1,0, 72 | 0,0,161,163,5,46,0,0,162,164,7,6,0,0,163,162,1,0,0,0,164,165,1,0, 73 | 0,0,165,163,1,0,0,0,165,166,1,0,0,0,166,48,1,0,0,0,167,169,7,7,0, 74 | 0,168,170,7,8,0,0,169,168,1,0,0,0,169,170,1,0,0,0,170,172,1,0,0, 75 | 0,171,173,7,6,0,0,172,171,1,0,0,0,173,174,1,0,0,0,174,172,1,0,0, 76 | 0,174,175,1,0,0,0,175,50,1,0,0,0,176,178,7,9,0,0,177,176,1,0,0,0, 77 | 178,179,1,0,0,0,179,177,1,0,0,0,179,180,1,0,0,0,180,181,1,0,0,0, 78 | 181,182,6,25,0,0,182,52,1,0,0,0,15,0,103,109,111,119,132,141,144, 79 | 147,153,157,165,169,174,179,1,6,0,0 80 | ] 81 | 82 | class AgtypeLexer(Lexer): 83 | 84 | atn = ATNDeserializer().deserialize(serializedATN()) 85 | 86 | decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] 87 | 88 | T__0 = 1 89 | T__1 = 2 90 | T__2 = 3 91 | T__3 = 4 92 | T__4 = 5 93 | T__5 = 6 94 | T__6 = 7 95 | T__7 = 8 96 | T__8 = 9 97 | T__9 = 10 98 | T__10 = 11 99 | T__11 = 12 100 | T__12 = 13 101 | IDENT = 14 102 | STRING = 15 103 | INTEGER = 16 104 | RegularFloat = 17 105 | ExponentFloat = 18 106 | WS = 19 107 | 108 | channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] 109 | 110 | modeNames = [ "DEFAULT_MODE" ] 111 | 112 | literalNames = [ "", 113 | "'true'", "'false'", "'null'", "'{'", "','", "'}'", "':'", "'['", 114 | "']'", "'::'", "'-'", "'Infinity'", "'NaN'" ] 115 | 116 | symbolicNames = [ "", 117 | "IDENT", "STRING", "INTEGER", "RegularFloat", "ExponentFloat", 118 | "WS" ] 119 | 120 | ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", 121 | "T__7", "T__8", "T__9", "T__10", "T__11", "T__12", "IDENT", 122 | "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "INTEGER", 123 | "INT", "RegularFloat", "ExponentFloat", "DECIMAL", "SCIENTIFIC", 124 | "WS" ] 125 | 126 | grammarFileName = "Agtype.g4" 127 | 128 | def __init__(self, input=None, output:TextIO = sys.stdout): 129 | super().__init__(input, output) 130 | self.checkVersion("4.11.1") 131 | self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) 132 | self._actions = None 133 | self._predicates = None 134 | 135 | 136 | -------------------------------------------------------------------------------- /age/builder.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | from . import gen 16 | from .gen.AgtypeLexer import AgtypeLexer 17 | from .gen.AgtypeParser import AgtypeParser 18 | from .gen.AgtypeVisitor import AgtypeVisitor 19 | from .models import * 20 | from .exceptions import * 21 | from antlr4 import * 22 | from antlr4.tree.Tree import * 23 | from decimal import Decimal 24 | 25 | class ResultHandler: 26 | def parse(ageData): 27 | pass 28 | 29 | def newResultHandler(query=""): 30 | resultHandler = Antlr4ResultHandler(None, query) 31 | return resultHandler 32 | 33 | def parseAgeValue(value, cursor=None): 34 | if value is None: 35 | return None 36 | 37 | resultHandler = Antlr4ResultHandler(None) 38 | try: 39 | return resultHandler.parse(value) 40 | except Exception as ex: 41 | raise AGTypeError(value) 42 | 43 | 44 | class Antlr4ResultHandler(ResultHandler): 45 | def __init__(self, vertexCache, query=None): 46 | self.lexer = AgtypeLexer() 47 | self.parser = AgtypeParser(None) 48 | self.visitor = ResultVisitor(vertexCache) 49 | 50 | def parse(self, ageData): 51 | if not ageData: 52 | return None 53 | # print("Parse::", ageData) 54 | 55 | self.lexer.inputStream = InputStream(ageData) 56 | self.parser.setTokenStream(CommonTokenStream(self.lexer)) 57 | self.parser.reset() 58 | tree = self.parser.agType() 59 | parsed = tree.accept(self.visitor) 60 | return parsed 61 | 62 | 63 | # print raw result String 64 | class DummyResultHandler(ResultHandler): 65 | def parse(self, ageData): 66 | print(ageData) 67 | 68 | # default agType visitor 69 | class ResultVisitor(AgtypeVisitor): 70 | vertexCache = None 71 | 72 | def __init__(self, cache) -> None: 73 | super().__init__() 74 | self.vertexCache = cache 75 | 76 | 77 | def visitAgType(self, ctx:AgtypeParser.AgTypeContext): 78 | agVal = ctx.agValue() 79 | if agVal != None: 80 | obj = ctx.agValue().accept(self) 81 | return obj 82 | 83 | return None 84 | 85 | def visitAgValue(self, ctx:AgtypeParser.AgValueContext): 86 | annoCtx = ctx.typeAnnotation() 87 | valueCtx = ctx.value() 88 | 89 | if annoCtx is not None: 90 | annoCtx.accept(self) 91 | anno = annoCtx.IDENT().getText() 92 | return self.handleAnnotatedValue(anno, valueCtx) 93 | else: 94 | return valueCtx.accept(self) 95 | 96 | 97 | # Visit a parse tree produced by AgtypeParser#StringValue. 98 | def visitStringValue(self, ctx:AgtypeParser.StringValueContext): 99 | return ctx.STRING().getText().strip('"') 100 | 101 | 102 | # Visit a parse tree produced by AgtypeParser#IntegerValue. 103 | def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext): 104 | return int(ctx.INTEGER().getText()) 105 | 106 | # Visit a parse tree produced by AgtypeParser#floatLiteral. 107 | def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext): 108 | c = ctx.getChild(0) 109 | tp = c.symbol.type 110 | text = ctx.getText() 111 | if tp == AgtypeParser.RegularFloat: 112 | return float(text) 113 | elif tp == AgtypeParser.ExponentFloat: 114 | return float(text) 115 | else: 116 | if text == 'NaN': 117 | return float('nan') 118 | elif text == '-Infinity': 119 | return float('-inf') 120 | elif text == 'Infinity': 121 | return float('inf') 122 | else: 123 | return Exception("Unknown float expression:"+text) 124 | 125 | 126 | # Visit a parse tree produced by AgtypeParser#TrueBoolean. 127 | def visitTrueBoolean(self, ctx:AgtypeParser.TrueBooleanContext): 128 | return True 129 | 130 | 131 | # Visit a parse tree produced by AgtypeParser#FalseBoolean. 132 | def visitFalseBoolean(self, ctx:AgtypeParser.FalseBooleanContext): 133 | return False 134 | 135 | 136 | # Visit a parse tree produced by AgtypeParser#NullValue. 137 | def visitNullValue(self, ctx:AgtypeParser.NullValueContext): 138 | return None 139 | 140 | 141 | # Visit a parse tree produced by AgtypeParser#obj. 142 | def visitObj(self, ctx:AgtypeParser.ObjContext): 143 | obj = dict() 144 | for c in ctx.getChildren(): 145 | if isinstance(c, AgtypeParser.PairContext): 146 | namVal = self.visitPair(c) 147 | name = namVal[0] 148 | valCtx = namVal[1] 149 | val = valCtx.accept(self) 150 | obj[name] = val 151 | return obj 152 | 153 | 154 | # Visit a parse tree produced by AgtypeParser#pair. 155 | def visitPair(self, ctx:AgtypeParser.PairContext): 156 | self.visitChildren(ctx) 157 | return (ctx.STRING().getText().strip('"') , ctx.agValue()) 158 | 159 | 160 | # Visit a parse tree produced by AgtypeParser#array. 161 | def visitArray(self, ctx:AgtypeParser.ArrayContext): 162 | li = list() 163 | for c in ctx.getChildren(): 164 | if not isinstance(c, TerminalNode): 165 | val = c.accept(self) 166 | li.append(val) 167 | return li 168 | 169 | def handleAnnotatedValue(self, anno:str, ctx:ParserRuleContext): 170 | if anno == "numeric": 171 | return Decimal(ctx.getText()) 172 | elif anno == "vertex": 173 | dict = ctx.accept(self) 174 | vid = dict["id"] 175 | vertex = None 176 | if self.vertexCache != None and vid in self.vertexCache : 177 | vertex = self.vertexCache[vid] 178 | else: 179 | vertex = Vertex() 180 | vertex.id = dict["id"] 181 | vertex.label = dict["label"] 182 | vertex.properties = dict["properties"] 183 | 184 | if self.vertexCache != None: 185 | self.vertexCache[vid] = vertex 186 | 187 | return vertex 188 | 189 | elif anno == "edge": 190 | edge = Edge() 191 | dict = ctx.accept(self) 192 | edge.id = dict["id"] 193 | edge.label = dict["label"] 194 | edge.end_id = dict["end_id"] 195 | edge.start_id = dict["start_id"] 196 | edge.properties = dict["properties"] 197 | 198 | return edge 199 | 200 | elif anno == "path": 201 | arr = ctx.accept(self) 202 | path = Path(arr) 203 | 204 | return path 205 | 206 | return ctx.accept(self) 207 | -------------------------------------------------------------------------------- /age/age.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | import re 16 | import psycopg2 17 | from psycopg2 import errors 18 | from psycopg2 import extensions as ext 19 | from .exceptions import * 20 | from .builder import ResultHandler , parseAgeValue, newResultHandler 21 | 22 | 23 | _EXCEPTION_NoConnection = NoConnection() 24 | _EXCEPTION_GraphNotSet = GraphNotSet() 25 | 26 | WHITESPACE = re.compile('\s') 27 | 28 | def setUpAge(conn:ext.connection, graphName:str): 29 | with conn.cursor() as cursor: 30 | cursor.execute("LOAD 'age';") 31 | cursor.execute("SET search_path = ag_catalog, '$user', public;") 32 | 33 | cursor.execute("SELECT typelem FROM pg_type WHERE typname='_agtype'") 34 | oid = cursor.fetchone()[0] 35 | if oid == None : 36 | raise AgeNotSet() 37 | 38 | AGETYPE = ext.new_type((oid,), 'AGETYPE', parseAgeValue) 39 | ext.register_type(AGETYPE) 40 | # ext.register_adapter(Path, marshalAgtValue) 41 | 42 | # Check graph exists 43 | if graphName != None: 44 | checkGraphCreated(conn, graphName) 45 | 46 | # Create the graph, if it does not exist 47 | def checkGraphCreated(conn:ext.connection, graphName:str): 48 | with conn.cursor() as cursor: 49 | cursor.execute("SELECT count(*) FROM ag_graph WHERE name=%s", (graphName,)) 50 | if cursor.fetchone()[0] == 0: 51 | cursor.execute("SELECT create_graph(%s);", (graphName,)) 52 | conn.commit() 53 | 54 | 55 | def deleteGraph(conn:ext.connection, graphName:str): 56 | with conn.cursor() as cursor: 57 | cursor.execute("SELECT drop_graph(%s, true);", (graphName,)) 58 | conn.commit() 59 | 60 | 61 | def buildCypher(graphName:str, cypherStmt:str, columns:list) ->str: 62 | if graphName == None: 63 | raise _EXCEPTION_GraphNotSet 64 | 65 | columnExp=[] 66 | if columns != None and len(columns) > 0: 67 | for col in columns: 68 | if col.strip() == '': 69 | continue 70 | elif WHITESPACE.search(col) != None: 71 | columnExp.append(col) 72 | else: 73 | columnExp.append(col + " agtype") 74 | else: 75 | columnExp.append('v agtype') 76 | 77 | stmtArr = [] 78 | stmtArr.append("SELECT * from cypher('") 79 | stmtArr.append(graphName) 80 | stmtArr.append("', $$ ") 81 | stmtArr.append(cypherStmt) 82 | stmtArr.append(" $$) as (") 83 | stmtArr.append(','.join(columnExp)) 84 | stmtArr.append(");") 85 | return "".join(stmtArr) 86 | 87 | def execSql(conn:ext.connection, stmt:str, commit:bool=False, params:tuple=None) -> ext.cursor : 88 | if conn == None or conn.closed: 89 | raise _EXCEPTION_NoConnection 90 | 91 | cursor = conn.cursor() 92 | try: 93 | cursor.execute(stmt, params) 94 | if commit: 95 | conn.commit() 96 | 97 | return cursor 98 | except SyntaxError as cause: 99 | conn.rollback() 100 | raise cause 101 | except Exception as cause: 102 | conn.rollback() 103 | raise SqlExcutionError("Excution ERR[" + str(cause) +"](" + stmt +")", cause) 104 | 105 | 106 | def querySql(conn:ext.connection, stmt:str, params:tuple=None) -> ext.cursor : 107 | return execSql(conn, stmt, False, params) 108 | 109 | # Execute cypher statement and return cursor. 110 | # If cypher statement changes data (create, set, remove), 111 | # You must commit session(ag.commit()) 112 | # (Otherwise the execution cannot make any effect.) 113 | def execCypher(conn:ext.connection, graphName:str, cypherStmt:str, cols:list=None, params:tuple=None) -> ext.cursor : 114 | if conn == None or conn.closed: 115 | raise _EXCEPTION_NoConnection 116 | 117 | stmt = buildCypher(graphName, cypherStmt, cols) 118 | 119 | cursor = conn.cursor() 120 | try: 121 | cursor.execute(stmt, params) 122 | return cursor 123 | except SyntaxError as cause: 124 | conn.rollback() 125 | raise cause 126 | except Exception as cause: 127 | conn.rollback() 128 | raise SqlExcutionError("Excution ERR[" + str(cause) +"](" + stmt +")", cause) 129 | 130 | 131 | def cypher(cursor:ext.cursor, graphName:str, cypherStmt:str, cols:list=None, params:tuple=None) -> ext.cursor : 132 | stmt = buildCypher(graphName, cypherStmt, cols) 133 | cursor.execute(stmt, params) 134 | 135 | 136 | # def execCypherWithReturn(conn:ext.connection, graphName:str, cypherStmt:str, columns:list=None , params:tuple=None) -> ext.cursor : 137 | # stmt = buildCypher(graphName, cypherStmt, columns) 138 | # return execSql(conn, stmt, False, params) 139 | 140 | # def queryCypher(conn:ext.connection, graphName:str, cypherStmt:str, columns:list=None , params:tuple=None) -> ext.cursor : 141 | # return execCypherWithReturn(conn, graphName, cypherStmt, columns, params) 142 | 143 | 144 | class Age: 145 | def __init__(self): 146 | self.connection = None # psycopg2 connection] 147 | self.graphName = None 148 | 149 | # Connect to PostgreSQL Server and establish session and type extension environment. 150 | def connect(self, graph:str=None, dsn:str=None, connection_factory=None, cursor_factory=None, **kwargs): 151 | conn = psycopg2.connect(dsn, connection_factory, cursor_factory, **kwargs) 152 | setUpAge(conn, graph) 153 | self.connection = conn 154 | self.graphName = graph 155 | return self 156 | 157 | def connect_new(self, graph, conn): 158 | setUpAge(conn, graph) 159 | self.connection = conn 160 | self.graphName = graph 161 | 162 | def close(self): 163 | self.connection.close() 164 | 165 | def setGraph(self, graph:str): 166 | checkGraphCreated(self.connection, graph) 167 | self.graphName = graph 168 | return self 169 | 170 | def commit(self): 171 | self.connection.commit() 172 | 173 | def rollback(self): 174 | self.connection.rollback() 175 | 176 | def execCypher(self, cypherStmt:str, cols:list=None, params:tuple=None) -> ext.cursor : 177 | return execCypher(self.connection, self.graphName, cypherStmt, cols=cols, params=params) 178 | 179 | def cypher(self, cursor:ext.cursor, cypherStmt:str, cols:list=None, params:tuple=None) -> ext.cursor : 180 | return cypher(cursor, self.graphName, cypherStmt, cols=cols, params=params) 181 | 182 | # def execSql(self, stmt:str, commit:bool=False, params:tuple=None) -> ext.cursor : 183 | # return execSql(self.connection, stmt, commit, params) 184 | 185 | 186 | # def execCypher(self, cypherStmt:str, commit:bool=False, params:tuple=None) -> ext.cursor : 187 | # return execCypher(self.connection, self.graphName, cypherStmt, commit, params) 188 | 189 | # def execCypherWithReturn(self, cypherStmt:str, columns:list=None , params:tuple=None) -> ext.cursor : 190 | # return execCypherWithReturn(self.connection, self.graphName, cypherStmt, columns, params) 191 | 192 | # def queryCypher(self, cypherStmt:str, columns:list=None , params:tuple=None) -> ext.cursor : 193 | # return queryCypher(self.connection, self.graphName, cypherStmt, columns, params) 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /samples/apache-age-basic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2e236ac7-6f78-4a59-bed7-45f593d060c2", 6 | "metadata": {}, 7 | "source": [ 8 | "# Basic Samples : Agtype mapper for Psycopg2 driver\n", 9 | "\n", 10 | "You can make transactions and queries for PostgreSQL with Psycopg2.\n", 11 | "\n", 12 | "This module enable to mapping agtype to python class(Path, Vertex, Edge)\n", 13 | "\n", 14 | "## Connect to PostgreSQL and agType setting" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "98a5863c-1e79-438e-81d4-d1f5354a1bdb", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import psycopg2 \n", 25 | "import age\n", 26 | "\n", 27 | "GRAPH_NAME = \"test_graph\"\n", 28 | "conn = psycopg2.connect(host=\"172.17.0.2\", port=\"5432\", dbname=\"postgres\", user=\"postgres\", password=\"agens\")\n", 29 | "\n", 30 | "age.setUpAge(conn, GRAPH_NAME)\n" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "id": "ebcf65a5-de7c-4224-aacc-2695c9e5f8d5", 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "with conn.cursor() as cursor:\n", 41 | " try :\n", 42 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ CREATE (n:Person {name: 'Joe', title: 'Developer'}) $$) as (v agtype); \"\"\", (GRAPH_NAME,) )\n", 43 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ CREATE (n:Person {name: 'Smith', title: 'Developer'}) $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 44 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 45 | " CREATE (n:Person {name: 'Tom', title: 'Manager'}) \n", 46 | " RETURN n\n", 47 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 48 | " for row in cursor:\n", 49 | " print(\"CREATED::\", row[0])\n", 50 | " \n", 51 | " \n", 52 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 53 | " MATCH (a:Person {name: 'Joe'}), (b:Person {name: 'Smith'}) CREATE (a)-[r:workWith {weight: 5}]->(b)\n", 54 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 55 | " \n", 56 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 57 | " MATCH (a:Person {name: 'Smith'}), (b:Person {name: 'Tom'}) CREATE (a)-[r:workWith {weight: 3}]->(b)\n", 58 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 59 | " \n", 60 | " # When data inserted or updated, You must commit.\n", 61 | " conn.commit()\n", 62 | " except Exception as ex:\n", 63 | " print(type(ex), ex)\n", 64 | " # if exception occurs, you must rollback all transaction. \n", 65 | " conn.rollback()\n", 66 | "\n", 67 | "with conn.cursor() as cursor:\n", 68 | " try:\n", 69 | " print(\"------- [Select Vertices] --------\")\n", 70 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ MATCH (n) RETURN n $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 71 | " for row in cursor:\n", 72 | " vertex = row[0]\n", 73 | " print(vertex.id, vertex.label, vertex[\"name\"], vertex[\"title\"])\n", 74 | " print(\"-->\", vertex)\n", 75 | " \n", 76 | " print(type(cursor))\n", 77 | " print(\"------- [Select Paths] --------\")\n", 78 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ MATCH p=()-[]->() RETURN p LIMIT 10 $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 79 | " for row in cursor:\n", 80 | " path = row[0]\n", 81 | " v1 = path[0]\n", 82 | " e1 = path[1]\n", 83 | " v2 = path[2]\n", 84 | " print(v1.gtype , v1[\"name\"], e1.gtype , e1.label, e1[\"weight\"], v2.gtype , v2[\"name\"])\n", 85 | " print(\"-->\", path)\n", 86 | " except Exception as ex:\n", 87 | " print(type(ex), ex)\n", 88 | " # if exception occurs, you must rollback even though just retrieving.\n", 89 | " conn.rollback()" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "f45f7c7d-2256-4aea-92f6-e0ad71017feb", 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "with conn.cursor() as cursor:\n", 100 | " try:\n", 101 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 102 | " MATCH p=(a)-[b]->(c) RETURN a.name, label(b), c.name \n", 103 | " $$) as (a agtype, b agtype, c agtype); \"\"\", (GRAPH_NAME,))\n", 104 | " for row in cursor:\n", 105 | " print(row[0], row[1], row[2])\n", 106 | " print(\"-->\", row)\n", 107 | " except Exception as ex:\n", 108 | " print(ex)\n", 109 | " conn.rollback()" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "id": "c40b9076-d45e-43e6-85ae-296ba68a3031", 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "with conn.cursor() as cursor:\n", 120 | " try :\n", 121 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 122 | " CREATE (n:Person {name: 'Jack', title: 'Developer', score:-6.45161290322581e+46}) \n", 123 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,) )\n", 124 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 125 | " CREATE (n:Person {name: 'John', title: 'Developer'}) \n", 126 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 127 | "\n", 128 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 129 | " MATCH (a:Person {name: 'Jack'}), (b:Person {name: 'John'}) \n", 130 | " CREATE (a)-[r:workWith {weight: 2}]->(b)\n", 131 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 132 | " \n", 133 | " # When data inserted or updated, You must commit \n", 134 | " conn.commit()\n", 135 | " except Exception as ex:\n", 136 | " print(ex)\n", 137 | " conn.rollback()\n", 138 | "\n", 139 | "with conn.cursor() as cursor:\n", 140 | " try :\n", 141 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 142 | " MATCH p=(a )-[b]->(c) RETURN a , b, c \n", 143 | " $$) as (ta agtype, tb agtype, tc agtype); \"\"\", (GRAPH_NAME,))\n", 144 | " \n", 145 | " for row in cursor:\n", 146 | " print(row[0][\"name\"], row[1].properties, row[2][\"name\"])\n", 147 | " \n", 148 | " except Exception as ex:\n", 149 | " print(ex)\n", 150 | " conn.rollback()\n" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "id": "29ffe1b7-86df-446a-9df0-635be25a9eea", 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "with conn.cursor() as cursor:\n", 161 | " try:\n", 162 | " cursor.execute(\"\"\"SELECT * from cypher(%s, $$ \n", 163 | " MATCH p=(a)-[b]->(c) RETURN p \n", 164 | " $$) as (v agtype); \"\"\", (GRAPH_NAME,))\n", 165 | " for row in cursor:\n", 166 | " path = row[0]\n", 167 | " print(path[0][\"name\"], path[1].id, path[1].properties, path[2][\"name\"])\n", 168 | " except Exception as ex:\n", 169 | " print(ex)\n", 170 | " conn.rollback()" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "id": "428e6ddf-3958-49ff-af73-809b9a1ce42b", 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "age.deleteGraph(conn, GRAPH_NAME)\n", 181 | "conn.close()" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "id": "a4819e39-9f37-4dd5-bdbd-337b6d289158", 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "\n", 192 | " " 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "id": "02041ef2-9761-4eb3-b270-ded23e1caa6d", 199 | "metadata": {}, 200 | "outputs": [], 201 | "source": [] 202 | } 203 | ], 204 | "metadata": { 205 | "kernelspec": { 206 | "display_name": "Python 3", 207 | "language": "python", 208 | "name": "python3" 209 | }, 210 | "language_info": { 211 | "codemirror_mode": { 212 | "name": "ipython", 213 | "version": 3 214 | }, 215 | "file_extension": ".py", 216 | "mimetype": "text/x-python", 217 | "name": "python", 218 | "nbconvert_exporter": "python", 219 | "pygments_lexer": "ipython3", 220 | "version": "3.9.2" 221 | } 222 | }, 223 | "nbformat": 4, 224 | "nbformat_minor": 5 225 | } 226 | -------------------------------------------------------------------------------- /age/models.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | import json 16 | from io import StringIO 17 | 18 | 19 | TP_NONE = 0 20 | TP_VERTEX = 1 21 | TP_EDGE = 2 22 | TP_PATH = 3 23 | 24 | 25 | class Graph(): 26 | def __init__(self, stmt=None) -> None: 27 | self.statement = stmt 28 | self.rows = list() 29 | self.vertices = dict() 30 | 31 | def __iter__(self): 32 | return self.rows.__iter__() 33 | 34 | def __len__(self): 35 | return self.rows.__len__() 36 | 37 | def __getitem__(self,index): 38 | return self.rows[index] 39 | 40 | def size(self): 41 | return self.rows.__len__() 42 | 43 | def append(self, agObj): 44 | self.rows.append(agObj) 45 | 46 | def getVertices(self): 47 | return self.vertices 48 | 49 | def getVertex(self, id): 50 | if id in self.vertices: 51 | return self.vertices[id] 52 | else: 53 | return None 54 | 55 | class AGObj: 56 | @property 57 | def gtype(self): 58 | return TP_NONE 59 | 60 | 61 | class Path(AGObj): 62 | entities = [] 63 | def __init__(self, entities=None) -> None: 64 | self.entities = entities 65 | 66 | @property 67 | def _nodes(self): 68 | return [each_node for each_node in self.entities if each_node.gtype == TP_VERTEX] 69 | 70 | @property 71 | def _edges(self): 72 | return [each_edge for each_edge in self.entities if each_edge.gtype == TP_EDGE] 73 | 74 | @property 75 | def gtype(self): 76 | return TP_PATH 77 | 78 | def __iter__(self): 79 | return self.entities.__iter__() 80 | 81 | def __len__(self): 82 | return self.entities.__len__() 83 | 84 | def __getitem__(self,index): 85 | return self.entities[index] 86 | 87 | def size(self): 88 | return self.entities.__len__() 89 | 90 | def append(self, agObj:AGObj ): 91 | self.entities.append(agObj) 92 | 93 | def __str__(self) -> str: 94 | return self.toString() 95 | 96 | def __repr__(self) -> str: 97 | return self.toString() 98 | 99 | def toString(self) -> str: 100 | buf = StringIO() 101 | buf.write("[") 102 | max = len(self.entities) 103 | idx = 0 104 | while idx < max: 105 | if idx > 0: 106 | buf.write(",") 107 | self.entities[idx]._toString(buf) 108 | idx += 1 109 | buf.write("]::PATH") 110 | 111 | return buf.getvalue() 112 | 113 | def toJson(self) -> str: 114 | buf = StringIO() 115 | buf.write("{\"gtype\": \"path\", \"elements\": [") 116 | 117 | max = len(self.entities) 118 | idx = 0 119 | while idx < max: 120 | if idx > 0: 121 | buf.write(",") 122 | self.entities[idx]._toJson(buf) 123 | idx += 1 124 | buf.write("]}") 125 | 126 | return buf.getvalue() 127 | 128 | 129 | 130 | 131 | class Vertex(AGObj): 132 | def __init__(self, id=None, label=None, properties=None) -> None: 133 | self.id = id 134 | self.label = label 135 | self.properties = properties 136 | 137 | @property 138 | def gtype(self): 139 | return TP_VERTEX 140 | 141 | def __setitem__(self,name, value): 142 | self.properties[name]=value 143 | 144 | def __getitem__(self,name): 145 | if name in self.properties: 146 | return self.properties[name] 147 | else: 148 | return None 149 | 150 | def __str__(self) -> str: 151 | return self.toString() 152 | 153 | def __repr__(self) -> str: 154 | return self.toString() 155 | 156 | def toString(self) -> str: 157 | return nodeToString(self) 158 | 159 | def _toString(self, buf): 160 | _nodeToString(self, buf) 161 | 162 | def toJson(self) -> str: 163 | return nodeToJson(self) 164 | 165 | def _toJson(self, buf): 166 | _nodeToJson(self, buf) 167 | 168 | 169 | class Edge(AGObj): 170 | def __init__(self, id=None, label=None, properties=None) -> None: 171 | self.id = id 172 | self.label = label 173 | self.start_id = None 174 | self.end_id = None 175 | self.properties = properties 176 | 177 | @property 178 | def gtype(self): 179 | return TP_EDGE 180 | 181 | def __setitem__(self,name, value): 182 | self.properties[name]=value 183 | 184 | def __getitem__(self,name): 185 | if name in self.properties: 186 | return self.properties[name] 187 | else: 188 | return None 189 | 190 | def __str__(self) -> str: 191 | return self.toString() 192 | 193 | def __repr__(self) -> str: 194 | return self.toString() 195 | 196 | def extraStrFormat(node, buf): 197 | if node.start_id != None: 198 | buf.write(", start_id:") 199 | buf.write(str(node.start_id)) 200 | 201 | if node.end_id != None: 202 | buf.write(", end_id:") 203 | buf.write(str(node.end_id)) 204 | 205 | 206 | def toString(self) -> str: 207 | return nodeToString(self, Edge.extraStrFormat) 208 | 209 | def _toString(self, buf): 210 | _nodeToString(self, buf, Edge.extraStrFormat) 211 | 212 | def extraJsonFormat(node, buf): 213 | if node.start_id != None: 214 | buf.write(", \"start_id\": \"") 215 | buf.write(str(node.start_id)) 216 | buf.write("\"") 217 | 218 | if node.end_id != None: 219 | buf.write(", \"end_id\": \"") 220 | buf.write(str(node.end_id)) 221 | buf.write("\"") 222 | 223 | def toJson(self) -> str: 224 | return nodeToJson(self, Edge.extraJsonFormat) 225 | 226 | def _toJson(self, buf): 227 | _nodeToJson(self, buf, Edge.extraJsonFormat) 228 | 229 | 230 | def nodeToString(node, extraFormatter=None): 231 | buf = StringIO() 232 | _nodeToString(node,buf,extraFormatter=extraFormatter) 233 | return buf.getvalue() 234 | 235 | 236 | def _nodeToString(node, buf, extraFormatter=None): 237 | buf.write("{") 238 | if node.label != None: 239 | buf.write("label:") 240 | buf.write(node.label) 241 | 242 | if node.id != None: 243 | buf.write(", id:") 244 | buf.write(str(node.id)) 245 | 246 | if node.properties != None: 247 | buf.write(", properties:{") 248 | for k,v in node.properties.items(): 249 | buf.write(k) 250 | buf.write(": ") 251 | buf.write(str(v)) 252 | buf.write(", ") 253 | buf.write("}") 254 | 255 | if extraFormatter != None: 256 | extraFormatter(node, buf) 257 | 258 | if node.gtype == TP_VERTEX: 259 | buf.write("}::VERTEX") 260 | if node.gtype == TP_EDGE: 261 | buf.write("}::EDGE") 262 | 263 | 264 | def nodeToJson(node, extraFormatter=None): 265 | buf = StringIO() 266 | _nodeToJson(node, buf, extraFormatter=extraFormatter) 267 | return buf.getvalue() 268 | 269 | 270 | def _nodeToJson(node, buf, extraFormatter=None): 271 | buf.write("{\"gtype\": ") 272 | if node.gtype == TP_VERTEX: 273 | buf.write("\"vertex\", ") 274 | if node.gtype == TP_EDGE: 275 | buf.write("\"edge\", ") 276 | 277 | if node.label != None: 278 | buf.write("\"label\":\"") 279 | buf.write(node.label) 280 | buf.write("\"") 281 | 282 | if node.id != None: 283 | buf.write(", \"id\":") 284 | buf.write(str(node.id)) 285 | 286 | if extraFormatter != None: 287 | extraFormatter(node, buf) 288 | 289 | if node.properties != None: 290 | buf.write(", \"properties\":{") 291 | for k,v in node.properties.items(): 292 | buf.write("\"") 293 | buf.write(k) 294 | buf.write("\": \"") 295 | buf.write(str(v)) 296 | buf.write("\", ") 297 | buf.write("}") 298 | buf.write("}") 299 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test_age_py.py: -------------------------------------------------------------------------------- 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 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, 10 | # software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 | # KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from age.models import Vertex 17 | import unittest 18 | import decimal 19 | import age 20 | 21 | DSN = "host=localhost port=5432 dbname=postgres user=postgres password=agens" 22 | TEST_HOST = "localhost" 23 | TEST_PORT = 5432 24 | TEST_DB = "postgres" 25 | TEST_USER = "postgres" 26 | TEST_PASSWORD = "agens" 27 | TEST_GRAPH_NAME = "test_graph" 28 | 29 | class TestAgeBasic(unittest.TestCase): 30 | ag = None 31 | def setUp(self): 32 | print("Connecting to Test Graph.....") 33 | self.ag = age.connect(graph=TEST_GRAPH_NAME, host=TEST_HOST, port=TEST_PORT, dbname=TEST_DB, user=TEST_USER, password=TEST_PASSWORD) 34 | 35 | 36 | def tearDown(self): 37 | # Clear test data 38 | print("Deleting Test Graph.....") 39 | age.deleteGraph(self.ag.connection, self.ag.graphName) 40 | self.ag.close() 41 | 42 | def testExec(self): 43 | ag = self.ag 44 | # Create and Return single column 45 | cursor = ag.execCypher("CREATE (n:Person {name: %s, title: 'Developer'}) RETURN n", params=('Andy',)) 46 | for row in cursor: 47 | print(Vertex, type(row[0])) 48 | 49 | 50 | # Create and Return multi columns 51 | cursor = ag.execCypher("CREATE (n:Person {name: %s, title: %s}) RETURN id(n), n.name", cols=['id','name'], params=('Jack','Manager')) 52 | row = cursor.fetchone() 53 | print(row[0], row[1]) 54 | self.assertEqual(int, type(row[0])) 55 | ag.commit() 56 | 57 | 58 | 59 | def testQuery(self): 60 | ag = self.ag 61 | ag.execCypher("CREATE (n:Person {name: %s}) ", params=('Jack',)) 62 | ag.execCypher("CREATE (n:Person {name: %s}) ", params=('Andy',)) 63 | ag.execCypher("CREATE (n:Person {name: %s}) ", params=('Smith',)) 64 | ag.execCypher("MATCH (a:Person), (b:Person) WHERE a.name = 'Andy' AND b.name = 'Jack' CREATE (a)-[r:workWith {weight: 3}]->(b)") 65 | ag.execCypher("""MATCH (a:Person), (b:Person) 66 | WHERE a.name = %s AND b.name = %s 67 | CREATE p=((a)-[r:workWith]->(b)) """, params=('Jack', 'Smith',)) 68 | 69 | ag.commit() 70 | 71 | cursor = ag.execCypher("MATCH p=()-[:workWith]-() RETURN p") 72 | for row in cursor: 73 | path = row[0] 74 | print("START:", path[0]) 75 | print("EDGE:", path[1]) 76 | print("END:", path[2]) 77 | 78 | cursor = ag.execCypher("MATCH p=(a)-[b]-(c) WHERE b.weight>2 RETURN a,label(b), b.weight, c", cols=["a","bl","bw", "c"], params=(2,)) 79 | for row in cursor: 80 | start = row[0] 81 | edgel = row[1] 82 | edgew = row[2] 83 | end = row[3] 84 | print(start["name"] , edgel, edgew, end["name"]) 85 | 86 | 87 | def testChangeData(self): 88 | ag = self.ag 89 | # Create Vertices 90 | # Commit automatically 91 | ag.execCypher("CREATE (n:Person {name: 'Joe'})") 92 | 93 | cursor = ag.execCypher("CREATE (n:Person {name: %s, title: 'Developer'}) RETURN n", params=('Smith',)) 94 | row = cursor.fetchone() 95 | print("CREATED: ", row[0]) 96 | 97 | # You must commit explicitly 98 | ag.commit() 99 | 100 | cursor = ag.execCypher("MATCH (n:Person {name: %s}) SET n.title=%s RETURN n", params=('Smith','Manager',)) 101 | row = cursor.fetchone() 102 | vertex = row[0] 103 | title1 = vertex["title"] 104 | print("SET title: ", title1) 105 | 106 | ag.commit() 107 | 108 | cursor = ag.execCypher("MATCH (p:Person {name: 'Smith'}) RETURN p.title") 109 | row = cursor.fetchone() 110 | title2 = row[0] 111 | 112 | self.assertEqual(title1, title2) 113 | 114 | cursor = ag.execCypher("MATCH (n:Person {name: %s}) SET n.bigNum=-6.45161e+46::numeric RETURN n", params=('Smith',)) 115 | row = cursor.fetchone() 116 | vertex = row[0] 117 | for row in cursor: 118 | print("SET bigNum: ", vertex) 119 | 120 | bigNum1 = vertex["bigNum"] 121 | 122 | self.assertEqual(decimal.Decimal("-6.45161e+46"), bigNum1) 123 | ag.commit() 124 | 125 | 126 | cursor = ag.execCypher("MATCH (p:Person {name: 'Smith'}) RETURN p.bigNum") 127 | row = cursor.fetchone() 128 | bigNum2 = row[0] 129 | 130 | self.assertEqual(bigNum1, bigNum2) 131 | 132 | 133 | cursor = ag.execCypher("MATCH (n:Person {name: %s}) REMOVE n.title RETURN n", params=('Smith',)) 134 | for row in cursor: 135 | print("REMOVE Prop title: ", row[0]) 136 | 137 | # You must commit explicitly 138 | ag.commit() 139 | 140 | 141 | def testCypher(self): 142 | ag = self.ag 143 | 144 | with ag.connection.cursor() as cursor: 145 | try : 146 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Jone',)) 147 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Jack',)) 148 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Andy',)) 149 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Smith',)) 150 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Tom',)) 151 | 152 | # You must commit explicitly 153 | ag.commit() 154 | except Exception as ex: 155 | print(ex) 156 | ag.rollback() 157 | 158 | with ag.connection.cursor() as cursor: 159 | try :# Create Edges 160 | ag.cypher(cursor,"MATCH (a:Person), (b:Person) WHERE a.name = 'Joe' AND b.name = 'Smith' CREATE (a)-[r:workWith {weight: 3}]->(b)") 161 | ag.cypher(cursor,"MATCH (a:Person), (b:Person) WHERE a.name = 'Andy' AND b.name = 'Tom' CREATE (a)-[r:workWith {weight: 1}]->(b)") 162 | ag.cypher(cursor,"MATCH (a:Person {name: 'Jack'}), (b:Person {name: 'Andy'}) CREATE (a)-[r:workWith {weight: 5}]->(b)") 163 | 164 | # You must commit explicitly 165 | ag.commit() 166 | except Exception as ex: 167 | print(ex) 168 | ag.rollback() 169 | 170 | 171 | # With Params 172 | cursor = ag.execCypher("""MATCH (a:Person), (b:Person) 173 | WHERE a.name = %s AND b.name = %s 174 | CREATE p=((a)-[r:workWith]->(b)) RETURN p""", 175 | params=('Andy', 'Smith',)) 176 | 177 | for row in cursor: 178 | print(row[0]) 179 | 180 | cursor = ag.execCypher("""MATCH (a:Person {name: 'Joe'}), (b:Person {name: 'Jack'}) 181 | CREATE p=((a)-[r:workWith {weight: 5}]->(b)) 182 | RETURN p """) 183 | 184 | for row in cursor: 185 | print(row[0]) 186 | 187 | 188 | 189 | def testMultipleEdges(self): 190 | ag = self.ag 191 | with ag.connection.cursor() as cursor: 192 | try : 193 | ag.cypher(cursor, "CREATE (n:Country {name: %s}) ", params=('USA',)) 194 | ag.cypher(cursor, "CREATE (n:Country {name: %s}) ", params=('France',)) 195 | ag.cypher(cursor, "CREATE (n:Country {name: %s}) ", params=('Korea',)) 196 | ag.cypher(cursor, "CREATE (n:Country {name: %s}) ", params=('Russia',)) 197 | 198 | # You must commit explicitly after all executions. 199 | ag.connection.commit() 200 | except Exception as ex: 201 | ag.rollback() 202 | raise ex 203 | 204 | with ag.connection.cursor() as cursor: 205 | try :# Create Edges 206 | ag.cypher(cursor,"MATCH (a:Country), (b:Country) WHERE a.name = 'USA' AND b.name = 'France' CREATE (a)-[r:distance {unit:'miles', value: 4760}]->(b)") 207 | ag.cypher(cursor,"MATCH (a:Country), (b:Country) WHERE a.name = 'France' AND b.name = 'Korea' CREATE (a)-[r:distance {unit: 'km', value: 9228}]->(b)") 208 | ag.cypher(cursor,"MATCH (a:Country {name: 'Korea'}), (b:Country {name: 'Russia'}) CREATE (a)-[r:distance {unit:'km', value: 3078}]->(b)") 209 | 210 | # You must commit explicitly 211 | ag.connection.commit() 212 | except Exception as ex: 213 | ag.rollback() 214 | raise ex 215 | 216 | 217 | cursor = ag.execCypher("""MATCH p=(:Country {name:"USA"})-[:distance]-(:Country)-[:distance]-(:Country) 218 | RETURN p""") 219 | 220 | count = 0 221 | for row in cursor: 222 | path = row[0] 223 | indent = "" 224 | for e in path: 225 | if e.gtype == age.TP_VERTEX: 226 | print(indent, e.label, e["name"]) 227 | elif e.gtype == age.TP_EDGE: 228 | print(indent, e.label, e["value"], e["unit"]) 229 | else: 230 | print(indent, "Unknown element.", e) 231 | 232 | count += 1 233 | indent += " >" 234 | 235 | self.assertEqual(5,count) 236 | 237 | def testCollect(self): 238 | ag = self.ag 239 | 240 | with ag.connection.cursor() as cursor: 241 | try : 242 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Joe',)) 243 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Jack',)) 244 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Andy',)) 245 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Smith',)) 246 | ag.cypher(cursor, "CREATE (n:Person {name: %s}) ", params=('Tom',)) 247 | 248 | # You must commit explicitly 249 | ag.commit() 250 | except Exception as ex: 251 | print(ex) 252 | ag.rollback() 253 | 254 | with ag.connection.cursor() as cursor: 255 | try :# Create Edges 256 | ag.cypher(cursor,"MATCH (a:Person), (b:Person) WHERE a.name = 'Joe' AND b.name = 'Smith' CREATE (a)-[r:workWith {weight: 3}]->(b)") 257 | ag.cypher(cursor,"MATCH (a:Person), (b:Person) WHERE a.name = 'Joe' AND b.name = 'Tom' CREATE (a)-[r:workWith {weight: 1}]->(b)") 258 | ag.cypher(cursor,"MATCH (a:Person {name: 'Joe'}), (b:Person {name: 'Andy'}) CREATE (a)-[r:workWith {weight: 5}]->(b)") 259 | 260 | # You must commit explicitly 261 | ag.commit() 262 | except Exception as ex: 263 | print(ex) 264 | ag.rollback() 265 | 266 | print(" - COLLECT 1 --------") 267 | with ag.connection.cursor() as cursor: 268 | ag.cypher(cursor, "MATCH (a)-[:workWith]->(c) WITH a as V, COLLECT(c) as CV RETURN V.name, CV", cols=["V","CV"]) 269 | for row in cursor: 270 | nm = row[0] 271 | collected = row[1] 272 | print(nm, "workWith", [i["name"] for i in collected]) 273 | self.assertEqual(3,len(collected)) 274 | 275 | 276 | print(" - COLLECT 2 --------") 277 | for row in ag.execCypher("MATCH (a)-[:workWith]->(c) WITH a as V, COLLECT(c) as CV RETURN V.name, CV", cols=["V1","CV"]): 278 | nm = row[0] 279 | collected = row[1] 280 | print(nm, "workWith", [i["name"] for i in collected]) 281 | self.assertEqual(3,len(collected)) 282 | 283 | if __name__ == '__main__': 284 | unittest.main() -------------------------------------------------------------------------------- /samples/apache-age-note.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "722ec93e-b87c-43d0-9c54-61b30933d892", 6 | "metadata": {}, 7 | "source": [ 8 | "# AGE Samples \n", 9 | "\n", 10 | "## Prepare\n", 11 | "```\n", 12 | "import age\n", 13 | "```\n", 14 | "## Connect to PostgreSQL(with AGE extention)\n", 15 | "* Connect to PostgreSQL server \n", 16 | "* Load AGE and register agtype to db session (Psycopg2 driver)\n", 17 | "* Check graph exists and set graph. If not, age make that.\n", 18 | "\n", 19 | "```\n", 20 | "ag = age.connect(graph=\"(graph name}\", host=\"{host}\", port=\"{port}\", dbname=\"{dbname}\", user=\"{db username}\", password=\"{password}\")\n", 21 | "\n", 22 | "# or \n", 23 | "DSN = \"host={host} port={port} dbname={dbname} user={db username} password={password}\"\n", 24 | "ag = age.connect(graph=\"(graph name}\", dsn=DSN)\n", 25 | "\n", 26 | "# or Without Graph Name : you can make a new graph later.\n", 27 | "\n", 28 | "ag = age.connect(host=\"{host}\", port=\"{port}\", dbname=\"{dbname}\", user=\"{db username}\", password=\"{password}\")\n", 29 | "\n", 30 | "# And set graph - if you don't have one yet, setGraph make that.)\n", 31 | "ag.setGraph(\"{graph name}\")\n", 32 | "```" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "34eaaafe-d9dc-442f-8248-0824c46c7b20", 39 | "metadata": { 40 | "tags": [] 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "import age\n", 45 | "from age.gen.AgtypeParser import *\n", 46 | "\n", 47 | "GRAPH_NAME = \"test_graph\"\n", 48 | "DSN = \"host=172.17.0.2 port=5432 dbname=postgres user=postgres password=agens\"\n", 49 | "\n", 50 | "ag = age.connect(graph=GRAPH_NAME, dsn=DSN)\n", 51 | "ag.setGraph(GRAPH_NAME)\n", 52 | "\n", 53 | "cursor = ag.execCypher(\"MATCH (n) RETURN n\")\n", 54 | "for row in cursor:\n", 55 | " print(row[0])\n", 56 | " " 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "id": "96bbaf49-e774-4939-8fe9-f179ac9addc9", 62 | "metadata": {}, 63 | "source": [ 64 | "---\n", 65 | "# API\n", 66 | "\n", 67 | "### age.connect(graph:str=None, dsn:str=None, connection_factory=None, cursor_factory=None, **kwargs) -> Age\n", 68 | "> Connect PostgreSQL server \n", 69 | " Parameters : dsn={dsn} or \n", 70 | " host=\"{host}\", port=\"{port}\", dbname=\"{dbname}\", user=\"{db username}\", password=\"{password}\"\n", 71 | "\n", 72 | "### Age.commit() , Age.rollback()\n", 73 | "> If your statement change data, you must call 'Age.commit()' explicitly. Otherwise change will not make effect.\n", 74 | "> Or when execution error occurs, you must call 'Age.rollback()'\n", 75 | "\n", 76 | "### Age.close()\n", 77 | "> Closes connection to PostgreSQL.\n", 78 | "\n", 79 | "### Age.execCypher(cypherStmt:str, cols:list=None, params:tuple=None) -> psycopg2.extensions.cursor :\n", 80 | "> Execute cypher statements to query or change data (CREATE, SET, REMOVE) with or without result.\n", 81 | "> If your statement change data, you must call 'Age.commit()' explicitly. Otherwise change will not make effect.\n", 82 | " \n", 83 | "> If your execution returns no result or only one result, you don't have to set 'cols' argument.\n", 84 | "> But it returns many columns, you have to pass columns names(and types) to 'cols' argument.\n", 85 | "\n", 86 | "> cols : str list \\[ 'colName {type}', ... \\] : If column data type is not set, agtype is default.\n", 87 | " \n", 88 | "### Age.cypher(cursor:psycopg2.extensions.cursor, cypherStmt:str, cols:list=None, params:tuple=None) -> psycopg2.extensions.cursor :\n", 89 | "> If you want execute many statements (changing data statement maybe) with one transaction explicitly, you may use Age.cypher(...) function.\n", 90 | "\n", 91 | "> For creating cursor and mamage transaction, you usually use 'with' clause.\n", 92 | " \n", 93 | "> If your execution returns no result or only one result, you don't have to set 'cols' argument.\n", 94 | "> But it returns many columns, you have to pass columns names(and types) to 'cols' argument.\n", 95 | "\n", 96 | "> cols : str list \\[ 'colName {type}', ... \\] : If column data type is not set, agtype is default.\n", 97 | " " 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "id": "0f15bc37-4b19-4204-af93-757b07e7e9f9", 103 | "metadata": {}, 104 | "source": [ 105 | "---\n", 106 | "## Create & Change Vertices\n", 107 | "\n", 108 | "> If cypher statement changes data (create, set, remove), \n", 109 | " you must use execCypher(cypherStmt, commit, *args). \n", 110 | " \n", 111 | "> If **'commit'** argument is **True**: the cypherStmt make effect automatically, but cursor is closed after execution. So you cannot access the result. \n", 112 | " If **False** : you can access the result, but you must commit session(ag.commit()) explicitly.\n", 113 | " (Otherwise the execution cannot make any effect.)\n", 114 | "\n", 115 | "\n", 116 | "> execCypher(cypherStmt:str, commit:bool, *args) \n", 117 | "\n", 118 | "```\n", 119 | "cursor = ag.execCypher(\"CREATE(...)\", commit=False) # Cypher Create Statement\n", 120 | "...\n", 121 | "# check result in cursor\n", 122 | "...\n", 123 | "ag.commit() # commit explicitly\n", 124 | "```\n" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "id": "99cbc91e-55ae-4d2e-b81e-a655f88ec807", 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "# Create Vertices\n", 135 | "ag.execCypher(\"CREATE (n:Person {name: 'Joe'})\")\n", 136 | "ag.execCypher(\"CREATE (n:Person {name: 'Smith'})\")\n", 137 | " \n", 138 | "# Execution with one agtype result\n", 139 | "cursor = ag.execCypher(\"CREATE (n:Person {name: %s}) RETURN n\", params=('Jack',))\n", 140 | "for row in cursor:\n", 141 | " print(\"CREATED: \", row[0]) \n", 142 | " \n", 143 | "cursor = ag.execCypher(\"CREATE (n:Person {name: %s, title: 'Developer'}) RETURN id(n)\", params=('Andy',))\n", 144 | "for row in cursor:\n", 145 | " print(\"CREATED: \", row[0])\n", 146 | " \n", 147 | "\n", 148 | "# Execution with one result as SQL TYPE \n", 149 | "cursor = ag.execCypher(\"MATCH (n:Person {name: %s}) SET n.title=%s RETURN n.title\", cols=[\"a VARCHAR\"], params=('Smith','Manager',))\n", 150 | "for row in cursor:\n", 151 | " print(\"SET: \", row[0])\n", 152 | "\n", 153 | "\n", 154 | "# Execution with one result as SQL TYPE \n", 155 | "cursor = ag.execCypher(\"MATCH (n:Person {name: %s}) REMOVE n.title RETURN id(n)\", cols=[\"a BIGINT\"], params=('Smith',))\n", 156 | "for row in cursor:\n", 157 | " print(\"REMOVE Prop: \", row[0])\n", 158 | "\n", 159 | "# You must commit explicitly\n", 160 | "ag.commit()\n" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "id": "cf0f16c8-07d0-49b9-ba4f-3f9044cac9e7", 166 | "metadata": {}, 167 | "source": [ 168 | "---\n", 169 | "## Query Vertices\n", 170 | "\n", 171 | "> execCypher(cypherStmt:str, cols:list=None, params:tuple=None) \n", 172 | "\n", 173 | "### Single result column\n", 174 | "\n", 175 | "```\n", 176 | "cursor = ag.execCypher(\"MATCH (n:Person {name: %s) RETURN n\", params('Andy',))\n", 177 | "for row in cursor:\n", 178 | " vertex = row[0]\n", 179 | " print(vertex.id, vertex[\"name\"], vertex) # row has id, label, properties \n", 180 | "```\n", 181 | "\n", 182 | "### Multi result columns\n", 183 | "\n", 184 | "```\n", 185 | "cursor = ag.execCypher(\"MATCH (n:Person) RETURN label(n), n.name\", cols=['label VARCHAR', 'name'])\n", 186 | "for row in cursor:\n", 187 | " label = row[0]\n", 188 | " name = row[1]\n", 189 | " print(label, name) \n", 190 | "```\n", 191 | "\n", 192 | "\n", 193 | "### Vertex object has id, label attribute and __getitem__, __setitem__ for properties\n", 194 | "```\n", 195 | "vertex.id\n", 196 | "vertex.label\n", 197 | "vertex[\"property_name\"]\n", 198 | "```" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "id": "4cd66088-2c74-449e-88bc-76877779c86d", 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "\n", 209 | "# Query Vertices with parsed row cursor.\n", 210 | "print(\"-- Query Vertices --------------------\")\n", 211 | "cursor = ag.execCypher(\"MATCH (n:Person) RETURN n\")\n", 212 | "for row in cursor:\n", 213 | " vertex = row[0]\n", 214 | " print(vertex)\n", 215 | " print(vertex.id, vertex.label, vertex[\"name\"])\n", 216 | " print(\"-->\", vertex)\n", 217 | "\n", 218 | "# Query Vertices with with multi column\n", 219 | "print(\"-- Query Vertices with with multi columns. --------------------\")\n", 220 | "cursor = ag.execCypher(\"MATCH (n:Person) RETURN label(n), n.name\", cols=['label VARCHAR', 'name'])\n", 221 | "for row in cursor:\n", 222 | " label = row[0]\n", 223 | " name = row[1]\n", 224 | " print(label, name) \n" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "0a8606b5-8583-49f9-aa39-a1ac3e90d542", 230 | "metadata": {}, 231 | "source": [ 232 | "---\n", 233 | "## Create Relation \n", 234 | "\n", 235 | "> execCypher(cypherStmt:str, commit:bool, *args)\n", 236 | "\n", 237 | "\n", 238 | "```\n", 239 | "# Execute statement and handle results\n", 240 | "cursor = ag.execCypher(\"MATCH (a:Person), (b:Person) WHERE a.name = %s AND b.name = %s CREATE p=((a)-[r:workWith]->(b)) RETURN p\", False, ('Andy', 'Smith',))\n", 241 | "...\n", 242 | "# You can access the results in cursor\n", 243 | "...\n", 244 | "ag.commit() # commit\n", 245 | "```\n", 246 | "\n", 247 | "```\n", 248 | "# Auto commit\n", 249 | "ag.execCypher(\"MATCH (a:Person), (b:Person) WHERE a.name = 'Andy' AND b.name = 'Tom' CREATE (a)-[r:workWith]->(b)\", True)\n", 250 | "\n", 251 | "```\n" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "id": "0f904526-59d7-4025-9878-15e458bc5b56", 258 | "metadata": {}, 259 | "outputs": [], 260 | "source": [ 261 | "\n", 262 | "# Create Edges\n", 263 | "ag.execCypher(\"MATCH (a:Person), (b:Person) WHERE a.name = 'Joe' AND b.name = 'Smith' CREATE (a)-[r:workWith {weight: 3}]->(b)\")\n", 264 | "ag.execCypher(\"MATCH (a:Person), (b:Person) WHERE a.name = 'Andy' AND b.name = 'Tom' CREATE (a)-[r:workWith {weight: 1}]->(b)\")\n", 265 | "ag.execCypher(\"MATCH (a:Person {name: 'Jack'}), (b:Person {name: 'Andy'}) CREATE (a)-[r:workWith {weight: 5}]->(b)\")\n", 266 | "\n", 267 | "ag.commit()\n", 268 | "\n", 269 | "# With Params and Return\n", 270 | "cursor = ag.execCypher(\"\"\"MATCH (a:Person), (b:Person) \n", 271 | " WHERE a.name = %s AND b.name = %s \n", 272 | " CREATE p=((a)-[r:workWith]->(b)) \n", 273 | " RETURN p\"\"\", \n", 274 | " params=('Andy', 'Smith',))\n", 275 | "\n", 276 | "for row in cursor:\n", 277 | " print(row[0])\n", 278 | "\n", 279 | "ag.commit()\n", 280 | "\n", 281 | "# With many columns Return\n", 282 | "cursor = ag.execCypher(\"\"\"MATCH (a:Person {name: 'Joe'}), (b:Person {name: 'Jack'}) \n", 283 | " CREATE (a)-[r:workWith {weight: 5}]->(b) \n", 284 | " RETURN a, r, b \"\"\", cols=['a','r', 'b'])\n", 285 | "\n", 286 | "for row in cursor:\n", 287 | " print(\"(a)\", row[0], \": (r)\", row[1], \": (b)\", row[2])\n", 288 | " \n", 289 | "\n", 290 | "ag.commit()\n", 291 | "\n", 292 | " " 293 | ] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "id": "2615465d-9cff-4e67-9935-21344df4574c", 298 | "metadata": {}, 299 | "source": [ 300 | "---\n", 301 | "## Query Relations\n", 302 | "\n", 303 | "> With single column\n", 304 | "```\n", 305 | "cursor = ag.execCypher(\"MATCH p=()-[:workWith]-() RETURN p\")\n", 306 | "for row in cursor:\n", 307 | " path = row[0]\n", 308 | " print(path) \n", 309 | "```\n", 310 | "\n", 311 | "> With multi columns\n", 312 | "```\n", 313 | "cursor = ag.execCypher(\"MATCH p=(a)-[b]-(c) RETURN a,label(b),c\", cols=[\"a\",\"b VARCHAR\",\"c\"])\n", 314 | "for row in cursor:\n", 315 | " start = row[0]\n", 316 | " edge = row[1]\n", 317 | " end = row[2]\n", 318 | " print(start[\"name\"] , edge.label, end[\"name\"]) \n", 319 | "```\n", 320 | "\n", 321 | "\n", 322 | "### Edge object has id, label,start_id, end_id attribute and __getitem__, __setitem__ for properties\n", 323 | "```\n", 324 | "edge = path.rel\n", 325 | "edge.id\n", 326 | "edge.label\n", 327 | "edge.start_id\n", 328 | "edge.end_id\n", 329 | "edge[\"property_name\"]\n", 330 | "edge.properties\n", 331 | "```" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "id": "7673e270-4ea3-4878-961c-8fc97106e1bd", 338 | "metadata": {}, 339 | "outputs": [], 340 | "source": [ 341 | "cursor = ag.execCypher(\"MATCH p=()-[:workWith]-() RETURN p\")\n", 342 | "for row in cursor:\n", 343 | " path = row[0]\n", 344 | " print(\"START:\", path[0])\n", 345 | " print(\"EDGE:\", path[1])\n", 346 | " print(\"END:\", path[2]) \n", 347 | "\n", 348 | "print(\"-- [Query path with multi columns --------\")\n", 349 | "cursor = ag.execCypher(\"MATCH p=(a)-[b]-(c) WHERE b.weight>2 RETURN a,label(b), b.weight, c\", cols=[\"a\",\"bl\",\"bw\", \"c\"], params=(2,))\n", 350 | "for row in cursor:\n", 351 | " start = row[0]\n", 352 | " edgel = row[1]\n", 353 | " edgew = row[2]\n", 354 | " end = row[3]\n", 355 | " print(start[\"name\"] , edgel, edgew, end[\"name\"]) \n", 356 | "\n", 357 | "\n", 358 | " " 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "id": "ac4d3e0e-0101-40cd-bc9a-59386a1e4485", 364 | "metadata": {}, 365 | "source": [ 366 | "--- \n", 367 | "## Many executions in one transaction & Multiple Edges" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "id": "c8b36687-c842-4663-a7aa-084ae618301e", 374 | "metadata": {}, 375 | "outputs": [], 376 | "source": [ 377 | "with ag.connection.cursor() as cursor:\n", 378 | " try :\n", 379 | " ag.cypher(cursor, \"CREATE (n:Country {name: %s}) \", params=('USA',))\n", 380 | " ag.cypher(cursor, \"CREATE (n:Country {name: %s}) \", params=('France',))\n", 381 | " ag.cypher(cursor, \"CREATE (n:Country {name: %s}) \", params=('Korea',))\n", 382 | " ag.cypher(cursor, \"CREATE (n:Country {name: %s}) \", params=('Russia',))\n", 383 | "\n", 384 | " # You must commit explicitly after all executions.\n", 385 | " ag.connection.commit()\n", 386 | " except Exception as ex:\n", 387 | " ag.rollback()\n", 388 | " raise ex\n", 389 | "\n", 390 | "with ag.connection.cursor() as cursor:\n", 391 | " try :# Create Edges\n", 392 | " ag.cypher(cursor,\"MATCH (a:Country), (b:Country) WHERE a.name = 'USA' AND b.name = 'France' CREATE (a)-[r:distance {unit:'miles', value: 4760}]->(b)\")\n", 393 | " ag.cypher(cursor,\"MATCH (a:Country), (b:Country) WHERE a.name = 'France' AND b.name = 'Korea' CREATE (a)-[r:distance {unit: 'km', value: 9228}]->(b)\")\n", 394 | " ag.cypher(cursor,\"MATCH (a:Country {name: 'Korea'}), (b:Country {name: 'Russia'}) CREATE (a)-[r:distance {unit:'km', value: 3078}]->(b)\")\n", 395 | "\n", 396 | " # You must commit explicitly\n", 397 | " ag.connection.commit()\n", 398 | " except Exception as ex:\n", 399 | " ag.rollback()\n", 400 | " raise ex\n", 401 | "\n", 402 | "\n", 403 | "cursor = ag.execCypher(\"\"\"MATCH p=(:Country {name:\"USA\"})-[:distance]-(:Country)-[:distance]-(:Country) \n", 404 | " RETURN p\"\"\")\n", 405 | "\n", 406 | "for row in cursor:\n", 407 | " path = row[0]\n", 408 | " indent = \"\"\n", 409 | " for e in path:\n", 410 | " if e.gtype == age.TP_VERTEX:\n", 411 | " print(indent, e.label, e[\"name\"])\n", 412 | " elif e.gtype == age.TP_EDGE:\n", 413 | " print(indent, e.label, e[\"value\"], e[\"unit\"])\n", 414 | " else:\n", 415 | " print(indent, \"Unknown element.\", e)\n", 416 | " \n", 417 | " indent += \" >\"\n" 418 | ] 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "id": "a952384e-aa59-4474-ba52-0af11e78f7f3", 423 | "metadata": {}, 424 | "source": [ 425 | "---\n", 426 | "## Query COLLECT" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": null, 432 | "id": "656b5e09-b998-4dee-a09f-4ef706561732", 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "\n", 437 | "with ag.connection.cursor() as cursor:\n", 438 | " ag.cypher(cursor, \"MATCH (a)-[:workWith]-(c) WITH a as V, COLLECT(c) as CV RETURN V.name, CV\", cols=[\"V\",\"CV\"])\n", 439 | " for row in cursor:\n", 440 | " nm = row[0]\n", 441 | " collected = row[1]\n", 442 | " print(nm, \"workWith\", [i[\"name\"] for i in collected])\n", 443 | "\n", 444 | "for row in ag.execCypher(\"MATCH (a)-[:workWith]-(c) WITH a as V, COLLECT(c) as CV RETURN V.name, CV\", cols=[\"V1\",\"CV\"]):\n", 445 | " nm = row[0]\n", 446 | " collected = row[1]\n", 447 | " print(nm, \"workWith\", [i[\"name\"] for i in collected])\n" 448 | ] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "id": "f502e0db-a603-4eb9-90e8-dac2c9edd1d4", 453 | "metadata": {}, 454 | "source": [ 455 | "---\n", 456 | "## Query Scalar or properties value" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": null, 462 | "id": "7f93e698-888a-4dde-b327-e591659e051f", 463 | "metadata": {}, 464 | "outputs": [], 465 | "source": [ 466 | "# Query scalar value\n", 467 | "print(\"-- Query scalar value --------------------\")\n", 468 | "for row in ag.execCypher(\"MATCH (n:Person) RETURN id(n)\"):\n", 469 | " print(row[0])\n", 470 | " \n", 471 | "# Query properties \n", 472 | "print(\"-- Query properties --------------------\")\n", 473 | "\n", 474 | "for row in ag.execCypher(\"MATCH (n:Person) RETURN properties(n)\"):\n", 475 | " print(row[0])\n", 476 | " \n", 477 | "# Query properties value\n", 478 | "print(\"-- Query property value --------------------\")\n", 479 | "for row in ag.execCypher(\"MATCH (n:Person {name: 'Andy'}) RETURN n.title\"):\n", 480 | " print(row[0])\n", 481 | " \n", 482 | " " 483 | ] 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "id": "8ce0a003-b038-4334-9de8-111569549040", 488 | "metadata": {}, 489 | "source": [ 490 | "## Close connection" 491 | ] 492 | }, 493 | { 494 | "cell_type": "code", 495 | "execution_count": null, 496 | "id": "e15b0654-66d2-4da4-af66-6f776e6729ac", 497 | "metadata": {}, 498 | "outputs": [], 499 | "source": [ 500 | "# Clear test data\n", 501 | "age.deleteGraph(ag.connection, GRAPH_NAME)\n", 502 | "# connection close\n", 503 | "ag.close()" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": null, 509 | "id": "40e4d7a4-4009-4c42-892c-93cde00762d2", 510 | "metadata": {}, 511 | "outputs": [], 512 | "source": [] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": null, 517 | "id": "ab18f5f5-84d3-439f-aa82-53ccfd569d31", 518 | "metadata": {}, 519 | "outputs": [], 520 | "source": [] 521 | } 522 | ], 523 | "metadata": { 524 | "kernelspec": { 525 | "display_name": "Python 3", 526 | "language": "python", 527 | "name": "python3" 528 | }, 529 | "language_info": { 530 | "codemirror_mode": { 531 | "name": "ipython", 532 | "version": 3 533 | }, 534 | "file_extension": ".py", 535 | "mimetype": "text/x-python", 536 | "name": "python", 537 | "nbconvert_exporter": "python", 538 | "pygments_lexer": "ipython3", 539 | "version": "3.9.2" 540 | } 541 | }, 542 | "nbformat": 4, 543 | "nbformat_minor": 5 544 | } 545 | -------------------------------------------------------------------------------- /age/gen/AgtypeParser.py: -------------------------------------------------------------------------------- 1 | # Generated from antlr/Agtype.g4 by ANTLR 4.11.1 2 | # encoding: utf-8 3 | from antlr4 import * 4 | from io import StringIO 5 | import sys 6 | if sys.version_info[1] > 5: 7 | from typing import TextIO 8 | else: 9 | from typing.io import TextIO 10 | 11 | def serializedATN(): 12 | return [ 13 | 4,1,19,80,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, 14 | 6,2,7,7,7,1,0,1,0,1,0,1,1,1,1,3,1,22,8,1,1,2,1,2,1,2,1,2,1,2,1,2, 15 | 1,2,1,2,3,2,32,8,2,1,3,1,3,1,3,1,3,5,3,38,8,3,10,3,12,3,41,9,3,1, 16 | 3,1,3,1,3,1,3,3,3,47,8,3,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,5,5,57, 17 | 8,5,10,5,12,5,60,9,5,1,5,1,5,1,5,1,5,3,5,66,8,5,1,6,1,6,1,6,1,7, 18 | 1,7,1,7,3,7,74,8,7,1,7,1,7,3,7,78,8,7,1,7,0,0,8,0,2,4,6,8,10,12, 19 | 14,0,0,87,0,16,1,0,0,0,2,19,1,0,0,0,4,31,1,0,0,0,6,46,1,0,0,0,8, 20 | 48,1,0,0,0,10,65,1,0,0,0,12,67,1,0,0,0,14,77,1,0,0,0,16,17,3,2,1, 21 | 0,17,18,5,0,0,1,18,1,1,0,0,0,19,21,3,4,2,0,20,22,3,12,6,0,21,20, 22 | 1,0,0,0,21,22,1,0,0,0,22,3,1,0,0,0,23,32,5,15,0,0,24,32,5,16,0,0, 23 | 25,32,3,14,7,0,26,32,5,1,0,0,27,32,5,2,0,0,28,32,5,3,0,0,29,32,3, 24 | 6,3,0,30,32,3,10,5,0,31,23,1,0,0,0,31,24,1,0,0,0,31,25,1,0,0,0,31, 25 | 26,1,0,0,0,31,27,1,0,0,0,31,28,1,0,0,0,31,29,1,0,0,0,31,30,1,0,0, 26 | 0,32,5,1,0,0,0,33,34,5,4,0,0,34,39,3,8,4,0,35,36,5,5,0,0,36,38,3, 27 | 8,4,0,37,35,1,0,0,0,38,41,1,0,0,0,39,37,1,0,0,0,39,40,1,0,0,0,40, 28 | 42,1,0,0,0,41,39,1,0,0,0,42,43,5,6,0,0,43,47,1,0,0,0,44,45,5,4,0, 29 | 0,45,47,5,6,0,0,46,33,1,0,0,0,46,44,1,0,0,0,47,7,1,0,0,0,48,49,5, 30 | 15,0,0,49,50,5,7,0,0,50,51,3,2,1,0,51,9,1,0,0,0,52,53,5,8,0,0,53, 31 | 58,3,2,1,0,54,55,5,5,0,0,55,57,3,2,1,0,56,54,1,0,0,0,57,60,1,0,0, 32 | 0,58,56,1,0,0,0,58,59,1,0,0,0,59,61,1,0,0,0,60,58,1,0,0,0,61,62, 33 | 5,9,0,0,62,66,1,0,0,0,63,64,5,8,0,0,64,66,5,9,0,0,65,52,1,0,0,0, 34 | 65,63,1,0,0,0,66,11,1,0,0,0,67,68,5,10,0,0,68,69,5,14,0,0,69,13, 35 | 1,0,0,0,70,78,5,17,0,0,71,78,5,18,0,0,72,74,5,11,0,0,73,72,1,0,0, 36 | 0,73,74,1,0,0,0,74,75,1,0,0,0,75,78,5,12,0,0,76,78,5,13,0,0,77,70, 37 | 1,0,0,0,77,71,1,0,0,0,77,73,1,0,0,0,77,76,1,0,0,0,78,15,1,0,0,0, 38 | 8,21,31,39,46,58,65,73,77 39 | ] 40 | 41 | class AgtypeParser ( Parser ): 42 | 43 | grammarFileName = "Agtype.g4" 44 | 45 | atn = ATNDeserializer().deserialize(serializedATN()) 46 | 47 | decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] 48 | 49 | sharedContextCache = PredictionContextCache() 50 | 51 | literalNames = [ "", "'true'", "'false'", "'null'", "'{'", 52 | "','", "'}'", "':'", "'['", "']'", "'::'", "'-'", "'Infinity'", 53 | "'NaN'" ] 54 | 55 | symbolicNames = [ "", "", "", "", 56 | "", "", "", "", 57 | "", "", "", "", 58 | "", "", "IDENT", "STRING", "INTEGER", 59 | "RegularFloat", "ExponentFloat", "WS" ] 60 | 61 | RULE_agType = 0 62 | RULE_agValue = 1 63 | RULE_value = 2 64 | RULE_obj = 3 65 | RULE_pair = 4 66 | RULE_array = 5 67 | RULE_typeAnnotation = 6 68 | RULE_floatLiteral = 7 69 | 70 | ruleNames = [ "agType", "agValue", "value", "obj", "pair", "array", 71 | "typeAnnotation", "floatLiteral" ] 72 | 73 | EOF = Token.EOF 74 | T__0=1 75 | T__1=2 76 | T__2=3 77 | T__3=4 78 | T__4=5 79 | T__5=6 80 | T__6=7 81 | T__7=8 82 | T__8=9 83 | T__9=10 84 | T__10=11 85 | T__11=12 86 | T__12=13 87 | IDENT=14 88 | STRING=15 89 | INTEGER=16 90 | RegularFloat=17 91 | ExponentFloat=18 92 | WS=19 93 | 94 | def __init__(self, input:TokenStream, output:TextIO = sys.stdout): 95 | super().__init__(input, output) 96 | self.checkVersion("4.11.1") 97 | self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache) 98 | self._predicates = None 99 | 100 | 101 | 102 | 103 | class AgTypeContext(ParserRuleContext): 104 | __slots__ = 'parser' 105 | 106 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 107 | super().__init__(parent, invokingState) 108 | self.parser = parser 109 | 110 | def agValue(self): 111 | return self.getTypedRuleContext(AgtypeParser.AgValueContext,0) 112 | 113 | 114 | def EOF(self): 115 | return self.getToken(AgtypeParser.EOF, 0) 116 | 117 | def getRuleIndex(self): 118 | return AgtypeParser.RULE_agType 119 | 120 | def enterRule(self, listener:ParseTreeListener): 121 | if hasattr( listener, "enterAgType" ): 122 | listener.enterAgType(self) 123 | 124 | def exitRule(self, listener:ParseTreeListener): 125 | if hasattr( listener, "exitAgType" ): 126 | listener.exitAgType(self) 127 | 128 | def accept(self, visitor:ParseTreeVisitor): 129 | if hasattr( visitor, "visitAgType" ): 130 | return visitor.visitAgType(self) 131 | else: 132 | return visitor.visitChildren(self) 133 | 134 | 135 | 136 | 137 | def agType(self): 138 | 139 | localctx = AgtypeParser.AgTypeContext(self, self._ctx, self.state) 140 | self.enterRule(localctx, 0, self.RULE_agType) 141 | try: 142 | self.enterOuterAlt(localctx, 1) 143 | self.state = 16 144 | self.agValue() 145 | self.state = 17 146 | self.match(AgtypeParser.EOF) 147 | except RecognitionException as re: 148 | localctx.exception = re 149 | self._errHandler.reportError(self, re) 150 | self._errHandler.recover(self, re) 151 | finally: 152 | self.exitRule() 153 | return localctx 154 | 155 | 156 | class AgValueContext(ParserRuleContext): 157 | __slots__ = 'parser' 158 | 159 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 160 | super().__init__(parent, invokingState) 161 | self.parser = parser 162 | 163 | def value(self): 164 | return self.getTypedRuleContext(AgtypeParser.ValueContext,0) 165 | 166 | 167 | def typeAnnotation(self): 168 | return self.getTypedRuleContext(AgtypeParser.TypeAnnotationContext,0) 169 | 170 | 171 | def getRuleIndex(self): 172 | return AgtypeParser.RULE_agValue 173 | 174 | def enterRule(self, listener:ParseTreeListener): 175 | if hasattr( listener, "enterAgValue" ): 176 | listener.enterAgValue(self) 177 | 178 | def exitRule(self, listener:ParseTreeListener): 179 | if hasattr( listener, "exitAgValue" ): 180 | listener.exitAgValue(self) 181 | 182 | def accept(self, visitor:ParseTreeVisitor): 183 | if hasattr( visitor, "visitAgValue" ): 184 | return visitor.visitAgValue(self) 185 | else: 186 | return visitor.visitChildren(self) 187 | 188 | 189 | 190 | 191 | def agValue(self): 192 | 193 | localctx = AgtypeParser.AgValueContext(self, self._ctx, self.state) 194 | self.enterRule(localctx, 2, self.RULE_agValue) 195 | self._la = 0 # Token type 196 | try: 197 | self.enterOuterAlt(localctx, 1) 198 | self.state = 19 199 | self.value() 200 | self.state = 21 201 | self._errHandler.sync(self) 202 | _la = self._input.LA(1) 203 | if _la==10: 204 | self.state = 20 205 | self.typeAnnotation() 206 | 207 | 208 | except RecognitionException as re: 209 | localctx.exception = re 210 | self._errHandler.reportError(self, re) 211 | self._errHandler.recover(self, re) 212 | finally: 213 | self.exitRule() 214 | return localctx 215 | 216 | 217 | class ValueContext(ParserRuleContext): 218 | __slots__ = 'parser' 219 | 220 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 221 | super().__init__(parent, invokingState) 222 | self.parser = parser 223 | 224 | 225 | def getRuleIndex(self): 226 | return AgtypeParser.RULE_value 227 | 228 | 229 | def copyFrom(self, ctx:ParserRuleContext): 230 | super().copyFrom(ctx) 231 | 232 | 233 | 234 | class NullValueContext(ValueContext): 235 | 236 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 237 | super().__init__(parser) 238 | self.copyFrom(ctx) 239 | 240 | 241 | def enterRule(self, listener:ParseTreeListener): 242 | if hasattr( listener, "enterNullValue" ): 243 | listener.enterNullValue(self) 244 | 245 | def exitRule(self, listener:ParseTreeListener): 246 | if hasattr( listener, "exitNullValue" ): 247 | listener.exitNullValue(self) 248 | 249 | def accept(self, visitor:ParseTreeVisitor): 250 | if hasattr( visitor, "visitNullValue" ): 251 | return visitor.visitNullValue(self) 252 | else: 253 | return visitor.visitChildren(self) 254 | 255 | 256 | class ObjectValueContext(ValueContext): 257 | 258 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 259 | super().__init__(parser) 260 | self.copyFrom(ctx) 261 | 262 | def obj(self): 263 | return self.getTypedRuleContext(AgtypeParser.ObjContext,0) 264 | 265 | 266 | def enterRule(self, listener:ParseTreeListener): 267 | if hasattr( listener, "enterObjectValue" ): 268 | listener.enterObjectValue(self) 269 | 270 | def exitRule(self, listener:ParseTreeListener): 271 | if hasattr( listener, "exitObjectValue" ): 272 | listener.exitObjectValue(self) 273 | 274 | def accept(self, visitor:ParseTreeVisitor): 275 | if hasattr( visitor, "visitObjectValue" ): 276 | return visitor.visitObjectValue(self) 277 | else: 278 | return visitor.visitChildren(self) 279 | 280 | 281 | class IntegerValueContext(ValueContext): 282 | 283 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 284 | super().__init__(parser) 285 | self.copyFrom(ctx) 286 | 287 | def INTEGER(self): 288 | return self.getToken(AgtypeParser.INTEGER, 0) 289 | 290 | def enterRule(self, listener:ParseTreeListener): 291 | if hasattr( listener, "enterIntegerValue" ): 292 | listener.enterIntegerValue(self) 293 | 294 | def exitRule(self, listener:ParseTreeListener): 295 | if hasattr( listener, "exitIntegerValue" ): 296 | listener.exitIntegerValue(self) 297 | 298 | def accept(self, visitor:ParseTreeVisitor): 299 | if hasattr( visitor, "visitIntegerValue" ): 300 | return visitor.visitIntegerValue(self) 301 | else: 302 | return visitor.visitChildren(self) 303 | 304 | 305 | class TrueBooleanContext(ValueContext): 306 | 307 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 308 | super().__init__(parser) 309 | self.copyFrom(ctx) 310 | 311 | 312 | def enterRule(self, listener:ParseTreeListener): 313 | if hasattr( listener, "enterTrueBoolean" ): 314 | listener.enterTrueBoolean(self) 315 | 316 | def exitRule(self, listener:ParseTreeListener): 317 | if hasattr( listener, "exitTrueBoolean" ): 318 | listener.exitTrueBoolean(self) 319 | 320 | def accept(self, visitor:ParseTreeVisitor): 321 | if hasattr( visitor, "visitTrueBoolean" ): 322 | return visitor.visitTrueBoolean(self) 323 | else: 324 | return visitor.visitChildren(self) 325 | 326 | 327 | class FalseBooleanContext(ValueContext): 328 | 329 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 330 | super().__init__(parser) 331 | self.copyFrom(ctx) 332 | 333 | 334 | def enterRule(self, listener:ParseTreeListener): 335 | if hasattr( listener, "enterFalseBoolean" ): 336 | listener.enterFalseBoolean(self) 337 | 338 | def exitRule(self, listener:ParseTreeListener): 339 | if hasattr( listener, "exitFalseBoolean" ): 340 | listener.exitFalseBoolean(self) 341 | 342 | def accept(self, visitor:ParseTreeVisitor): 343 | if hasattr( visitor, "visitFalseBoolean" ): 344 | return visitor.visitFalseBoolean(self) 345 | else: 346 | return visitor.visitChildren(self) 347 | 348 | 349 | class FloatValueContext(ValueContext): 350 | 351 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 352 | super().__init__(parser) 353 | self.copyFrom(ctx) 354 | 355 | def floatLiteral(self): 356 | return self.getTypedRuleContext(AgtypeParser.FloatLiteralContext,0) 357 | 358 | 359 | def enterRule(self, listener:ParseTreeListener): 360 | if hasattr( listener, "enterFloatValue" ): 361 | listener.enterFloatValue(self) 362 | 363 | def exitRule(self, listener:ParseTreeListener): 364 | if hasattr( listener, "exitFloatValue" ): 365 | listener.exitFloatValue(self) 366 | 367 | def accept(self, visitor:ParseTreeVisitor): 368 | if hasattr( visitor, "visitFloatValue" ): 369 | return visitor.visitFloatValue(self) 370 | else: 371 | return visitor.visitChildren(self) 372 | 373 | 374 | class StringValueContext(ValueContext): 375 | 376 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 377 | super().__init__(parser) 378 | self.copyFrom(ctx) 379 | 380 | def STRING(self): 381 | return self.getToken(AgtypeParser.STRING, 0) 382 | 383 | def enterRule(self, listener:ParseTreeListener): 384 | if hasattr( listener, "enterStringValue" ): 385 | listener.enterStringValue(self) 386 | 387 | def exitRule(self, listener:ParseTreeListener): 388 | if hasattr( listener, "exitStringValue" ): 389 | listener.exitStringValue(self) 390 | 391 | def accept(self, visitor:ParseTreeVisitor): 392 | if hasattr( visitor, "visitStringValue" ): 393 | return visitor.visitStringValue(self) 394 | else: 395 | return visitor.visitChildren(self) 396 | 397 | 398 | class ArrayValueContext(ValueContext): 399 | 400 | def __init__(self, parser, ctx:ParserRuleContext): # actually a AgtypeParser.ValueContext 401 | super().__init__(parser) 402 | self.copyFrom(ctx) 403 | 404 | def array(self): 405 | return self.getTypedRuleContext(AgtypeParser.ArrayContext,0) 406 | 407 | 408 | def enterRule(self, listener:ParseTreeListener): 409 | if hasattr( listener, "enterArrayValue" ): 410 | listener.enterArrayValue(self) 411 | 412 | def exitRule(self, listener:ParseTreeListener): 413 | if hasattr( listener, "exitArrayValue" ): 414 | listener.exitArrayValue(self) 415 | 416 | def accept(self, visitor:ParseTreeVisitor): 417 | if hasattr( visitor, "visitArrayValue" ): 418 | return visitor.visitArrayValue(self) 419 | else: 420 | return visitor.visitChildren(self) 421 | 422 | 423 | 424 | def value(self): 425 | 426 | localctx = AgtypeParser.ValueContext(self, self._ctx, self.state) 427 | self.enterRule(localctx, 4, self.RULE_value) 428 | try: 429 | self.state = 31 430 | self._errHandler.sync(self) 431 | token = self._input.LA(1) 432 | if token in [15]: 433 | localctx = AgtypeParser.StringValueContext(self, localctx) 434 | self.enterOuterAlt(localctx, 1) 435 | self.state = 23 436 | self.match(AgtypeParser.STRING) 437 | pass 438 | elif token in [16]: 439 | localctx = AgtypeParser.IntegerValueContext(self, localctx) 440 | self.enterOuterAlt(localctx, 2) 441 | self.state = 24 442 | self.match(AgtypeParser.INTEGER) 443 | pass 444 | elif token in [11, 12, 13, 17, 18]: 445 | localctx = AgtypeParser.FloatValueContext(self, localctx) 446 | self.enterOuterAlt(localctx, 3) 447 | self.state = 25 448 | self.floatLiteral() 449 | pass 450 | elif token in [1]: 451 | localctx = AgtypeParser.TrueBooleanContext(self, localctx) 452 | self.enterOuterAlt(localctx, 4) 453 | self.state = 26 454 | self.match(AgtypeParser.T__0) 455 | pass 456 | elif token in [2]: 457 | localctx = AgtypeParser.FalseBooleanContext(self, localctx) 458 | self.enterOuterAlt(localctx, 5) 459 | self.state = 27 460 | self.match(AgtypeParser.T__1) 461 | pass 462 | elif token in [3]: 463 | localctx = AgtypeParser.NullValueContext(self, localctx) 464 | self.enterOuterAlt(localctx, 6) 465 | self.state = 28 466 | self.match(AgtypeParser.T__2) 467 | pass 468 | elif token in [4]: 469 | localctx = AgtypeParser.ObjectValueContext(self, localctx) 470 | self.enterOuterAlt(localctx, 7) 471 | self.state = 29 472 | self.obj() 473 | pass 474 | elif token in [8]: 475 | localctx = AgtypeParser.ArrayValueContext(self, localctx) 476 | self.enterOuterAlt(localctx, 8) 477 | self.state = 30 478 | self.array() 479 | pass 480 | else: 481 | raise NoViableAltException(self) 482 | 483 | except RecognitionException as re: 484 | localctx.exception = re 485 | self._errHandler.reportError(self, re) 486 | self._errHandler.recover(self, re) 487 | finally: 488 | self.exitRule() 489 | return localctx 490 | 491 | 492 | class ObjContext(ParserRuleContext): 493 | __slots__ = 'parser' 494 | 495 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 496 | super().__init__(parent, invokingState) 497 | self.parser = parser 498 | 499 | def pair(self, i:int=None): 500 | if i is None: 501 | return self.getTypedRuleContexts(AgtypeParser.PairContext) 502 | else: 503 | return self.getTypedRuleContext(AgtypeParser.PairContext,i) 504 | 505 | 506 | def getRuleIndex(self): 507 | return AgtypeParser.RULE_obj 508 | 509 | def enterRule(self, listener:ParseTreeListener): 510 | if hasattr( listener, "enterObj" ): 511 | listener.enterObj(self) 512 | 513 | def exitRule(self, listener:ParseTreeListener): 514 | if hasattr( listener, "exitObj" ): 515 | listener.exitObj(self) 516 | 517 | def accept(self, visitor:ParseTreeVisitor): 518 | if hasattr( visitor, "visitObj" ): 519 | return visitor.visitObj(self) 520 | else: 521 | return visitor.visitChildren(self) 522 | 523 | 524 | 525 | 526 | def obj(self): 527 | 528 | localctx = AgtypeParser.ObjContext(self, self._ctx, self.state) 529 | self.enterRule(localctx, 6, self.RULE_obj) 530 | self._la = 0 # Token type 531 | try: 532 | self.state = 46 533 | self._errHandler.sync(self) 534 | la_ = self._interp.adaptivePredict(self._input,3,self._ctx) 535 | if la_ == 1: 536 | self.enterOuterAlt(localctx, 1) 537 | self.state = 33 538 | self.match(AgtypeParser.T__3) 539 | self.state = 34 540 | self.pair() 541 | self.state = 39 542 | self._errHandler.sync(self) 543 | _la = self._input.LA(1) 544 | while _la==5: 545 | self.state = 35 546 | self.match(AgtypeParser.T__4) 547 | self.state = 36 548 | self.pair() 549 | self.state = 41 550 | self._errHandler.sync(self) 551 | _la = self._input.LA(1) 552 | 553 | self.state = 42 554 | self.match(AgtypeParser.T__5) 555 | pass 556 | 557 | elif la_ == 2: 558 | self.enterOuterAlt(localctx, 2) 559 | self.state = 44 560 | self.match(AgtypeParser.T__3) 561 | self.state = 45 562 | self.match(AgtypeParser.T__5) 563 | pass 564 | 565 | 566 | except RecognitionException as re: 567 | localctx.exception = re 568 | self._errHandler.reportError(self, re) 569 | self._errHandler.recover(self, re) 570 | finally: 571 | self.exitRule() 572 | return localctx 573 | 574 | 575 | class PairContext(ParserRuleContext): 576 | __slots__ = 'parser' 577 | 578 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 579 | super().__init__(parent, invokingState) 580 | self.parser = parser 581 | 582 | def STRING(self): 583 | return self.getToken(AgtypeParser.STRING, 0) 584 | 585 | def agValue(self): 586 | return self.getTypedRuleContext(AgtypeParser.AgValueContext,0) 587 | 588 | 589 | def getRuleIndex(self): 590 | return AgtypeParser.RULE_pair 591 | 592 | def enterRule(self, listener:ParseTreeListener): 593 | if hasattr( listener, "enterPair" ): 594 | listener.enterPair(self) 595 | 596 | def exitRule(self, listener:ParseTreeListener): 597 | if hasattr( listener, "exitPair" ): 598 | listener.exitPair(self) 599 | 600 | def accept(self, visitor:ParseTreeVisitor): 601 | if hasattr( visitor, "visitPair" ): 602 | return visitor.visitPair(self) 603 | else: 604 | return visitor.visitChildren(self) 605 | 606 | 607 | 608 | 609 | def pair(self): 610 | 611 | localctx = AgtypeParser.PairContext(self, self._ctx, self.state) 612 | self.enterRule(localctx, 8, self.RULE_pair) 613 | try: 614 | self.enterOuterAlt(localctx, 1) 615 | self.state = 48 616 | self.match(AgtypeParser.STRING) 617 | self.state = 49 618 | self.match(AgtypeParser.T__6) 619 | self.state = 50 620 | self.agValue() 621 | except RecognitionException as re: 622 | localctx.exception = re 623 | self._errHandler.reportError(self, re) 624 | self._errHandler.recover(self, re) 625 | finally: 626 | self.exitRule() 627 | return localctx 628 | 629 | 630 | class ArrayContext(ParserRuleContext): 631 | __slots__ = 'parser' 632 | 633 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 634 | super().__init__(parent, invokingState) 635 | self.parser = parser 636 | 637 | def agValue(self, i:int=None): 638 | if i is None: 639 | return self.getTypedRuleContexts(AgtypeParser.AgValueContext) 640 | else: 641 | return self.getTypedRuleContext(AgtypeParser.AgValueContext,i) 642 | 643 | 644 | def getRuleIndex(self): 645 | return AgtypeParser.RULE_array 646 | 647 | def enterRule(self, listener:ParseTreeListener): 648 | if hasattr( listener, "enterArray" ): 649 | listener.enterArray(self) 650 | 651 | def exitRule(self, listener:ParseTreeListener): 652 | if hasattr( listener, "exitArray" ): 653 | listener.exitArray(self) 654 | 655 | def accept(self, visitor:ParseTreeVisitor): 656 | if hasattr( visitor, "visitArray" ): 657 | return visitor.visitArray(self) 658 | else: 659 | return visitor.visitChildren(self) 660 | 661 | 662 | 663 | 664 | def array(self): 665 | 666 | localctx = AgtypeParser.ArrayContext(self, self._ctx, self.state) 667 | self.enterRule(localctx, 10, self.RULE_array) 668 | self._la = 0 # Token type 669 | try: 670 | self.state = 65 671 | self._errHandler.sync(self) 672 | la_ = self._interp.adaptivePredict(self._input,5,self._ctx) 673 | if la_ == 1: 674 | self.enterOuterAlt(localctx, 1) 675 | self.state = 52 676 | self.match(AgtypeParser.T__7) 677 | self.state = 53 678 | self.agValue() 679 | self.state = 58 680 | self._errHandler.sync(self) 681 | _la = self._input.LA(1) 682 | while _la==5: 683 | self.state = 54 684 | self.match(AgtypeParser.T__4) 685 | self.state = 55 686 | self.agValue() 687 | self.state = 60 688 | self._errHandler.sync(self) 689 | _la = self._input.LA(1) 690 | 691 | self.state = 61 692 | self.match(AgtypeParser.T__8) 693 | pass 694 | 695 | elif la_ == 2: 696 | self.enterOuterAlt(localctx, 2) 697 | self.state = 63 698 | self.match(AgtypeParser.T__7) 699 | self.state = 64 700 | self.match(AgtypeParser.T__8) 701 | pass 702 | 703 | 704 | except RecognitionException as re: 705 | localctx.exception = re 706 | self._errHandler.reportError(self, re) 707 | self._errHandler.recover(self, re) 708 | finally: 709 | self.exitRule() 710 | return localctx 711 | 712 | 713 | class TypeAnnotationContext(ParserRuleContext): 714 | __slots__ = 'parser' 715 | 716 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 717 | super().__init__(parent, invokingState) 718 | self.parser = parser 719 | 720 | def IDENT(self): 721 | return self.getToken(AgtypeParser.IDENT, 0) 722 | 723 | def getRuleIndex(self): 724 | return AgtypeParser.RULE_typeAnnotation 725 | 726 | def enterRule(self, listener:ParseTreeListener): 727 | if hasattr( listener, "enterTypeAnnotation" ): 728 | listener.enterTypeAnnotation(self) 729 | 730 | def exitRule(self, listener:ParseTreeListener): 731 | if hasattr( listener, "exitTypeAnnotation" ): 732 | listener.exitTypeAnnotation(self) 733 | 734 | def accept(self, visitor:ParseTreeVisitor): 735 | if hasattr( visitor, "visitTypeAnnotation" ): 736 | return visitor.visitTypeAnnotation(self) 737 | else: 738 | return visitor.visitChildren(self) 739 | 740 | 741 | 742 | 743 | def typeAnnotation(self): 744 | 745 | localctx = AgtypeParser.TypeAnnotationContext(self, self._ctx, self.state) 746 | self.enterRule(localctx, 12, self.RULE_typeAnnotation) 747 | try: 748 | self.enterOuterAlt(localctx, 1) 749 | self.state = 67 750 | self.match(AgtypeParser.T__9) 751 | self.state = 68 752 | self.match(AgtypeParser.IDENT) 753 | except RecognitionException as re: 754 | localctx.exception = re 755 | self._errHandler.reportError(self, re) 756 | self._errHandler.recover(self, re) 757 | finally: 758 | self.exitRule() 759 | return localctx 760 | 761 | 762 | class FloatLiteralContext(ParserRuleContext): 763 | __slots__ = 'parser' 764 | 765 | def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): 766 | super().__init__(parent, invokingState) 767 | self.parser = parser 768 | 769 | def RegularFloat(self): 770 | return self.getToken(AgtypeParser.RegularFloat, 0) 771 | 772 | def ExponentFloat(self): 773 | return self.getToken(AgtypeParser.ExponentFloat, 0) 774 | 775 | def getRuleIndex(self): 776 | return AgtypeParser.RULE_floatLiteral 777 | 778 | def enterRule(self, listener:ParseTreeListener): 779 | if hasattr( listener, "enterFloatLiteral" ): 780 | listener.enterFloatLiteral(self) 781 | 782 | def exitRule(self, listener:ParseTreeListener): 783 | if hasattr( listener, "exitFloatLiteral" ): 784 | listener.exitFloatLiteral(self) 785 | 786 | def accept(self, visitor:ParseTreeVisitor): 787 | if hasattr( visitor, "visitFloatLiteral" ): 788 | return visitor.visitFloatLiteral(self) 789 | else: 790 | return visitor.visitChildren(self) 791 | 792 | 793 | 794 | 795 | def floatLiteral(self): 796 | 797 | localctx = AgtypeParser.FloatLiteralContext(self, self._ctx, self.state) 798 | self.enterRule(localctx, 14, self.RULE_floatLiteral) 799 | self._la = 0 # Token type 800 | try: 801 | self.state = 77 802 | self._errHandler.sync(self) 803 | token = self._input.LA(1) 804 | if token in [17]: 805 | self.enterOuterAlt(localctx, 1) 806 | self.state = 70 807 | self.match(AgtypeParser.RegularFloat) 808 | pass 809 | elif token in [18]: 810 | self.enterOuterAlt(localctx, 2) 811 | self.state = 71 812 | self.match(AgtypeParser.ExponentFloat) 813 | pass 814 | elif token in [11, 12]: 815 | self.enterOuterAlt(localctx, 3) 816 | self.state = 73 817 | self._errHandler.sync(self) 818 | _la = self._input.LA(1) 819 | if _la==11: 820 | self.state = 72 821 | self.match(AgtypeParser.T__10) 822 | 823 | 824 | self.state = 75 825 | self.match(AgtypeParser.T__11) 826 | pass 827 | elif token in [13]: 828 | self.enterOuterAlt(localctx, 4) 829 | self.state = 76 830 | self.match(AgtypeParser.T__12) 831 | pass 832 | else: 833 | raise NoViableAltException(self) 834 | 835 | except RecognitionException as re: 836 | localctx.exception = re 837 | self._errHandler.reportError(self, re) 838 | self._errHandler.recover(self, re) 839 | finally: 840 | self.exitRule() 841 | return localctx 842 | 843 | 844 | 845 | 846 | 847 | --------------------------------------------------------------------------------