|
5 | 5 | from neo4j.exceptions import ClientError |
6 | 6 |
|
7 | 7 | from neomodel import config |
8 | | -from neomodel.exceptions import DoesNotExist, NodeClassAlreadyDefined |
| 8 | +from neomodel.exceptions import ( |
| 9 | + DoesNotExist, |
| 10 | + FeatureNotSupported, |
| 11 | + NodeClassAlreadyDefined, |
| 12 | +) |
9 | 13 | from neomodel.hooks import hooks |
10 | 14 | from neomodel.properties import Property, PropertyManager |
11 | 15 | from neomodel.util import Database, _get_node_properties, _UnsavedNode, classproperty |
@@ -160,6 +164,27 @@ def _create_relationship_index(relationship_type: str, property_name: str, stdou |
160 | 164 | raise |
161 | 165 |
|
162 | 166 |
|
| 167 | +def _create_relationship_constraint(relationship_type: str, property_name: str, stdout): |
| 168 | + if db.version_is_higher_than("5.7"): |
| 169 | + try: |
| 170 | + db.cypher_query( |
| 171 | + f"""CREATE CONSTRAINT constraint_unique_{relationship_type}_{property_name} |
| 172 | + FOR ()-[r:{relationship_type}]-() REQUIRE r.{property_name} IS UNIQUE""" |
| 173 | + ) |
| 174 | + except ClientError as e: |
| 175 | + if e.code in ( |
| 176 | + RULE_ALREADY_EXISTS, |
| 177 | + CONSTRAINT_ALREADY_EXISTS, |
| 178 | + ): |
| 179 | + stdout.write(f"{str(e)}\n") |
| 180 | + else: |
| 181 | + raise |
| 182 | + else: |
| 183 | + raise FeatureNotSupported( |
| 184 | + f"Unique indexes on relationships are not supported in Neo4j version {db.database_version}. Please upgrade to Neo4j 5.7 or higher." |
| 185 | + ) |
| 186 | + |
| 187 | + |
163 | 188 | def _install_node(cls, name, property, quiet, stdout): |
164 | 189 | # Create indexes and constraints for node property |
165 | 190 | db_property = property.db_property or name |
@@ -201,6 +226,16 @@ def _install_relationship(cls, relationship, quiet, stdout): |
201 | 226 | property_name=db_property, |
202 | 227 | stdout=stdout, |
203 | 228 | ) |
| 229 | + elif property.unique_index: |
| 230 | + if not quiet: |
| 231 | + stdout.write( |
| 232 | + f" + Creating relationship unique constraint for {prop_name} on relationship type {relationship_type} for relationship model {cls.__module__}.{relationship_cls.__name__}\n" |
| 233 | + ) |
| 234 | + _create_relationship_constraint( |
| 235 | + relationship_type=relationship_type, |
| 236 | + property_name=db_property, |
| 237 | + stdout=stdout, |
| 238 | + ) |
204 | 239 |
|
205 | 240 |
|
206 | 241 | def install_all_labels(stdout=None): |
@@ -246,8 +281,21 @@ def __new__(mcs, name, bases, namespace): |
246 | 281 | else: |
247 | 282 | if "deleted" in namespace: |
248 | 283 | raise ValueError( |
249 | | - "Class property called 'deleted' conflicts " |
250 | | - "with neomodel internals." |
| 284 | + "Property name 'deleted' is not allowed as it conflicts with neomodel internals." |
| 285 | + ) |
| 286 | + elif "id" in namespace: |
| 287 | + raise ValueError( |
| 288 | + """ |
| 289 | + Property name 'id' is not allowed as it conflicts with neomodel internals. |
| 290 | + Consider using 'uid' or 'identifier' as id is also a Neo4j internal. |
| 291 | + """ |
| 292 | + ) |
| 293 | + elif "element_id" in namespace: |
| 294 | + raise ValueError( |
| 295 | + """ |
| 296 | + Property name 'element_id' is not allowed as it conflicts with neomodel internals. |
| 297 | + Consider using 'uid' or 'identifier' as element_id is also a Neo4j internal. |
| 298 | + """ |
251 | 299 | ) |
252 | 300 | for key, value in ( |
253 | 301 | (x, y) for x, y in namespace.items() if isinstance(y, Property) |
|
0 commit comments