diff --git a/README.adoc b/README.adoc index c6adda33b..06e29a3ac 100644 --- a/README.adoc +++ b/README.adoc @@ -11,6 +11,7 @@ Michael Simons :latest_version: 6.0.0-M05 :branch: main // end::properties[] +:examplesdir: docs/src/main/asciidoc/modules/ROOT/examples [abstract] -- @@ -24,6 +25,8 @@ The functionality and behaviour released in the GA release may differ from those This driver is officially supported and endorsed by Neo4j. It is a standalone driver, independent of and *not* built on top of the https://github.com/neo4j/neo4j-java-driver[common Neo4j Java Driver]. While the latter provides a Neo4j-idiomatic way to access Neo4j from Java, the JDBC driver adheres to https://docs.oracle.com/en/java/javase/17/docs/api/java.sql/java/sql/package-summary.html[JDBC 4.3]. + +NOTE: This documentation refers to *this* driver as the _Neo4j JDBC Driver_ and to the idiomatic Neo4j driver as the _common Neo4j Java Driver_. // end::abstract[] -- @@ -58,21 +61,57 @@ We offer several distributions, please have a look http://neo4j.github.io/neo4j- If you feel adventurous, grab the code and build the driver yourself. You find the instructions in our link:CONTRIBUTING.adoc[contribution documentation]. +== Quickstart + +After adding the bundle to your application, you can use the Neo4j JDBC driver as any other JDBC driver. + +// tag::quickstart[] +TIP: In case any tooling asks you for the name of the concrete driver class, it is `org.neo4j.jdbc.Neo4jDriver`. + +[source, java, tabsize=4] +.Acquire a connection and execute a query +---- +include::{examplesDir}/Quickstart.java[tag=pt1] +---- +<.> Instantiate a JDBC connection. There's no need to do any class loading beforehand, the driver will be automatically registered +<.> Create a (reusable) statement +<.> Execute a query +<.> Iterate over the results, as with any other JDBC result set +<.> JDBC's indexing starts at 1 +<.> JDBC also allows retrieval of result columns by name; the Neo4j JDBC driver also supports complex objects, such as lists + +In the example above we used Neo4j's _lingua franca_, https://neo4j.com/docs/getting-started/cypher-intro/[Cypher], to query the database. +The Neo4j JDBC Driver has limited support for using SQL as well. +It can do so automatically or on a case-by-case basis. +To translate a single, call `java.sql.Connection#nativeSQL(String)` and use the result in your queries. +For automatic translation, instantiate the driver setting the optional URL parameter `sql2cypher` to `true`. +The following example shows how: + +[source, java, tabsize=4, indent=0] +.Configure the JDBC driver to automatically translate SQL to cypher. +---- +include::{examplesDir}/Quickstart.java[tag=pt2] +---- +<.> This SQL query will be translated into the same Cypher query of the previous example. +The remainder of the method is identical to before. + +For more informaiton, see xref:sql2cypher.adoc[SQL to Cypher translation]. +// end::quickstart[] + == Introduction // tag::introduction[] -The JDBC acronym stands for "Java Database Connectivity" and as such is not bound exclusively to relational databases. -Nevertheless, JDBC is highly influenced by the SQL standard and existing, relational databases, in regard to terms, definitions and behaviour defined. -Neo4j is a graph database with quite a different paradigm than relational and a non-standardized behaviour in some areas. -There might be some details that don't map 100% in each place, and we make sure to educate you about these in this documentation +JDBC stands for "Java Database Connectivity" and is thus not bound exclusively to relational databases. +Nevertheless, JDBC's terms, definitions, and behavior are highly influenced by SQL and relational databases. +As Neo4j is a graph database with quite a different paradigm than relational and a non-standardized behaviour in some areas, there might be some details that don't map 100% in each place, and we make sure to educate you about these in this documentation. -NOTE: Inside this documentation we will refer to *this* driver as the _Neo4j JDBC Driver_ and to the idiomatic Neo4j driver as the _common Neo4j Java Driver_. +This documentation focuses on install, use, and configure the Neo4j JDBC Driver, as well as discussing the driver's design choices. +While we do provide runnable examples showing how to use JDBC with Neo4j, this is not a documentation about how to correctly use JDBC as an API. -The Neo4j JDBC Driver requires JDK 17 on the client side and a minimum version of Neo4j 5.5 on the server side. -To use it against a Neo4j cluster, server-side routing must be enabled on the cluster. +NOTE: The Neo4j JDBC Driver requires JDK 17 on the client side and Neo4j 5.5+ on the server side. +To use it with a Neo4j cluster, server-side routing must be enabled on the cluster. === Features -* JDK 17 baseline * Fully supports the Java module system * Adheres to JDBC 4.3 * Can run any Cypher statement @@ -81,53 +120,46 @@ To use it against a Neo4j cluster, server-side routing must be enabled on the cl * Provides an optional default implementation to translate many SQL statements into semantically similar Cypher statements * Can be safely used with JDBC connection pools as opposed to the common Neo4j Java Driver or any JDBC driver based on that, as it doesn't do internal connection pooling and transaction management otherwise than dictated by the JDBC Spec -The absence of any connection pooling and transaction management is actually an advantage of the Neo4j JDBC Driver over the common Neo4j Java Driver. +The absence of any connection pooling and transaction management is an advantage of the Neo4j JDBC Driver over the common Neo4j Java Driver. It allows to pick and choose any database connection pooling system such as https://github.com/brettwooldridge/HikariCP[HikariCP] and transaction management such as https://jakarta.ee/specifications/transactions/[Jakarta Transactions]. -NOTE: The default SQL to Cypher translation implementation is based on https://www.jooq.org[jOOQ] by https://www.datageekery.com[Datageekery]. -We are a long-time fans of how Lukas Eder—inventor of jOOQ—has bridged the gap between Java and database querying. -It even inspired the https://github.com/neo4j-contrib/cypher-dsl[Cypher-DSL], providing the other half of our translation layer. -We are grateful for kick-starting the original Sql2Cypher project together in early 2023, on which we can build now. - === Limitations -* The database metadata is retrieved on a best effort base, using existing schema methods of Neo4j, such as `db.labels`, `db.schema.nodeTypeProperties()` -* While single label nodes map naturally to table names, Nodes with multiple labels don't -* There is no reliable way to always determine the datatype for properties on nodes without reading all of them (which this driver does not do) -* Some JDBC features are not yet supported (such as the `CallableStatement`), some feature won't ever be supported -* The SQL to Cypher translator does only support a limited subset of clauses and SQL constructs that can be semantically equivalent translated to Cypher (See xref:s2c_supported_statements[xrefstyle=short]) -* There is no "right" way to map `JOIN` statements to relations, so your mileage may vary +* The database metadata is retrieved using Neo4j's schema methods, such as `db.labels`, `db.schema.nodeTypeProperties()`, which may not always be accurate +* While single label nodes map naturally to table names, nodes with multiple labels don't +* There is no reliable way to always determine the datatype for properties on nodes, as it would require reading all of them (which this driver does not do) +* Some JDBC features are not supported yet (such as the `CallableStatement`); some feature will never be supported +* The SQL to Cypher translator supports only a limited subset of clauses and SQL constructs that can be equivalently translated to Cypher (See xref:s2c_supported_statements[]) +* There is no "right" way to map `JOIN` statements to relationships, so your mileage may vary === When to use the Neo4j JDBC Driver? -This driver has been developed with the following use-cases in mind: - * Integration with ETL and ELT tools that don't offer an integration based on the common Neo4j Java driver -* An easier on-ramp towards Neo4j for teams that are familiar with JDBC and want to keep on using that API, but with Cypher and Neo4j -* Integration for ecosystems like Jakarta EE whose transaction management will directly support any compliant JDBC driver +* An easier on-ramp towards Neo4j for people familiar with JDBC, who want to keep using that API, but with Cypher and Neo4j +* Integration for ecosystems like Jakarta EE whose transaction management directly supports any JDBC-compliant driver * Integration with database migration tools such as Flyway -There is *no need* to redesign an application that is build on the common Neo4j Java Driver to use this driver. +*There is no need to redesign an application that is built on the common Neo4j Java Driver to migrate to this driver.* If your ecosystem already provides a higher-level integration based on the common Neo4j Java Driver, such as https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j (SDN)] for https://spring.io/projects/spring-boot/[Spring], there is no need to switch to something else. -In case of https://quarkus.io[Quarkus] the Neo4j JDBC Driver is an option to consider: While we do provide an integration for the https://github.com/quarkiverse/quarkus-neo4j[common Neo4j Java Driver], this integration does not support Quarkus' transaction systems in contrast to this driver. +In case of https://quarkus.io[Quarkus], the Neo4j JDBC Driver is an option to consider: although we do provide an integration for the https://github.com/quarkiverse/quarkus-neo4j[common Neo4j Java Driver], this integration does not support Quarkus' transaction systems in contrast to this driver. -While there is little incentive to use this driver with Hibernate (https://github.com/neo4j/neo4j-ogm[Neo4j-OGM] or SDN are the better alternatives for Neo4j), it might be worth giving https://spring.io/projects/spring-data-jdbc/[Spring Data JDBC] a try. +As there is little incentive to use this driver with Hibernate (https://github.com/neo4j/neo4j-ogm[Neo4j-OGM] or SDN are the best alternatives for Neo4j), it might be worth giving https://spring.io/projects/spring-data-jdbc/[Spring Data JDBC] a try. -=== Differences to the previous versions of this driver and other JDBC drivers for Neo4j +=== Differences with the previous versions of this driver and other JDBC drivers for Neo4j -Several other JDBC drivers exists for Neo4j, most notably the previous versions 4 and 5 of this driver, originally developed by http://larus-ba.it/[Larus BA, Italy] for Neo4j. -Most—if not all of them—do wrap the common Neo4j Java Driver and implement the JDBC spec on top of that. -This comes with a bunch of problems: +Several other JDBC drivers exists for Neo4j, most notably the previous versions 4 and 5 of this driver. +Most (if not all) of them wrap the common Neo4j Java Driver and implement the JDBC spec on top of that. +This comes with a number of issues: -* The common Neo4j Java Driver manages a connection pool; JDBC drivers on the other hand delegate this task to dedicated pooling solutions: If you take the above-mentioned driver into a standard container, you will eventually end up with a pool of pools -* The transaction management of the common Neo4j Java Driver is not exactly aligned with the way JDBC thinks about transactions, it's usually hard to get this exactly right -* Additionally, the original JDBC driver from Larus shades a couple of dependencies, such as Jackson as well as additional logging frameworks which takes a toll on the classpath and in case of logging, does actually lead to runtime problems -* Existing drivers with a SQL to Cypher translation layer are "read-only" and don't support write statements +* You end up with a _pool of connection pools_, because the common Neo4j Java Driver manages a connection pool, whereas JDBC drivers delegate this task to dedicated pooling solutions. +* The transaction management of the common Neo4j Java Driver is not aligned with the way JDBC manages transactions. +* Older versions of the Neo4j JDBC driver shade a few dependencies, such as `Jackson` as well as additional logging frameworks. +This takes a toll on the classpath and, in case of logging, it leads to runtime problems. +* Existing drivers with an SQL-to-Cypher translation layer are "read-only" and don't support write statements, so they cannot be used for ETL use-cases aiming to ingest data into Neo4j. -There are some drivers available that provide a SQL to Cypher translation layer as well. -Those however are read-only and cannot be used for ETL use-cases aiming to ingest data into Neo4j. +WARNING: This driver does not support automatic reshaping or flattening of the result sets, as the previous versions do. +If you query for nodes, relationships, paths, or maps, you should use `getObject` on the result sets and cast them to the appropriate type (you find all of them inside the package `org.neo4j.jdbc.values`). +However, the default SQL-to-Cypher translator will (when connected to a database) figure out what properties nodes have and turn the asterisk (`*`) into individual columns of nodes and relationships, just like what you would expect when running a `SELECT *` statement. -One feature that this driver does not provide is automatic reshaping or flattening of the result-sets, as the previous incarnation does: -If you query for objects such as nodes, relationships, paths or maps you can and should use `getObject` on the result-sets and cast to the appropriate type (you find all of them inside the package `org.neo4j.jdbc.values`). -However, the default SQL to Cypher translator will—when connected to a database—figure out what properties labels have and turn the asterisk (`*`) into individual columns of nodes and relationships, just like what you would expect when running a `SELECT *` statement. +For information on upgrade/migration from other drivers to this one, see xref:migrating.adoc[]. // end::introduction[] diff --git a/docs/src/main/asciidoc/modules/ROOT/nav.adoc b/docs/src/main/asciidoc/modules/ROOT/nav.adoc index 0bfb64bad..05d1c4945 100644 --- a/docs/src/main/asciidoc/modules/ROOT/nav.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/nav.adoc @@ -1,4 +1,4 @@ -* xref:quickstart.adoc[] +* xref:usage.adoc[] * xref:distribution.adoc[] * xref:configuration.adoc[] * xref:metadata.adoc[] @@ -6,4 +6,4 @@ * xref:text2cypher.adoc[] * xref:datatypes.adoc[] * xref:syntax.adoc[] -* xref:migrating.adoc[] \ No newline at end of file +* xref:migrating.adoc[] diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/configuration.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/configuration.adoc index 3e4536f76..097ba720f 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/configuration.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/configuration.adoc @@ -3,30 +3,27 @@ == Driver class name The Neo4j JDBC Driver is `org.neo4j.jdbc.Neo4jDriver`. -In modern Java tools you should not have to touch this class directly, but there are some connection pools and front-ends that will ask you for this. +With modern Java tools, you should not need to touch this class directly, but there are some connection pools and front-ends that will ask you for this. The class is public API. We also provide `org.neo4j.jdbc.Neo4jDataSource` as `javax.sql.DataSource`. == Causal clustering and bookmarks -The Neo4j JDBC Driver uses bookmarks by default to provide causal consistency when running against a Neo4j cluster. -Bookmarks are managed on the driver level itself, not on the connections spawned by an instance of the driver. -Thus, all connections that are spawned by one instance, will partake in the same causal chain of transactions. -Connections from different instances of the drivers will not use the same set of bookmarks and there is no built-in machinery that would enable this. +The Neo4j JDBC Driver uses bookmarks by default to provide causal consistency in all Neo4j deployments. +Bookmarks are managed on the driver level itself, not on the connections spawned by an instance of the driver, so all connections spawned by one instance will partake in the same causal chain of transactions. +Connections from different driver instances will not use the same set of bookmarks and there is no built-in machinery that would enable this. If you want or need this, you can directly access the `Neo4jDriver` type to retrieve the current set of known bookmarks and pass them to another driver instance. == Neo4j transactional metadata -Neo4j supports additional metadata for each ongoing transaction, see https://neo4j.com/docs/cypher-manual/current/clauses/transaction-clauses/#query-listing-transactions[SHOW TRANSACTIONS]. -As there is no explicit transaction object in the JDBC spec (a `Connection` is either in auto-commit or explicit commit mode, and all variations of a transaction entity are always third party implementations), the Neo4j JDBC driver needs another mechanism to make these configurable. +Neo4j supports attaching metadata to transactions, see https://neo4j.com/docs/cypher-manual/current/clauses/transaction-clauses/#query-listing-transactions[`SHOW TRANSACTIONS`]. +As there is no explicit transaction object in the JDBC specification, the Neo4j JDBC driver needs another mechanism to make these configurable. The JDBC driver provides the extension interface `Neo4jMetadataWriter`. -Our driver, the connection implementation and all statement variants can be unwrapped accordingly. -The configuration is additive, which means that -metadata configured for a driver instance will be used for all connections spawned from that driver, -connections can add additional metadata to those before creating new statements and statements themselves can also add their own metadata. -Metadata added on a statement has precedence over connection which in turn has precedence over the drivers: +Our driver, the connection implementation, and all statement variants can be unwrapped accordingly. +The configuration is additive: metadata configured for a driver instance will be used for all connections spawned from that driver, connections can add further metadata, and statements can also add their own metadata. +Metadata added on a statement has precedence over connection metadata which in turn has precedence over driver metadata: [source, java, tabsize=4] .Configuring transactional metadata @@ -51,19 +48,25 @@ The canonical URL format for the Neo4j JDBC Driver is jdbc:neo4j://:/?param1=value1¶m2=value2 ---- -The database name and any query parameters are optional and can be omitted. -All configuration arguments, can be passed either as query parameters or via a `java.util.Properties` object. +The database name and all query parameters are optional and can be omitted. +All configuration arguments can be passed either as query parameters or via a `java.util.Properties` object. The latter is sometimes defined by tooling for you. -With regard to authentication it's highly recommended to follow the JDBC spec, which discourages using any form of URL authentication. -All query parameters must be https://en.wikipedia.org/wiki/Percent-encoding[percent-encoded] if they contain special characters, e.g., `...?param1=space%20separated`. -TIP: There is no need to specify `bolt` or other additional prefixes in the URL, as a matter of fact: The driver does not support this. - It does however support adding details about the transport being used: - + - `neo4j+s` enables encryption and only accepts SSL certificates from the server that are signed by a known certificate authority. - `neo4j+ssc` enables encryption and accepts self-signed certificates (which must be added to the certificate store). +With regard to authentication, we recommend to follow the JDBC specification, which discourages using any form of URL authentication. +All query parameters must be https://en.wikipedia.org/wiki/Percent-encoding[percent-encoded] if they contain special characters, e.g. `...?param1=space%20separated`. -The driver accepts the following configuration arguments, either properties or as URL query parameters: +[TIP] +==== +The driver supports the following URI schemes, which tweak the security configuration: + +* `neo4j` - No encryption. +* `neo4j+s` - Enables encryption and only accepts SSL certificates from the server that are signed by a known Certificate Authority. +* `neo4j+ssc` - Enables encryption and accepts self-signed certificates (which must be added to the certificate store). + +`bolt` URI schemes are not supported. +==== + +The driver accepts the following configuration arguments, either as properties or as URL query parameters: .Configuration arguments |=== @@ -81,27 +84,27 @@ The driver accepts the following configuration arguments, either properties or a |`enableSQLTranslation` |`Boolean` -|Flag that enables automatic translation from SQL to Cypher (requires a translator on the classpath) +|Flag that enables automatic translation from SQL-to-Cypher (requires a translator on the classpath) |`false` |`cacheSQLTranslations` |`Boolean` -|Flag that enables caching of translations. SQL translations are not "free", parsing of SQL costs a bit of time, and so does Cypher rendering. In addition, we might up look up metadata to be able to project individual properties. If this takes to long, any translation might be cached. +|Flag that enables caching of translations. SQL translations are not "free": parsing of SQL costs a bit of time, and so does Cypher rendering. In addition, we might up look up metadata to be able to project individual properties. If this takes too long, translations may be cached. |`false` |`rewritePlaceholders` |`Boolean` -|Flag that allows you to use `?` as placeholders in *Cypher* statements (as required by JDBC). These will automatically be rewritten into `$1`, `$2` … `$n`, starting at 1, so that the numbering matches the 1-based JDBC index. -|Defaults to `true` when `enableSQLTranslation` is `false`, `false` otherwise +|Flag that allows you to use `?` as placeholder in *Cypher* statements (as required by JDBC). These will automatically be rewritten into `$1`, `$2` … `$n`, starting at 1, so that the numbering matches the 1-based JDBC index. +|`true` when `enableSQLTranslation` is `false`, `false` otherwise |`ssl` |`Boolean` -|An optional flag that is an alternative to `neo4j+s`. It can be used for example to programmatically enable the full SSL chain. +|Optional flag, alternative to `neo4j+s`. It can be used for example to programmatically enable the full SSL chain. |`null` |`sslMode` |`Enum` -|An optional configuration for fine-grained control over SSL configuration. Allowed values are `disable`, `require`, `verify-full`. +|Optional configuration for fine-grained control over SSL configuration. Allowed values are `disable`, `require`, `verify-full`. See >. |`null` |`user` @@ -124,36 +127,36 @@ The driver accepts the following configuration arguments, either properties or a a|The authentication scheme to use. *NOT RECOMMENDED* as URL query parameter for security reasons. Currently supported values are: * `basic` (default) for basic authentication. -* `none` for no authentication. The properties `user`, `password` and `authRealm` have no effect. * `bearer` for bearer authentication (SSO). `password` should be set to the bearer token; `user` and `authRealm` have no effect. * `kerberos` for kerberos authentication. Requires `password` to be set to the kerberos ticket; `user` and `authRealm` have no effect. +* `none` if authentication is disabled on the server. The properties `user`, `password`, `authRealm` have no effect. |`basic` |`useBookmarks` |`boolean` -|Enables bookmark management for full causal cluster support. This is enabled by default and the recommended setting for all scenarions that use a connection pool. +|Enables bookmark management for full causal cluster support. This is enabled by default and the recommended setting for all scenarios that use a connection pool. If you disable it, it will only be disabled for the specific connection. -Other connections retrieved from the driver instance are not affected, and the driver will still manage their bookmarks. +Other connections retrieved from the driver instance to the same or to other databases are not affected, and the individual connections will still manage their bookmarks. |`true` |=== == Getting a driver or a connection instance -NOTE: This section most likely only applies if you use the Neo4j JDBC Driver as part of application development in contrast to using it as part of front-end tool such as https://dbeaver.io[DBeaver], https://www.jetbrains.com/datagrip/[DataGrip] or UI-based ETL tools. +NOTE: This section likely only applies if you use the Neo4j JDBC Driver as part of application development in contrast to using it as part of front-end tool such as https://dbeaver.io[DBeaver], https://www.jetbrains.com/datagrip/[DataGrip] or UI-based ETL tools. The easiest way to acquire a connection is directly through the `java.sql.DriverManager`. [source, java, tabsize=4] -.Acquiring a JDBC connection towards Neo4j +.Acquiring a JDBC connection to a Neo4j server ---- include::{examplesDir}/Configuration.java[tag=cdm] ---- -While our connection implementation is thread-safe, it does—as dictated by the JDBC Spec—only allow one concurrent transaction per connection. -For a multi-thread application you want to use a connection pool. -There's https://github.com/brettwooldridge/HikariCP[HikariCP], but usually application server and containers or frameworks bring their own. -It's safe to use any of them, as the Neo4j JDBC Driver does not do internal pooling. +While our connection implementation is thread-safe, it allows only one concurrent transaction per connection (as dictated by the JDBC specification). +For a multi-thread application, use a connection pool. +There's https://github.com/brettwooldridge/HikariCP[HikariCP], but usually application server and containers/frameworks bring their own. +It's safe to use any of them, as the Neo4j JDBC Driver does no internal pooling. If you need access to an instance of the Neo4j driver itself, you can use the following approach: @@ -165,33 +168,33 @@ include::{examplesDir}/Configuration.java[tag=ddm] == Securing your connection by using SSL -The Neo4j JDBC Driver supports the same SSL option as the common Java driver does, with the same URL protocols, using `+s` or `+ssc` as additional indicators for the level of security that is required. +The Neo4j JDBC Driver supports the same SSL option of the common Java driver, with the same URL protocols, using `+s` or `+ssc` as additional indicators for the required level of security. The same configuration can also be achieved with a URL query parameter or an entry in the properties passed to the `DriverManager` or driver instance when asking for a connection. As long as you don't specify contradicting values, it's fine to combine both approaches. +[#ssl-mode] === Understanding the SSL mode -The possible settings are best explained using the available SSL modes. The following list is ordered by ascending security: -* `disable` default, "I don’t care about security and don’t want to pay the overhead for encryption" -* `require` "I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want." (Server must support encryption, no hostname / CA validation is done, this is only secure on private networks, without going through the "hassle" of proper certificates and should not really be used over public internet) -* `verify-full` "I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it’s the one I specify." +* `disable` -- (default), "I don't care about security and don't want to pay the overhead for encryption." +* `require` -- "I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want." (Server must support encryption, no hostname/CA validation is done. This saves the hassle of proper certificates and is only secure on private networks; it should not really be used over public networks.) +* `verify-full` -- "I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify." NOTE: The Neo4j JDBC Driver does not include revocation checks. -The most secure option can also be enabled by just using `ssl=true` either as query parameter or as property entry passed to the `DriverManager`. +The most secure option can also be enabled by setting `ssl=true` either as query parameter or as property entry passed to the `DriverManager`. This option corresponds to `neo4j+s`. -`require` on the other hand corresponds to `neo4j+ssc`. +On the other hand, `require` corresponds to `neo4j+ssc`. -You might wonder why using the additional enum: We might support additional modes in the future, such as letting the service decide about SSL, or being able to express a preference towards SSL without requiring it. +The additional enum allows us to possibly support additional modes in the future, such as letting the service decide about SSL, or being able to express a preference towards SSL without requiring it. -TIP: Neo4j cluster can offer both plain bolt connections and encrypted SSL connection or just one of them. So just because you can connect using `neo4j+s` does not mean that you cannot connect using just `neo4j` or vice versa. This is dependent on the setup of your database. Neo4js managed offering, https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j AuraDB], only supports encrypted connection, so you _must_ use `+s`, `ssl=true` or `sslMode=verify-full`. +TIP: Neo4j servers can offer both plain Bolt connections and encrypted SSL connection, or just one of them. The fact that you can connect using `neo4j+s` does not mean that you cannot connect using just `neo4j`, or viceversa. This is dependent on the server setup. https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j Aura], Neo4j's managed cloud service, only supports encrypted connections, so you _must_ use `+s`, `ssl=true`, or `sslMode=verify-full`. === Valid URLs -The following URLs are all valid +The following URLs are all valid: `neo4j+s://xyz.databases.neo4j.io`:: Use full verification with the `xzy` instance at AuraDB `neo4j://xyz.databases.neo4j.io?ssl=true`:: The same, but using the shorthand URL parameter @@ -200,16 +203,19 @@ The following URLs are all valid `neo4j+ssc://this.is.a.trustworthy.instance.for.sure.com`:: Trust whatever certificate and hostname there is, but do use SSL `neo4j://my-testing-instance.local`:: Use a plain connection. -We only refuse contradicting values when you use several configuration mechanism: +The driver only refuses contradicting configurations, such as: -* `+s` with `ssl=false` or `sslMode` set to `disable` -* `+ssc` with `ssl=false` or any `sslmode` not equal to `require` +* `+s` with `ssl=false`, or `sslMode` set to `disable` +* `+ssc` with `ssl=false`, or any `sslmode` not equal to `require` -In essence, you cannot express to use SSL and not use it at the same time. -The reason to offer several mechanism is that we want you to be able to use a fixed URL with dynamic query parameters or dynamic URLs or whatever way of configuring you prefer in a programmatic way. +Basically, you cannot ask to use SSL and not use it at the same time. +The driver offers several mechanism so that you can use a fixed URL with dynamic query parameters, or dynamic URLs, or whatever way of configuring you prefer in a programmatic way. === Using .dotenv files -When you sign up for https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j AuraDB] and create a database, you will be asked to download a text-file named similar to `Neo4j-9df57663-Created-2023-06-12.txt`. This is essentially a https://www.dotenv.org[.dotenv] file containing all necessary information required to connect to an aura Database. +When you create a https://neo4j.com/cloud/platform/aura-graph-database/[Neo4j Aura] instance, you will be asked to download a text-file named similar to `Neo4j-9df57663-Created-2023-06-12.txt`. +This is essentially a https://www.dotenv.org[.dotenv] file containing the information required to connect to the database. -These files can be directly used via `Neo4jDriver.fromEnv()`. This method exists in several overloads, which let you configure both filename and directory. Additionally, the builder behind it let's you configure options that are not usually contained in the files from AuraDB. +These files can be directly used via `Neo4jDriver.fromEnv()` (see xref:usage.adoc#connect-dotenv[Getting a connection via environment variables]). +This method exists in several overloads, which let you configure both filename and directory. +Additionally, the builder lets you configure options that are not contained in the files from Aura. diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/datatypes.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/datatypes.adoc index ecf8f879a..81add4415 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/datatypes.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/datatypes.adoc @@ -1,15 +1,16 @@ = Neo4j specific conversions -Neo4j does not offer all types used in the relational world. -For some of them we offer conversions that used to be helpful in frameworks such as https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j (SDN)] and we implement here in the exact same way, so you could use both SDN and this driver interchangeable. +Neo4j does not support all types used in the relational world. +For some of them we offer conversions that are also available in frameworks such as https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j (SDN)]. +Those conversions are available in this driver as well, so you could use both SDN and this driver interchangeably. -== Data types for Fixed-point arithmetic +== Data types for fixed-point arithmetic Neo4j does not support `BigInteger` and `BigDecimal`. -The only way to store them is as String and read them back into the corresponding type. -This is inline with SDN and OGM. +The only way to store them is as `String`, and to read them back into the corresponding type. +This is in line with SDN and OGM. -So any parameter of those types passed to `PreparedStatement` or `CallableStatement` will be stored as String, but can be equally read back through corresponding methods on the resultsets. +Any parameter of those types passed to `PreparedStatement` or `CallableStatement` will be stored as `String`, but can be read back through corresponding methods on the result sets. == SQL Date, Time and Timestamps @@ -17,4 +18,6 @@ So any parameter of those types passed to `PreparedStatement` or `CallableStatem `java.sql.Time`:: Maps to Cypher `LOCAL TIME` `java.sql.Timestamp`:: Maps to Cypher `LOCAL DATETIME` -For more precise mapping use a Neo4j `Value` instance with the appropriate type and `setObject` respectively `getObject`. \ No newline at end of file +For information on Cypher date types, see https://neo4j.com/docs/cypher-manual/current/values-and-types/temporal/[Temporal types]. + +For more precise a mapping, use a Neo4j https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Value.html[`Value`] instance with the appropriate type and its methods `setObject` and `getObject`. diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/distribution.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/distribution.adoc index de14b1e51..9f4ba0afd 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/distribution.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/distribution.adoc @@ -8,27 +8,29 @@ The driver consists of 3 modules: This is the actual JDBC implementation for Neo4j {artifact-id-spi}:: -This is the SPI for the default SQL to Cypher translation implementation and for any further or custom implementation. +This is the SPI for the default SQL-to-Cypher translation implementation and for any further or custom implementation. {artifact-id-impl}:: -This is the *optional* default implementation of a SQL to Cypher translator. +This is the default and *optional* implementation of an SQL-to-Cypher translator. +It provides a somewhat opinionated approach of translating SQL statements into semantically equivalent Cypher statements. -IMPORTANT: If you just want to use the Neo4j JDBC Driver for running Cypher statements, the only module you need to think about is `{group-id}:{artifact-id}`, and in case you are developing in an environment with proper dependency management, this is all you need to declare. +IMPORTANT: If you just want to use the Neo4j JDBC Driver to run Cypher statements, you only need the module `{group-id}:{artifact-id}`. As long as you are developing in an environment with proper dependency management, this is all you need to declare. -While the translator SPI is a required dependency and pulled in via Maven or Gradle, the actual implementation is not. -This allows for +While the translator SPI is a required dependency and gets pulled in via Maven or Gradle, the actual implementation is not. +This allows for: -- The possibility to opt-out of having additional dependencies in all cases you are not using SQL to Cypher translation +- Opting out of having additional dependencies if you don't need the SQL-to-Cypher translation - Requiring different JDK baselines or licensing modules for our implementations -- Allowing you to create alternative ways of creating a translator +- Allowing you to create alternative translators === Dependencies -We offer two "dependency free" modules, that shade everything into one binary artifact. -Those bundles should cover a lot of tooling, and we discuss those bundles below in <>. -In case you are interested in the actual dependencies of the driver, those are the direct, compile-time dependencies of the driver: +There are two "dependency-free" modules, which shade everything into one binary artifact each. +Those bundles cover a lot of tooling (see <>). -.Dependency tree of the core driver. +The driver's direct, compile-time dependencies are listed below: + +.Dependency tree of the core driver [source,text] ---- org.neo4j:neo4j-jdbc @@ -49,8 +51,7 @@ org.neo4j:neo4j-jdbc == Available bundles All bundles of the Neo4j JDBC Driver are distributed on Maven Central. -The bundles have different characteristics that we will explain the following sections. -Depending on your use-case or your environment you pick one or the other bundle, but not several at once. +The bundles have different characteristics: depending on your use-case or your environment, you can pick one or the other bundle, but not both at once. === Individual components @@ -59,7 +60,7 @@ Pick this distribution if you have an application that uses dependencies managem IMPORTANT: We offer a Maven BOM project, which you should import when using the individual modules, so that you have always a consistent set of versions. The coordinates are `{group-id}:neo4j-jdbc-bom`. -For Maven you will want to use this dependency declaration: +For Maven, use this dependency declaration: [source,xml,subs="verbatim,attributes"] .Maven dependency for the core Neo4j JDBC Driver artifact @@ -81,7 +82,7 @@ dependencies { } ---- -If you want to use the SQL to Cypher translation from Neo4j, you need to add the following dependency in your Maven build: +If you want to use the SQL-to-Cypher translation from Neo4j, you need to add the following dependency in your Maven build: [source,xml,subs="verbatim,attributes"] .Maven dependency for the default SQL to Cypher translator @@ -106,19 +107,20 @@ dependencies { [#small_bundle] === Small bundle -Pick this distribution if you work with ETL tools or tooling for relational databases that allow adding JDBC driver only as single artifacts or that make it unnecessary hard to add additional jars. -This bundle does not contain the default SQL to Cypher translator! +Pick this distribution if you work with ETL tools or tooling for relational databases that allow adding the JDBC driver only as single artifacts or that make it unnecessary hard to add additional jars. +This bundle does not contain the default SQL-to-Cypher translator! The coordinates of this bundle are `{group-id}:{artifact-id-bundle}` and you can download it from Maven central: https://repo.maven.apache.org/maven2/org/neo4j/{artifact-id-bundle}/{version}/{artifact-id-bundle}-{version}.jar -TIP: All bundles can certainly be used as a normal project dependency as well. This might be useful for example if your project depends on another, potentially conflicting Netty version. +TIP: All bundles can be used as normal project dependencies as well. +This might be useful for example if your project depends on a different, potentially conflicting Netty version. === Full bundle -This bundle contains the default SQL to Cypher translator. +This bundle contains the default SQL-to-Cypher translator. Otherwise, it is identical to the <>. -Its coordinates are `{group-id}:{artifact-id-full-bundle}` and you can download it from central here: +Its coordinates are `{group-id}:{artifact-id-full-bundle}` and you can download it from Maven central here: https://repo.maven.apache.org/maven2/org/neo4j/{artifact-id-full-bundle}/{version}/{artifact-id-full-bundle}-{version}.jar @@ -129,4 +131,4 @@ We also ship a very experimental bundle that we call `text2cypher`: https://repo.maven.apache.org/maven2/org/neo4j/{artifact-id-text2cypher-bundle}/{version}/{artifact-id-text2cypher-bundle}-{version}.jar -See <> for more information about the text2cypher translator. +See xref:text2cypher.adoc[] more information. diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/index.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/index.adoc index 4977e68c4..9288cd42c 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/index.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/index.adoc @@ -38,15 +38,9 @@ include::README.adoc[tag=abstract] == Introduction include::README.adoc[tag=introduction] -== About this documentation - -In this documentation we will focus on getting and configuring the Neo4j JDBC Driver and enabling optional features. -We will discuss the design choices made in the metadata retrieval and how we map Neo4j labels to tables as well as our opinionated choices with regards the automatic SQL to Cypher translation. - -While we do provide runnable examples that due to their nature will show how to use JDBC in general, this is not a documentation about how to correctly use JDBC as an API. ifndef::site-gen-antora[] -include::quickstart.adoc[leveloffset=+1] +include::usage.adoc[leveloffset=+1] include::distribution.adoc[leveloffset=+1] diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/metadata.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/metadata.adoc index 419b4d86d..91b559fd3 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/metadata.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/metadata.adoc @@ -4,16 +4,14 @@ All the methods on connection level dealing with metadata and information about the available content provide information in terms that are defined in the SQL standard, including catalogs and schemas. +In most relational databases, a catalog is equivalent to a specific database on a server or cluster, and the schema refers to the collection of tables, views and procedures in that catalog. + From the SQL 1992 standard (find an archived copy http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt[here]): -> (4.12) Catalogs are named collections of schemas in an SQL-environment. An -SQL-environment contains zero or more catalogs. A catalog con- -tains one or more schemas, but always contains a schema named -INFORMATION_SCHEMA that contains the views and domains of the -Information Schema. +> (4.12) Catalogs are named collections of schemas in an SQL-environment. An SQL-environment contains zero or more catalogs. A catalog contains one or more schemas, but always contains a schema named INFORMATION_SCHEMA that contains the views and domains of the Information Schema. -We decided in https://github.com/neo4j/neo4j-jdbc/discussions/55[55] to not support catalogs at the beginning, so any metadata result set will return literal `null` when asked for the catalog of a database object. -No metadata method does support filtering on a non-null catalog parameter and no catalog specifier can be used in a query. +This driver does not support catalogs (see https://github.com/neo4j/neo4j-jdbc/discussions/55[discussion 55]), so any metadata result set will return literal `null` when asked for the catalog of a database object. +No metadata method supports filtering on a non-null catalog parameter and no catalog specifier can be used in a query. Future developments might use catalogs to describe composite databases, in essence listing the constituents of the composite database defined in the connection. The same standard defines schemas as follows: @@ -38,22 +36,22 @@ Labels will be reported as table objects with the `TABLE_TYPE` being literal `TA === Summary -* Catalog: Always `null`, trying to filter on anything non-null does not yield results -* Schema: Always `public`, filtering on `public` and literal will yield result, anything else won't. +* Catalog: Always `null`; filtering on anything non-null yields no results. +* Schema: Always `public`; filtering on `public` and literal will yield result, anything else won't. * Table descriptors: Reported as `TABLE` in the `TABLE_TYPE` column. == Labels to tables -The CLG and Langstar groups speak about "Node type combinations" and gravitates towards "open node type semantics" in the GQL standard: +The CLG and Langstar groups speak about "Node type combinations" and gravitate towards "open node type semantics" in the GQL standard: > Node type combinations is a less permissive form of open node type semantics. The idea of node type combinations is that nodes also conform to a graph type if they are not of one of the node types in the node type set of the graph type, but of a node type that is an intersection of (a subset of) the node type in a node type set of the graph type. -An example for their proposal can be seen https://urban-adventure-ov6lvqn.pages.github.io/?s=eyJjaGVja2VkIjp0cnVlLCJncmFwaFR5cGVJbnB1dCI6Iig6QSB7YTo6SU5UfSAuLi4pXG4oOkIge2I6OklOVH0gLi4uKSIsImdyYXBoSW5wdXQiOiIoYWI6QSZCIHthOjUsIGI6NX0pIiwib3B0aW9ucyI6WyJPcGVuIGVkZ2UgdHlwZXMgYWxsb3dlZCIsIklOSjEiLCJPcGVuIG5vZGUgdHlwZXMgYWxsb3dlZCIsIk9wZW4gZ3JhcGggdHlwZSBzZW1hbnRpY3MgKEdUUzIpIiwiT3BlbiBlbmRwb2ludCB0eXBlcyBhbGxvd2VkIiwiQ2xvc2VkIGVkZ2UgdHlwZXMgZGlzYWxsb3dlZCIsIkNsb3NlZCBub2RlIHR5cGVzIGRpc2FsbG93ZWQiLCJDbG9zZWQgZW5kcG9pbnQgdHlwZXMgZGlzYWxsb3dlZCIsIlNpbmdsZSBsYWJlbCBhbGxvd2VkIChFUEEyKSIsIkVUSTEiLCJOVEkyIl19[here]. +An example for their proposal can be found https://urban-adventure-ov6lvqn.pages.github.io/?s=eyJjaGVja2VkIjp0cnVlLCJncmFwaFR5cGVJbnB1dCI6Iig6QSB7YTo6SU5UfSAuLi4pXG4oOkIge2I6OklOVH0gLi4uKSIsImdyYXBoSW5wdXQiOiIoYWI6QSZCIHthOjUsIGI6NX0pIiwib3B0aW9ucyI6WyJPcGVuIGVkZ2UgdHlwZXMgYWxsb3dlZCIsIklOSjEiLCJPcGVuIG5vZGUgdHlwZXMgYWxsb3dlZCIsIk9wZW4gZ3JhcGggdHlwZSBzZW1hbnRpY3MgKEdUUzIpIiwiT3BlbiBlbmRwb2ludCB0eXBlcyBhbGxvd2VkIiwiQ2xvc2VkIGVkZ2UgdHlwZXMgZGlzYWxsb3dlZCIsIkNsb3NlZCBub2RlIHR5cGVzIGRpc2FsbG93ZWQiLCJDbG9zZWQgZW5kcG9pbnQgdHlwZXMgZGlzYWxsb3dlZCIsIlNpbmdsZSBsYWJlbCBhbGxvd2VkIChFUEEyKSIsIkVUSTEiLCJOVEkyIl19[here]. -We therefor compute node types in a similar way: +This driver therefore compute node types in a similar way: * Single label nodes will map naturally to a table name, the single label will become the table name -** The label name will be taken as is and will be case-sensitive. So if you have three labels `Movie`, `movie`, `MOVIE` you will see three tables in the meta-data -** This is in-line with the default SQL to Cypher translation -* Node type combinations will map to table names composed as `label1_label2`, sorting the labels alphabetically, so that it is independent of the way Neo4j will return those -* Property sets for these Node type combinations will then be computed +** The label name will be taken as is and will be case-sensitive. The labels `Movie`, `movie`, `MOVIE` will result in three tables in the metadata +** This is in line with the default SQL-to-Cypher translation +* Node type combinations will map to table names composed as `label1_label2`, sorting the labels alphabetically to make them independent of the order Neo4j returns them +* Property sets for these node type combinations will then be computed diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/migrating.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/migrating.adoc index fb5f5d76f..7222436f1 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/migrating.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/migrating.adoc @@ -2,11 +2,11 @@ There are some other JDBC drivers for Neo4j, under various licenses and with varying features and capabilities. In the following we outline possible migration processes. -The basic usage patterns for all JDBC drivers for Neo4j are very similar, in the end it's "just" using a JDBC compliant driver, and you would use it as described in the original https://docs.oracle.com/javase/tutorial/jdbc/basics/index.html[Java tutorial about JDBC]. +The basic usage patterns for all JDBC drivers for Neo4j are very similar: in the end, it's "just" about using a JDBC compliant driver, and you would use it as described in the original https://docs.oracle.com/javase/tutorial/jdbc/basics/index.html[Java tutorial about JDBC]. == Migrating from version 4 or 5 of this driver -Version 5 and 4 of the Neo4j JDBC Driver have been mainly developed by http://larus-ba.it/[Larus BA, Italy], a certified consulting and integration solutions partner for Neo4j. Thank you so much for all your work. +Version 5 and 4 of the Neo4j JDBC Driver have been mainly developed by http://larus-ba.it/[Larus BA, Italy], a certified consulting and integration solutions partner for Neo4j. WARNING: The most important change that you need to make is removing the dependency on `org.neo4j:neo4j-jdbc-bolt`. You need to replace it with `{group-id}:{artifact-id}` or one of the bundles we provide, see xref:distribution.adoc#available_bundles[available bundles]. @@ -26,25 +26,23 @@ The following URLs behave the same but must be rewritten: * `jdbc:neo4j:neo4j+s://:/` becomes `jdbc:neo4j+s://:/` * `jdbc:neo4j:neo4j+ssc://:/` becomes `jdbc:neo4j+ssc://:/` -The following configuration properties are *not* supported and have no other replacement: +The following configuration properties are *not* supported and have no replacement: * `leaked.sessions.logging` * `readonly` * `usebookmarks` - -We don't offer any build-in retry mechanism, so the corresponding setting don't have an effect: - -* `max.transaction.retry.time` +* `max.transaction.retry.time` (this driver has no built-in retry mechanism) As with any persistent database connection you want to cater for failed transactions. -We made good experience with https://resilience4j.readme.io[resilience4j] which does fit in well with common Java frameworks, such as Spring Boot. +We made good experience with https://resilience4j.readme.io[resilience4j] which fits in well with common Java frameworks, such as Spring Boot. The following properties can be achieved with standardized JDBC settings: -* `encryption`: Use the appropriate transport scheme (`neo4j`, `neo4j+s` or `neo4j+ssc`) -* `autocommit`: Use `java.sql.Connection.setAutoCommit` +* `encryption` -- Use the appropriate transport scheme (`neo4j`, `neo4j+s` or `neo4j+ssc`) +* `autocommit` -- Use `java.sql.Connection.setAutoCommit` -Connection pooling can be achieved with any JDBC compliant connection pool, the following properties don't have an effect: +Connection pooling can be achieved with any JDBC-compliant connection pool. +The following properties have no effect: * `max.connection.lifetime` * `max.connection.poolsize` @@ -55,17 +53,19 @@ The following properties just have different names: The following properties can be achieved using a different URL: -* `database` is now part of the URL, instead of specifying `jdbc:neo4j:neo4j+s://foobar.io:7687?database=abc` you would use the database name as path segment in the url, such as: `jdbc:neo4j+s://foobar.io:7687/abc` +* `database` is now part of the URL: instead of specifying `jdbc:neo4j:neo4j+s://foobar.io:7687?database=abc` you would use the database name as path segment in the url, such as: `jdbc:neo4j+s://foobar.io:7687/abc` -In case your tooling requires to use a concrete driver class: This JDBC driver has only `org.neo4j.jdbc.Neo4jDriver`. +If your tooling requires to use a concrete driver class, this JDBC driver has only `org.neo4j.jdbc.Neo4jDriver`. If you depend on a `javax.sql.DataSource`, we provide `org.neo4j.jdbc.Neo4jDataSource`. === Flattening -While the Neo4j JDBC Driver does not support `flatten` option, it can emulate its effect. -`flatten` did unnest returned nodes and relationships by providing all their properties as individual columns. +While the Neo4j JDBC Driver does not support the `flatten` option, it can emulate its effect. +`flatten` un-nests returned nodes and relationships by providing all their properties as individual columns. -If you enable automatic SQL to Cypher translation (See xref:s2c_introduction[xrefstyle=short]), any `*`-select will inspect whether it affects nodes or relationship and will unnest their properties, so that a `SELECT * FROM Movie m` will effectively become `MATCH (m:Movie) RETURN m.title AS title, m.released AS released`. Read more about this topic in xref:s2c_star_selects[xrefstyle=short]). +If you enable automatic xref:sql2cypher.adoc#s2c_introduction[SQL to Cypher translation], any `*`-select will inspect whether it affects nodes or relationships and will un-nest their properties, so that a `SELECT * FROM Movie m` will effectively become `MATCH (m:Movie) RETURN m.title AS title, m.released AS released`. +For more information, see xref:sql2cypher.adoc#s2c_star_selects[Star-Selects]. In case you want to access the actual node, return the whole table alias or just use Cypher. -The Neo4j JDBC Driver does support complex object types as return types. + +The Neo4j JDBC Driver supports complex object types as return types. diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/quickstart.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/quickstart.adoc deleted file mode 100644 index 6ff13c187..000000000 --- a/docs/src/main/asciidoc/modules/ROOT/pages/quickstart.adoc +++ /dev/null @@ -1,114 +0,0 @@ -= Quickstart - -Add the JDBC driver to your application, for example as a Gradle dependency: - -[source, kotlin, subs="verbatim,attributes"]] -.Using the full bundle as a runtime dependency inside a Gradle based project ----- -dependencies { - runtimeOnly({group-id}:{artifact-id-full-bundle}:{version}) -} ----- - -With that in place, you can use the JDBC driver for Neo4j as you would do with any other JDBC driver. - -[source, java, tabsize=4] -.Acquire a connection using the JDBC driver and execute a query ----- -include::{examplesDir}/Quickstart.java[tag=pt1] ----- -<.> Get a JDBC connection, no need to do any class loading beforehand, the driver will be automatically registered -<.> Create a reusable statement -<.> Immediate execute a query on it -<.> Iterate the results like you would do with any other JDBC result set -<.> JDBC is index 1 based -<.> JDBC also allows retrieval of result columns by name; the Neo4j JDBC driver also supports complexe objects such as lists - -In the example above we used Neo4j's lingua franca, https://neo4j.com/docs/getting-started/cypher-intro/[Cypher], to query Neo4j. -The Neo4j JDBC Driver has limited support for using SQL as well. -It will do so automatically or on a case by case basis. -For the latter you can use `java.sql.Connection#nativeSQL(String)` and use the result in your queries -For automatic translation instantiate the driver using an additional URL parameter, `sql2cypher` set to `true`. -The following example shows how: - -[source, java, tabsize=4, indent=0] -.Configure the JDBC driver to automatically translate SQL to cypher. ----- -include::{examplesDir}/Quickstart.java[tag=pt2] ----- -<.> This SQL query will be translated into the same Cypher query shown before, the remainder of the method is identical to before. - -TIP: In case any tooling asks you for the name of the concrete driver class, it is: `org.neo4j.jdbc.Neo4jDriver`. - -We will cover the SQL to Cypher translation in detail later in this manual. - -The JDBC Spec does not support named parameters, only index based parameters. -Indexes start at `1` in SQL. -So for all `PreparedStatement` instances you would want to specify your parameters like this: - -[source, java, tabsize=4, indent=0] -.Using parameters with a `PreparedStatement` ----- -include::{examplesDir}/NamedParameters.java[tag=index] ----- - -This is independent of the SQL to Cypher translation mechanism: - -[source, java, tabsize=4, indent=0] -.Using parameters with a `PreparedStatement` (SQL variant) ----- -include::{examplesDir}/NamedParameters.java[tag=index-sql] ----- - -You can downcast the `PreparedStatement` to `Neo4jPreparedStatement`. -This JDBC extension will let you use named parameters: - -[source, java, tabsize=4, indent=0] -.Using named parameters with the `Neo4jPreparedStatement` ----- -include::{examplesDir}/NamedParameters.java[tag=index-np] ----- - -== Getting a connection via environment variables - -If you are happy to depend directly on `org.neo4j.jdbc.Neo4jDriver` and want to get a connection as easy as possible, you might want to use `fromEnv`: - -[source, java, tabsize=4, indent=0] -.Get a connection from the environment. ----- -include::{examplesDir}/Quickstart.java[tag=pt3] ----- -<.> Notice how we directly use the concrete driver class here and how the methods returns an optional: If we don't find the required properties, we can't create a connection. - -The `fromEnv` looks for a couple of specific system environment variables and it will be true to the principles of the https://12factor.net[12 factor app] while doing so: - -- It will look in the System environment first -- It will then look for a file named `.env` in the current working directory - -There are overloads that let you configure the directory and the filename to look for. -The properties we support are as follows: - -`NEO4J_URI`:: The address or URI of the instance to connect to -`NEO4J_USERNAME`:: Optional username -`NEO4J_PASSWORD`:: Optional password -`NEO4J_SQL_TRANSLATION_ENABLED`:: Optional flag to enable full SQL to Cypher translation, defaults to `false` - -NOTE: Information from both the System environment and the .env files are combined. If for example `NEO4J_SQL_TRANSLATION_ENABLED` is in the System environment but not in the .env file, it will still be picked up. - -This feature is especially useful with Neo4j AuraDB. -When creating a new AuraDB instance you are required to download an `.env` file and you can directly use it with the Neo4j JDBC Driver like this: - -[source,java] -.Using a .env file from AuraDB ----- -try ( - var con = Neo4jDriver.fromEnv("Neo4j-cb3d8b2d-Created-2024-02-14.txt") - .orElseThrow(); - var stmt = con.createStatement(); - var movies = stmt.executeQuery("MATCH (n:Movie) RETURN n.title AS title") -) { - while (movies.next()) { - System.out.println(movies.getString("title")); - } -} ----- \ No newline at end of file diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/sql2cypher.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/sql2cypher.adoc index 01e42f489..d6b8293f1 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/sql2cypher.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/sql2cypher.adoc @@ -1,48 +1,47 @@ +[#s2c_introduction] = SQL to Cypher translation -[#s2c_introduction] == Introduction -The translation from SQL queries to Cypher is an optional feature of this driver that consists of two parts: +The translation of queries from SQL to Cypher is an optional feature of this driver and it consists of two parts: -- The translator SPI, which you find in the module `{group-id}:neo4j-jdbc-translator-spi`. -It consists of two interfaces, `SqlTranslatorFactory` and the actual `SqlTranslator`. +- The translator SPI, located in the module `{group-id}:neo4j-jdbc-translator-spi`. +It consists of two interfaces: `SqlTranslatorFactory` and the actual `SqlTranslator`. - A concrete implementation of this SPI, published as `{group-id}:neo4j-jdbc-translator-impl`. -The latter is covered in "xref:s2c[]" and bundled as the "full bundle", described in xref:available_bundles[xrefstyle=short]. -The former is provided for two reasons: It allows us to distribute the driver with and without the bundled, default translator and can be an option for you to run your custom translator. +The latter is covered in "xref:s2c[]" and available in the "full bundle", described in xref:distribution.adoc#available_bundles[Available bundles]. +The former is provided for two reasons: it allows us to distribute the driver with and without the bundled, default translator and allows you to run your custom translator. -Translators can be chained, and you can have as many translators on the classpath as you want. -They will be ordered by a configurable precedence with our default implementation having the lowest precedence. -Thus, you can have for example a custom translator that takes care of a fixed set of queries, and if it cannot translate another, it will just be passed down to our implementation. +Translators can be chained, and there can be as many translators on the classpath as you want. +Their precedence is configurable, with our default implementation having the lowest precedence. +Thus, you can for example have a custom translator that takes care of a fixed set of queries and, if it receives a query it cannot translate, it will pass it down to our implementation. -Translating arbitrary SQL queries to Cypher is an opinionated task as there is no right way to map table names to objects in the graph: A table name can be used as is as a label, you might want to transform it to a singular form etc. And then we haven't even started how to map relationships: Do you want to have relationship types derived from a join table, a join column (in that case, which one?) or the name of a foreign key? +Translating arbitrary SQL queries to Cypher is an opinionated task, as there is no right way to map table names to graph objects: a table name can be used as-is as a label, or may be transformed into a singular form, etc. Mapping relationships is even trickier: should relationship types derive from a join table, a join column (in that case, which one?), or a foreign key? -We made some assumptions that we find to match various use cases and instead of providing configuration and more code to cater for all scenarios, we offer the possibility to write your own translation layer. +We believe our assumptions are appropriate for various use cases and instead of providing configuration to cater for all scenarios, we offer the possibility to write your own translation layer. The driver will use the standard Java service loader mechanism to find an implementation of the SPI on the module- or classpath. -NOTE: Some tools like Tableau use a class-loader that won't let the driver use the standard Java service loader mechanism. -For these scenarios we provide an additional configuration property named `translatorFactory`. -Set this to `DEFAULT` for directly loading our default implementation or to a fully-qualified classname for any other factory. -Be aware that either our default implementation or your custom one needs to be on the classpath nevertheless. +NOTE: Some tools (like Tableau) use a class-loader that won't let the driver use the standard Java service loader mechanism. +For these scenarios, we provide an additional configuration property named `translatorFactory`. +Set this to `DEFAULT` to directly load our default implementation or to a fully-qualified classname for any other factory. +*Be aware* that either our default implementation or your custom one must be on the classpath. == Translating SQL to Cypher -There's only one requirement to enable SQL to Cypher translation: -You have to have one module implementing the SPI on the classpath. -This is *automatically* the case if you use the full-bundle under those coordinates: `{group-id}:{artifact-id-full-bundle}`. -In that case, you *don't* need to add any other dependencies. -If you use the individual distribution or the "small" bundle `{group-id}:{artifact-id-bundle}` you must add the following artifact: `{group-id}:{artifact-id-impl}`. +There's only one requirement to enable the SQL-to-Cypher translation: you have to have one module implementing the SPI on the classpath. +This is *automatically* the case if you use the full-bundle (`{group-id}:{artifact-id-full-bundle}`). +In that case, you *don't* need to add any other dependency. +If you use the individual distribution or the "small" bundle `{group-id}:{artifact-id-bundle}`, you must add the artifact `{group-id}:{artifact-id-impl}`. The implementation will be automatically loaded. -If you use the translation on a case-by-case basis, it will be lazily loaded, that is: No additional classes are touched or loaded into memory. +If you use the translation on a case-by-case basis, it will be lazily loaded (i.e no additional classes are touched or loaded into memory). If you configure automatic translation for all statements, the implementation will be eagerly loaded. -There are no further configuration option with regard to loading the implementation. +There are no further configuration options with regard to loading the implementation. === On a case-by-case basis The translator can be used on a case-by-case basis through the official JDBC API `nativeSQL`, which you find on the `java.sql.Connection` class. -With the following imports +With the following imports: [source, java, tabsize=4] ---- @@ -58,18 +57,16 @@ include::{examplesDir}/SQLTranslator.java[tag=pt1] === For all queries -If you open the connection to your Neo4j instance using `enableSQLTranslation=true` either as URL parameter or configuration property, all statements will be translated from SQL to Cypher on your behalf. -If you configure the driver in that way, the translator will be eagerly loaded +If you open the connection to your Neo4j instance using `enableSQLTranslation=true` either as URL parameter or configuration property, all statements will be translated from SQL to Cypher. +If you configure the driver in this way, the translator will be eagerly loaded. [source, java, tabsize=4, indent=0] ---- include::{examplesDir}/SQLTranslator.java[tag=pt2] ---- -Sometimes it maybe is necessary to fall back to Cypher for some statements. -You might want to use some constructs that you cannot express with SQL or our default translator cannot handle the SQL necessary. -We offer a special comment that you can use as a hint in your statement that will stop automatic translation: `/*+ NEO4J FORCE_CYPHER */`. -Use it like this: +Sometimes you may need to fall back to Cypher for some statements, either to use constructs that you cannot express with SQL, or because our default translator cannot handle your query. +We offer a special comment that you can use as a hint in your statement to stop automatic translation: `/*+ NEO4J FORCE_CYPHER */`. [source, java, tabsize=4, indent=0] ---- @@ -78,7 +75,7 @@ include::{examplesDir}/SQLTranslator.java[tag=force-cypher] === Possible error scenarios -A `NoSuchElementException` with a message of `No SQL translators available` will be thrown when there is no implementation of the SQL to Cypher translator available, and you either use `java.sql.Connection.nativeSQL` or enable automatic translation. The exception will be thrown when you access the method or eagerly on opening a connection in the latter case. +A `NoSuchElementException` with a message of `No SQL translators available` will be thrown when there is no implementation of the SQL to Cypher translator available, and you either used `java.sql.Connection.nativeSQL` or enabled automatic translation. The exception will be thrown when you access the method or eagerly on opening a connection in the latter case. [#s2c] == Using the default translator @@ -87,9 +84,9 @@ A `NoSuchElementException` with a message of `No SQL translators available` will Our default translator uses the OSS parser from https://www.jooq.org[jOOQ], which supports a broad spectrum of SQL dialects already. We picked the generic, default dialect of jOOQ as our default dialect, but you can overwrite this in the SQL to Cypher configuration using the parameter `s2c.sqlDialect` with one of the supported dialects listed in <>. -`POSTGRES` might be a good choice for several integrations. +`POSTGRES` can be a good choice for several integrations. -Bear in mind though that any shortcomings in the translation are probably not due to the lack of parser support, but due to the lack of an obvious, semantically equivalent Cypher construct. +Bear in mind though that any shortcomings in the translation are probably not due to a lack in the parser, but due to the lack of an obvious, semantically equivalent Cypher construct. That means we might be able to parse a certain piece of SQL, but are unable to translate in into something meaningful that Neo4j can understand without additional, contextual information. @@ -103,65 +100,66 @@ They must be prefixed with `s2c` in the URL or config options: |Name |Meaning|Default |`parseNameCase` -|Whether to parse table names as is or not -|As is +|Whether to parse table names as is or not. +|`true` |`tableToLabelMappings` -|A map from table names to labels +|A map from table names to labels. |An empty map |`joinColumnsToTypeMappings` -|A map from column names to relationship type names +|A map from column names to relationship types. |An empty map |`prettyPrint` -|Whether to format the generated Cypher or not +|Whether to format the generated Cypher or not. |`true` |`alwaysEscapeNames` -|Whether to always escape names -|Unless explicitly configured `false` when pretty printing is on, otherwise true +|Whether to always escape names. +|Unless explicitly configured `false` when pretty printing is on, otherwise `true`. |`sqlDialect` -|Which dialect to use when parsing, supported values are `POSTGRES`, `SQLITE`, `MYSQL`, `H2`, `HSQLDB`, `DERBY` and `DEFAULT` +|Which dialect to use when parsing. Supported values are `POSTGRES`, `SQLITE`, `MYSQL`, `H2`, `HSQLDB`, `DERBY` and `DEFAULT`. |`DEFAULT` |=== -Here are a couple of examples (Note that we are using the `properties` config to avoid terrible long URLs in this documentation, however, all the attributes can be specified via URL, too): +The next few examples use the `properties` config to avoid terrible long URLs in this documentation, but all the attributes can be specified via URL as well. [source, java, tabsize=4, indent=0] -.Disable pretty printing, only escape if necessary, configure dedicated table mappings +.Disable pretty printing; only escape if necessary; configure dedicated table mappings ---- include::{examplesDir}/SQLTranslator.java[tag=config1] ---- -In the next example we parse the table names into upper case, which might be helpful in some situations: - [source, java, tabsize=4, indent=0] -.Upper case parsing +.Parse table names into upper case ---- include::{examplesDir}/SQLTranslator.java[tag=config2] ---- -Here we change the prefix of parameters (they are still going only by index in SQL) and add mappings for join columns: +Named parameter syntax in the SQL parser defaults to `:name` (such as supported by Oracle, JPA, Spring, a colon followed by a name). +The following example changes that prefix to `$` (the same prefix that Cypher uses): [source, java, tabsize=4, indent=0] -.Disable pretty printing, only escape if necessary, configure dedicated table mappings +.Change parameters prefix and add mappings for join columns ---- include::{examplesDir}/SQLTranslator.java[tag=config3] ---- +This is helpful when a tool generates names like that and does not allow customization. + [#s2c_supported_statements] === Supported statements The following statements are all under tests and describe what you can expect from the default translation layer: -include::translator/simple.adoc[leveloffset=+3] +include::translator/simple.adoc[leveloffset=+2] -include::translator/expressions.adoc[leveloffset=+3] +include::translator/expressions.adoc[leveloffset=+2] -include::translator/predicates.adoc[leveloffset=+3] +include::translator/predicates.adoc[leveloffset=+2] -include::translator/joins.adoc[leveloffset=+3] +include::translator/joins.adoc[leveloffset=+2] -include::translator/dml.adoc[leveloffset=+3] +include::translator/dml.adoc[leveloffset=+2] diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/syntax.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/syntax.adoc index c7b41c8b4..59c581710 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/syntax.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/syntax.adoc @@ -6,7 +6,7 @@ This section deals with conventions for indexing and naming parameters in all ty === General syntax -You can invoke stored procedures as follows +You can invoke stored procedures as follows: Common JDBC syntax:: `{? = call db.index.fulltext.queryNodes(?, ?)}` @@ -19,12 +19,12 @@ Return (only applicable for functions):: === Named parameters -Our callable statement implementation (`org.neo4j.jdbc.Neo4jCallableStatement`) does support named parameters. As per JDBC spec those are not named placeholders in a query- or statement-string, but the actual, formal parameter for the stored procedures to be called. -We support both the `$` and the colon syntax, that is you might use either `$name` or `:name`. +Our callable statement implementation (`org.neo4j.jdbc.Neo4jCallableStatement`) does support named parameters. As per the JDBC specification, those are not named placeholders in a query- or statement-string, but the actual, formal parameters for the stored procedures to be called. +We support both the `$` and the colon syntax (i.e. either `$name` or `:name`). -The assigment `{? = call xxx()}` will be rewritten into `call xxx() yield *`, and `{$x = call xxx()}` will be rewritten as `call xxx() yield x`. +The assigment `{? = call xxx()}` will be rewritten to `call xxx() yield *`, and `{$x = call xxx()}` will be rewritten to `call xxx() yield x`. === Result sets of callable statements -When you execute a callable statement via ´executeQuery`, you must use the result set returned. -If you just use `execute`, we assume that the underlying procedure does only return one row and you use the getters on the statement itself. \ No newline at end of file +When you execute a callable statement via `executeQuery`, you must use the result set returned. +If you just use `execute`, we assume that the underlying procedure returns only one row and that you use the getters on the statement itself. diff --git a/docs/src/main/asciidoc/modules/ROOT/pages/text2cypher.adoc b/docs/src/main/asciidoc/modules/ROOT/pages/text2cypher.adoc index 0066ba78f..6865a300f 100644 --- a/docs/src/main/asciidoc/modules/ROOT/pages/text2cypher.adoc +++ b/docs/src/main/asciidoc/modules/ROOT/pages/text2cypher.adoc @@ -12,42 +12,72 @@ If you add this translator to the classpath or use the < Notice how we directly use the concrete driver class here and how the methods return an optional: no connection can be created if the required connection variables are not found. + +The `fromEnv` method looks for a few specific system environment variables and it adheres to the https://12factor.net[12 factor app] principles: + +- First, it looks in the system environment +- Second, it looks for a file named `.env` in the current working directory. There are overloads that let you configure the directory and the filename to look for. + +The supported variables are: + +`NEO4J_URI`:: The address or URI of the instance to connect to. +`NEO4J_USERNAME`:: (Optional) Username. +`NEO4J_PASSWORD`:: (Optional) Password. +`NEO4J_SQL_TRANSLATION_ENABLED`:: (Optional) Whether to enable full SQL-to-Cypher translation, defaults to `false`. + +NOTE: Information from both the system environment and the .env files are combined. +If for example `NEO4J_SQL_TRANSLATION_ENABLED` is in the system environment but not in the .env file, it will still be picked up. +Given the order of priority, information in the system environment always has precedence over the .env file. + +This feature is especially useful with Neo4j AuraDB. +When creating a new AuraDB instance, you download a `.env` file that you can directly use with the Neo4j JDBC Driver: + +[source,java] +.Using a .env file from AuraDB +---- +try ( + var con = Neo4jDriver.fromEnv("Neo4j-cb3d8b2d-Created-2024-02-14.txt") + .orElseThrow(); + var stmt = con.createStatement(); + var movies = stmt.executeQuery("MATCH (n:Movie) RETURN n.title AS title") +) { + while (movies.next()) { + System.out.println(movies.getString("title")); + } +} +---- diff --git a/neo4j-jdbc-translator/impl/src/test/resources/dml.adoc b/neo4j-jdbc-translator/impl/src/test/resources/dml.adoc index 1e37bea4a..18c57553b 100644 --- a/neo4j-jdbc-translator/impl/src/test/resources/dml.adoc +++ b/neo4j-jdbc-translator/impl/src/test/resources/dml.adoc @@ -1,27 +1,26 @@ = DML statements -In this section we list the supported Data-Manipulation-Language (DML) statements. -While a `SELECT` statement is technically DML as well, as it is used to project existing relations into new relations, it has been covered previously. +This section lists the supported Data-Manipulation-Language (DML) statements. +Although a `SELECT` statement is technically DML as well, it is covered in xref:translator/simple.adoc[]. == Deleting nodes Nodes can be deleted via the SQL `DELETE` statement. -This can happen unconditionally: + +For example, to unconditionally delete all `person` nodes: [source,sql,id=d0_0,name=delete] ---- DELETE FROM person ---- -which will delete all `person` nodes: - [source,cypher,id=d0_0_expected] ---- MATCH (person:person) DELETE person ---- -A `WHERE` clause can be added to prevent this and all conditions can be used: +A `WHERE` clause can be added to prevent this: [source,sql,id=d0_1,name=delete] ---- @@ -29,8 +28,6 @@ DELETE FROM person WHERE person.id = 1 ---- -so that only the `person` node with a matching property is deleted. - [source,cypher,id=d0_1_expected] ---- MATCH (person:person) @@ -38,7 +35,9 @@ WHERE person.id = 1 DELETE person ---- -If you want to delete everything, but your tooling is complaining, just add a conditions that is always `true`: +''' + +If you want to delete everything, but your tooling complains, just add a conditions that is always `true`: [source,sql,id=d0_1b,name=delete] ---- @@ -46,8 +45,6 @@ DELETE FROM person WHERE true ---- -Your data is gone, either way: - [source,cypher,id=d0_1b_expected] ---- MATCH (person:person) @@ -55,7 +52,9 @@ WHERE true DELETE person ---- -This is safer, but also pointless: +''' + +Of, the condition can also be that always evaluates to `false`, never deleting anything: [source,sql,id=d0_1c,name=delete] ---- @@ -63,8 +62,6 @@ DELETE FROM person WHERE false ---- -Your data is gone, either way: - [source,cypher,id=d0_1c_expected] ---- MATCH (person:person) @@ -72,22 +69,22 @@ WHERE false DELETE person ---- -Tables can be aliased +''' + +Tables can be aliased, and the alias will be used in Cypher, too: [source,sql,id=d0_2,name=delete] ---- DELETE FROM person p ---- -and the alias will be used in Cypher, too: - [source,cypher,id=d0_2_expected] ---- MATCH (p:person) DELETE p ---- -Alias tables is also support in combination with specifying the label to which the table name is mapped. +Aliasing tables is also supported in combination with specifying the label to which the table name is mapped. Using the same query with `table_mappings=person:Person` configured, [source,sql,id=d0_3,name=delete,table_mappings=person:Person] @@ -105,7 +102,7 @@ DELETE p == Deleting nodes and their related nodes -You can use SQL `TRUNCATE` to detach delete nodes. +You can use SQL `TRUNCATE` to https://neo4j.com/docs/cypher-manual/current/clauses/delete/#delete-a-node-with-all-its-relationships[`DETACH DELETE` nodes]. [source,sql,id=d0_4,name=truncate,table_mappings=people:Person] ---- @@ -129,42 +126,43 @@ A single list of values with explicit columns and constant values can be inserte INSERT INTO People (first_name, last_name, born) VALUES ('Helge', 'Schneider', 1955) ---- -which will be translated to: - [source,cypher,id=d1_0_expected] ---- CREATE (people:`Person` {first_name: 'Helge', last_name: 'Schneider', born: 1955}) ---- -All expressions, including parameters, are supported: +All expressions, including parameters, are supported. +Parameters will be named from 1 on upwards in Cypher. [source,sql,id=d1_1,name=insert,table_mappings=people:Person] ---- INSERT INTO People (first_name, last_name, born) VALUES (?, ?, ?) ---- -Parameters will be named from 0 on upwards in Cypher: - [source,cypher,id=d1_1_expected] ---- CREATE (people:`Person` {first_name: $1, last_name: $2, born: $3}) ---- -If you omit the columns names on the insertion target, we generate names: +''' + +If you omit the column names on the insertion target, we generate names: [source,sql,id=d1_2,name=insert,table_mappings=people:Person] ---- INSERT INTO People VALUES ('Helge', 'Schneider', 1955) ---- -Note the `unknown field xxx` graph properties created: +Note the `unknown field xxx` property names: [source,cypher,id=d1_2_expected] ---- CREATE (people:`Person` {`unknown field 0`: 'Helge', `unknown field 1`: 'Schneider', `unknown field 2`: 1955}) ---- -The SQL `VALUES` clause actually supports list of values: +''' + +The SQL `VALUES` clause actually supports lists of values: [source,sql,id=d1_3,name=insert,table_mappings=people:Person] ---- @@ -173,7 +171,7 @@ INSERT INTO People (first_name, last_name, born) VALUES ('Bela', 'B', 1962) ---- -Those values will be translated into a Cypher array to be unwind in the Cypher statement. +Those values will be translated into a Cypher array to be unwound in the Cypher statement. This is a great solution for batching inserts: [source,cypher,id=d1_3_expected] @@ -186,15 +184,15 @@ CREATE (people:`Person`) SET people = properties ---- -A returning clause is supported as well, so that +''' + +A returning clause is supported as well: [source,sql,id=d1_4,name=insert,table_mappings=people:Person] ---- INSERT INTO People p (name) VALUES (?) RETURNING elementId(p) ---- -is translated into - [source,cypher,id=d1_4_expected] ---- CREATE (p:Person {name: $1}) RETURN elementId(p) @@ -203,43 +201,38 @@ CREATE (p:Person {name: $1}) RETURN elementId(p) == Upserts We support a restricted range of "upserts" via the non-standard but pretty common `ON DUPLICATE` and `ON CONFLICT` SQL clauses. -Upserts will generally be translated to `MERGE` statements. -While they tend to work without constraints, you really should but unique-constraints on the node properties you merge on. -Otherwise, Neo4j create duplicates due to locking issues. -Read me more about the latter https://neo4j.com/developer/kb/understanding-how-merge-works/[here]. +Upserts are translated into `MERGE` statements. +While they work without constraints, you should really have uniqueness-constraints on the node properties you merge on, or Neo4j may create duplicates (see https://neo4j.com/developer/kb/understanding-how-merge-works/[Understanding how merge works]). -Two options are possible to merge on all columns inserted via `ON DUPLICATE KEY IGNORE` and `ON CONFLICT IGNORE`. +Upserts on all columns can happen via either `ON DUPLICATE KEY IGNORE` or `ON CONFLICT IGNORE`. +While `ON DUPLICATE KEY` does offer upgrade options, it assumes the primary (or unique) key being violated to be known. +Although this is most certainly the case in a relational system, this translation layer that runs without a database connection doesn't know. [source,sql,id=upsert1] +.Upsert with `ON DUPLICATE KEY IGNORE` ---- INSERT INTO Movie(title, released) VALUES(?, ?) ON DUPLICATE KEY IGNORE ---- -will be translated to: - [source,cypher,id=upsert1_expected] ---- MERGE (movie:`Movie` {title: $1, released: $2}) ---- -The same goes for `ON CONFLICT DO NOTHING`. -In the example we configured a table mapping: - [source,sql,id=upsert2,table_mappings=actors:Actor] +.Upsert with `ON CONFLICT IGNORE` ---- INSERT INTO actors(name, firstname) VALUES(?, ?) ON CONFLICT DO NOTHING ---- -will be translated to: - [source,cypher,id=upsert2_expected] ---- MERGE (actors:`Actor` {name: $1, firstname: $2}) ---- -If you want to define an action, you must use `ON CONFLICT` specifying the key you want to merge on. -While `ON DUPLICATE KEY` does offer upgrade options, it assumes the primary (or unique) key being violated to be known. -This is most certainly the case in a relational system, but not in this translation layer, that does run without a database connection: +''' + +If you want to define an action, you must use `ON CONFLICT` and specify the key you want to merge on. [source,sql,id=upsert3] ---- @@ -247,7 +240,7 @@ INSERT INTO tbl(i, j, k) VALUES (1, 40, 700) ON CONFLICT (i) DO UPDATE SET j = 0, k = 2 * EXCLUDED.k ---- -Take note how the special reference `EXCLUDED` can be used to refer to the values of columns that have not been part of the key. +Note how the special reference `EXCLUDED` can be used to refer to the values of columns that have not been part of the key. They will be reused with their values in the `ON MATCH SET` clause. [source,cypher,id=upsert3_expected] @@ -265,8 +258,6 @@ INSERT INTO tbl(i, j, k) VALUES (1, 2, ?) ON CONFLICT (i) DO UPDATE SET j = EXCLUDED.k ---- -Same result, but referring to the parameter: - [source,cypher,id=upsert4_expected] ---- MERGE (tbl:`tbl` {i: 1}) @@ -274,7 +265,10 @@ ON CREATE SET tbl.j = 2, tbl.k = $1 ON MATCH SET tbl.j = $1 ---- -If you just want to specify a concrete merge column instead of merging on all columns, this possible too: +''' + +It's possible to just specify a concrete merge column instead of merging on all columns as well. +It will be translated with `ON CREATE`: [source,sql,id=upsert3b] ---- @@ -282,14 +276,13 @@ INSERT INTO tbl(i, j, k) VALUES (1, 40, 700) ON CONFLICT (i) DO NOTHING ---- -will be using `ON CREATE` only: - [source,cypher,id=upsert3b_expected] ---- MERGE (tbl:`tbl` {i: 1}) ON CREATE SET tbl.j = 40, tbl.k = 700 ---- +''' Using `ON CONFLICT` and specifying a key is the only way to insert multiple rows with a `MERGE` statement: diff --git a/neo4j-jdbc-translator/impl/src/test/resources/expressions.adoc b/neo4j-jdbc-translator/impl/src/test/resources/expressions.adoc index cad10adb6..3d5273b9f 100644 --- a/neo4j-jdbc-translator/impl/src/test/resources/expressions.adoc +++ b/neo4j-jdbc-translator/impl/src/test/resources/expressions.adoc @@ -1,10 +1,10 @@ = Expressions -Most SQL expressions will have a corresponding Cypher expression and can be translated straigt forward: +Most SQL expressions have corresponding Cypher expressions and can be translated straightforward. == Literal Values -Literal values are usually 1:1 translations: +Literal values are 1:1 translations. [source,sql,id=e0_0,name=select_literal_values] ---- @@ -12,8 +12,6 @@ SELECT 1, TRUE, FALSE, NULL, 'a' ---- -become - [source,cypher,id=e0_0_expected] ---- RETURN 1, TRUE, FALSE, NULL, 'a' @@ -21,7 +19,7 @@ RETURN 1, TRUE, FALSE, NULL, 'a' == Arithmetic expressions -So are the arithmetic expressions: +Arithmetic expressions are 1:1 translations. [source,sql,id=e1_0,name=select_with_arithmetic] ---- @@ -33,7 +31,7 @@ SELECT square(2) ---- -Note that the underlying tech of the default translator uses https://github.com/neo4j-contrib/cypher-dsl[Cypher-DSL] internally, which will wrap arithmetic (and logical expressions) with parentheses: +Note that the underlying tech of the default translator uses https://github.com/neo4j-contrib/cypher-dsl[Cypher-DSL] internally, which will wrap arithmetic (and logical) expressions with parentheses: [source,cypher,id=e1_0_expected] ---- @@ -49,7 +47,7 @@ RETURN === Numeric functions -We can translate all numeric functions that are supported by Neo4j's Cypher implementation: https://neo4j.com/docs/cypher-manual/current/functions/mathematical-numeric/[Mathematical functions - numeric]: +We can translate all numeric functions that are supported by Neo4j's Cypher implementation: https://neo4j.com/docs/cypher-manual/current/functions/mathematical-numeric/[Mathematical functions - Numeric]: [source,sql,id=e2_0,name=select_with_mathematical_functions] ---- @@ -77,7 +75,7 @@ RETURN === Logarithmic functions -Neo4j supports a brought range of https://neo4j.com/docs/cypher-manual/current/functions/mathematical-logarithmic/[logarithmic functions] and the input +Neo4j supports a broad range of https://neo4j.com/docs/cypher-manual/current/functions/mathematical-logarithmic/[logarithmic functions]. [source,sql,id=e3_0,name=select_with_logarithmic_functions] ---- @@ -203,7 +201,7 @@ SELECT cast(1 as bigint) ---- -will be translated to (Compare https://neo4j.com/docs/cypher-manual/current/functions/scalar/[Scalar functions]): +will be translated to (see https://neo4j.com/docs/cypher-manual/current/functions/scalar/[Scalar functions]): [source,cypher,id=e6_0_expected] ---- @@ -223,7 +221,7 @@ RETURN == Query expressions -Several advanced SQL expressions are supported as well, such as +Several advanced SQL expressions are supported as well. === `CASE` simple @@ -238,16 +236,16 @@ SELECT CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END ---- -which will be translated to - [source,cypher,id=e7_0_expected,parseCypher=false] ---- RETURN CASE 1 WHEN 2 THEN 3 END, CASE 1 WHEN 2 THEN 3 ELSE 4 END, CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 END, CASE 1 WHEN 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END ---- +See https://neo4j.com/docs/cypher-manual/current/queries/case/[Cypher -> Conditional expressions (CASE)] for more information. + === `CASE` advanced -And `CASE` statement using a search (See https://neo4j.com/docs/cypher-manual/current/syntax/expressions[expressions] for more information): +And `CASE` statement using a search: [source,sql,id=e7_1,name=select_with_string_functions_case_a] ---- @@ -269,7 +267,9 @@ RETURN CASE WHEN 1 = 2 THEN 3 WHEN 4 = 5 THEN 6 ELSE 7 END ---- -=== `CASE` abbreviations (which aren't `COALESCE` or `NVL`) +See https://neo4j.com/docs/cypher-manual/current/queries/case/[Cypher -> Conditional expressions (CASE)] for more information. + +=== `CASE` abbreviations (which are not `COALESCE` or `NVL`) The input diff --git a/neo4j-jdbc-translator/impl/src/test/resources/joins.adoc b/neo4j-jdbc-translator/impl/src/test/resources/joins.adoc index 1dea9cac9..3cc40abe8 100644 --- a/neo4j-jdbc-translator/impl/src/test/resources/joins.adoc +++ b/neo4j-jdbc-translator/impl/src/test/resources/joins.adoc @@ -1,19 +1,21 @@ +[#joinin-relationships] = Using joins to map relationships -On the surface joins are relationships materialized in SQL (Foreign keys are not). -Sadly, it's not as straight forward to map. -There are several options and possibilities to handle things: +On the surface, joins are relationships materialized in SQL (foreign keys are not). +Sadly, it's not as straightforward to map to Cypher. +There are several implementation options: -* When joining two tables on a column, taking the left hand table column, use its name as relationship type and treat it as outgoing from left to right -* When joining two tables with an intersection table (which you usually model in SQL for m:n relationships with attributes), use the name of that intersection table as relationship type +* When joining two tables on a column, take the left hand table column, use its name as relationship type, and treat it as outgoing from left to right. +* When joining two tables with an intersection table (which it's usually modeled in SQL for `m:n` relationships with attributes), use the name of that intersection table as relationship type. -We implemented some variants thereof, however we don't claim their absolute usefulness in all cases +We implemented some variants thereof, however we don't claim their absolute usefulness in all cases. == 1:n joins === Natural joins -SQL `NATURAL` joins are the easiest way to denote relationship names without having todo any mapping. +SQL `NATURAL` joins are the easiest way to denote relationship names without having to do any mapping. +A one-hope `NATURAL JOIN` will translate to an anonymous, wildcard relationship. [source,sql,id=nj1,name=naturalJoin] ---- @@ -21,14 +23,15 @@ SELECT p, m FROM Person p NATURAL JOIN Movie m ---- -A one-hope `NATURAL JOIN` will resolve to an anonymous, wildcard relationship: - [source,cypher,id=nj1_expected] ---- MATCH (p:Person)-->(m:Movie) RETURN p, m ---- -`NATURAL` joins can be chained like this, the connecting join table does not need to exist: +''' + +`NATURAL` joins can be chained, and the connecting join table does not need to exist. +This will be turned into a Neo4j relationship: [source,sql,id=nj2,name=naturalJoins,metaData=Movie:title|released] ---- @@ -37,8 +40,6 @@ NATURAL JOIN ACTED_IN r NATURAL JOIN Movie m ---- -It will be turned into a Neo4j relation: - [source,cypher,id=nj2_expected] ---- MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) @@ -48,7 +49,7 @@ RETURN p.name, r.role, === Simple join -We configured the translator use the following table mapping: +Assume we configured the translator to use the following table mapping: * `people` mapped to label `People` * `movies` mapped to label `Movie` @@ -91,7 +92,7 @@ RETURN p.name, m.title === Using the `ON` clause -We used backticks here for the table- and column names and no mapping. +We used backticks here for the table and column names and no mapping. [source,sql,id=r1_0,name=foreign_key_join] ---- @@ -100,7 +101,7 @@ FROM `Person` as p JOIN `Movie` as m ON (m.id = p.`DIRECTED`) ---- -The translation yields in the same result as before +The translation is same as before: [source,cypher,id=r1_0_expected] ---- @@ -110,12 +111,11 @@ RETURN p.name, m.title == m:n joins -An intersection table is a table that contains references to two other tables in the form of at least two columns—hopefully with foreign keys and therefor indexes defined on them. -This construct is usually required in the relational model to create an m:n relationship. -Sometimes the intersection table also spots additional columns. -Of course, such an auxiliary construct is not necessary in Neo4j. -We can model as many outgoing and incoming relationships from one label to another as we desire and they can also have properties. -We can hover use that construct for our translator +An intersection table is a table that contains references to two other tables in the form of at least two columns. +This construct is usually required in the relational model to create an `m:n` relationship. +Such an auxiliary construct is not necessary in Neo4j. +We can model as many outgoing and incoming relationships from one label to another, and they can also have properties. +We can thus use that construct for our translator. The following example uses a configured mapping as follows: @@ -130,11 +130,11 @@ FROM people p -- <.> JOIN movie_actors r ON r.person_id = p.id -- <.> JOIN movies m ON m.id = r.person_id -- <.> ---- -<.> The driving table from which we map outgoing relationships +<.> The table from which to map outgoing relationships <.> An intersection table, that is used again in the next `JOIN` clause -<.> The final join clause +<.> The final `JOIN` clause -We don't do semantic analysis, the order of the joins matter and will lead to the following query: +We do no semantic analysis: the order of the joins matter, and will lead to the following query: [source,cypher,id=r2_0_expected] ---- @@ -154,8 +154,6 @@ FROM people p JOIN people d ON r2.person_id = d.id ---- -as demonstrated by - [source,cypher,id=r2_1_expected] ---- MATCH (p:`Person`)-[r:`ACTED_IN`]->(m:`Movie`)<-[r2:`DIRECTED`]-(d:`Person`) diff --git a/neo4j-jdbc-translator/impl/src/test/resources/predicates.adoc b/neo4j-jdbc-translator/impl/src/test/resources/predicates.adoc index 5db89dd60..b77e54951 100644 --- a/neo4j-jdbc-translator/impl/src/test/resources/predicates.adoc +++ b/neo4j-jdbc-translator/impl/src/test/resources/predicates.adoc @@ -1,19 +1,17 @@ = Predicates :toc: -As with expressions a lot of logical SQL expressions and conditions used as predicates can be translated into straight forward into Cypher predicates. +As with expressions a lot of logical SQL expressions and conditions used as predicates can be translated straightforward into Cypher predicates. -== Conjunctions and disjunctions. +== Conjunctions and disjunctions -Logical conjunctions and disjunctions are all supported: +Logical conjunctions and disjunctions are all supported. [source,sql,id=p1_0,name=logic_operators] ---- SELECT 1 FROM p WHERE 1 = 1 AND 2 = 2 OR 3 = 3 ---- -will be translated to - [source,cypher,id=p1_0_expected] ---- MATCH (p:p) @@ -23,6 +21,8 @@ WHERE ((1 = 1 RETURN 1 ---- +''' + The input [source,sql,id=p1_1,name=logic_operators_rare] @@ -83,7 +83,9 @@ WHERE (1 <= 2) AND (2 <= 3) RETURN 1 ---- -SQL has a `SYMMETRIC` keyword for the `BETWEEN` clause, to indicate that you do not care whouch bound of the range is larger than the other: +''' + +SQL has a `SYMMETRIC` keyword for the `BETWEEN` clause, to indicate that you do not care which bound of the range is larger: [source,sql,id=p2_2,name=predicate_between_symmetric] ---- diff --git a/neo4j-jdbc-translator/impl/src/test/resources/simple.adoc b/neo4j-jdbc-translator/impl/src/test/resources/simple.adoc index ef21370ff..da33776e1 100644 --- a/neo4j-jdbc-translator/impl/src/test/resources/simple.adoc +++ b/neo4j-jdbc-translator/impl/src/test/resources/simple.adoc @@ -2,26 +2,26 @@ == Table names to labels -The most simple select statement to translate is a statement without the `FROM` clause, such as: +The most simple `SELECT` statement to translate is one without `FROM` clause, such as: [source,sql,id=t1_1,name=no_driving_table] ---- SELECT 1 ---- -It is equivalent without loss to the following Cypher: +It is equivalent to a Cypher `RETURN`: [source,cypher,id=t1_1_expected] ---- RETURN 1 ---- -`SELECT` statements without further `JOIN` clauses are pretty straight forward to translate. -The hardest challenge here is how to map the table name to labels: +`SELECT` statements without `JOIN` clauses are pretty straightforward to translate. +The challenge here is how to map the table name to labels: -* We parse the SQL Statement case-sensitive by default -* Table names will be mapped to node labels -* Table aliases will be used as identifiable symbolic names +* We parse the SQL statement case-sensitive by default +* Table names are mapped to node labels +* Table aliases are used as identifiable symbolic names [source,sql,id=t1_0,name=select_with_condition] ---- @@ -31,7 +31,7 @@ FROM My_Table -- <.> WHERE t.a = 1 ---- <.> Will be used as the label to match, as-is, i.e. `My_Table` -<.> The table alias will become the node-alias +<.> The table alias will become the node alias The whole construct will be translated to @@ -42,12 +42,13 @@ WHERE t.a = 1 RETURN t.a, t.b ---- -We recommend using table aliases, but the translations works without them as well. +Table aliases are optional, if you omit them, we derive aliases from the labels and types. +If you inspect the translated queries, we recommend using aliases, as this makes the queries better readable. [#s2c_star_selects] == Star-Selects -A star- or `*` select comes in different forms: +A star- or `*`- select comes in different forms: Unqualified:: `SELECT * FROM table` @@ -56,9 +57,9 @@ Qualified:: And a variant, selecting the relation itself: `SELECT t FROM table t`. -We make use of this fact to give users a way to decide whether they want to return Neo4j nodes and relationships as entities, maps or flattened to individual columns. -The latter however requires our translator to have access to the schema of the underlying Neo4j database. -The following sections will describe the use-cases: +We make use of this to let you decide whether you want to return Neo4j nodes and relationships as entities, maps, or flattened to individual columns. +The latter requires our translator to have access to the schema of the underlying Neo4j database. +The following sections describe the use-cases. === Projecting individual properties @@ -69,7 +70,7 @@ Don't use a star-select but enumerate the properties: SELECT m.title FROM Movie m ---- -The table alias will be used as a symbolic name +The table alias will be used as a symbolic name: [source,cypher,id=star_1_expected] ---- @@ -77,6 +78,8 @@ MATCH (m:Movie) RETURN m.title; ---- +''' + You can omit the table alias: [source,sql,id=star_2] @@ -92,8 +95,10 @@ MATCH (movie:Movie) RETURN movie.title; ---- -If you access your JDBC columns by name, this might lead to code that is hard to maintain -You might want to alias the column, then: +''' + +Accessing JDBC columns by name leads to code that is hard to maintain, as column renamings impact code as well. +To avoid this, alias the column: [source,sql,id=star_3] ---- @@ -110,26 +115,26 @@ RETURN movie.title AS title; === Projecting all properties -If you run a +A `SELECT *` statement gets translated differently depending on whether the connection to the Neo4j database is available. [source,sql,id=star_5] ---- -SELECT * FROM Movie m +SELECT * FROM Person p ---- -you will get the following Cypher statement in case you run the translation offline: +If you are offline, you will get the following Cypher statement: [source,cypher,id=star_5_expected] ---- -MATCH (m:Movie) RETURN * +MATCH (p:Person) RETURN * ---- -The above query will return one column ("m"), which is a Neo4j node. -This is usually not what is expected in the relational world. +The above query will return one column (`p`) containing a Neo4j node. +This is usually not what you expect in the relational world. If you run the translation online and Neo4j metadata can be retrieved, -it will generate a statement that flattens the properties of each node and relationship plus their element ids: +you will get a statement that flattens the properties of each node and relationship, plus their element IDs: -In case the `Person` node has properties `born` and `name` +In case the `Person` node has properties `born` and `name`, [source,sql,id=star_6,name=unqualifiedAsteriskSingleTable,metaData=Person:born|name] ---- @@ -145,15 +150,13 @@ RETURN elementId(p) AS element_id, p.born AS born, p.name AS name ---- -This works well with multiple tables as well (`Movie` has properties `title` and `released`) +This works well with multiple tables as well (`Movie` has properties `title` and `released`): [source,sql,id=star_7,name=unqualifiedAsteriskMultipleTables,metaData=Person:born|name;Movie:title|released] ---- SELECT * FROM Person p JOIN Movie m ON m.id = p.acted_in ---- -Properties will be delivered as requested: - [source,cypher,id=star_7_expected] ---- MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie) @@ -161,7 +164,7 @@ RETURN elementId(p) AS element_id, p.born AS born, p.name AS name, elementId(m) AS element_id1, m.title AS title, m.released AS released ---- -We add increasing numbers to column names if they clash (we duplicated the `name` property and added a `remark` to the metadata): +We append increasing numbers to column names to clashing ones (ex. with `name` and `remark` properties both in `Movie` and `Person`): [source,sql,id=star_8,name=unqualifiedAsteriskDuplicatedColumns,metaData=Person:born|name|remark;Movie:name|released|remark] ---- @@ -179,7 +182,9 @@ RETURN elementId(p) AS element_id, m.name AS name1, m.released AS released, m.remark AS remark1 ---- -The following example uses a join-table to access relationships (we explain this later in this manual), but the flattening of properties works there as well: +''' + +The following example uses a join-table to access relationships (we explain this later in this manual <>), and the flattening of properties works here as well: [source,sql,id=star_9,name=unqualifiedAsteriskJoinTable, metaData=ACTED_IN:role;Person:born|name;Movie:title|released,table_mappings=people:Person;movies:Movie;movie_actors:ACTED_IN] ---- @@ -199,15 +204,14 @@ RETURN elementId(p) AS element_id, m.title AS title, m.released AS released ---- -Ordering without specifying a table alias does work as expected: +''' [source,sql,id=star_6b,name=unqualifiedAsteriskSingleTable,metaData=Person:born|name] +.Ordering without specifying a table alias ---- SELECT * FROM Person p ORDER BY name ASC ---- -you will get this Cypher statement - [source,cypher,id=star_6b_expected] ---- MATCH (p:Person) @@ -216,8 +220,10 @@ RETURN elementId(p) AS element_id, ORDER BY p.name ---- +''' + A qualified alias can be used as well. -In case without meta-data it will return a map of properties of the node or relationship in question: +If no Neo4j metadata is available, you will get a map of properties of the node/relationship: [source,sql,id=star_4,name=mapQualifiedAsteriskWithoutMetadata] ---- @@ -226,7 +232,7 @@ FROM Person p JOIN Movie m ON m.id = p.acted_in ---- -The corresponding columns must be downcast to a Map in JDBC: +The corresponding columns must be downcast to a map in JDBC: [source,cypher,id=star_4_expected] ---- @@ -234,7 +240,9 @@ MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie) RETURN m{.*} AS m, p{.*} AS p ---- -If we add meta-data (here `born` and `name` to `Person`), the qualified star will project all those: +''' + +If we add more data (ex. `born` and `name` to `Person`), the qualified star will project all of them (note how we also project one single, known column from the `Movie` table): [source,sql,id=star_4a,name=mapQualifiedAsteriskWithMetadata,metaData=Person:born|name] ---- @@ -243,8 +251,6 @@ FROM Person p JOIN Movie m ON m.id = p.acted_in ---- -Note how we also projected one single, known property from the `Movie` node: - [source,cypher,id=star_4a_expected] ---- MATCH (p:Person)-[acted_in:ACTED_IN]->(m:Movie) @@ -268,22 +274,24 @@ MATCH (m:Movie) RETURN m; ---- -A node can be alias as well: +''' + +A node can be aliased as well: [source,sql,id=star_n2] ---- SELECT m AS node FROM Movie m ---- -will result in a Cypher statement returning the matched node as node. - [source,cypher,id=star_n2_expected] ---- MATCH (m:Movie) RETURN m AS node; ---- -Unaliased tables can be used as well: +''' + +Un-aliased tables can be used as well: [source,sql,id=star_n3] ---- @@ -296,7 +304,9 @@ MATCH (movie:Movie) RETURN movie; ---- -And multiple entities are supported, too: +''' + +Multiple entities are supported, too: [source,sql,id=star_10] ---- @@ -312,13 +322,13 @@ MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p, r, m == Comparing SQL with Cypher examples -Sources of the following examples are from https://neo4j.com/developer/cypher/guide-sql-to-cypher/[Comparing SQL with Cypher]. +The source of the following examples is: https://neo4j.com/developer/cypher/guide-sql-to-cypher/[Comparing SQL with Cypher]. === Find all Products === Select and Return Records -Easy in SQL, just select everything from the `products` table. +Select everything from the `products` table. [source,sql,id=t2_0,name=select_and_return_records,table_mappings=products:Product] ---- @@ -326,7 +336,7 @@ SELECT p.* FROM products as p ---- -Similarly, in Cypher, you just *match* a simple pattern: all nodes with the *label* `:Product` and `RETURN` them. +Similarly, in Cypher, you just `MATCH` a simple pattern: all nodes with the *label* `Product` and `RETURN` them. [source,cypher,id=t2_0_expected] ---- @@ -334,8 +344,10 @@ MATCH (p:Product) RETURN p{.*} AS p ---- +''' + The above query will project all properties of the matched node. -If you want to select the node itself, just select it without using the asterisk: +If you want to return the node itself, select it without using the asterisk: [source,sql,id=t2_0a,name=select_and_return_records,table_mappings=products:Product] ---- @@ -343,8 +355,6 @@ SELECT p FROM products as p ---- -It will be translated into a query that returns nodes: - [source,cypher,id=t2_0a_expected] ---- MATCH (p:Product) @@ -353,8 +363,9 @@ RETURN p === Field Access, Ordering and Paging -*More efficient is to return only a subset of attributes*, like `ProductName` and `UnitPrice`. -And while we're on it, let's also order by price and only return the 10 most expensive items. +*It is more efficient to return only a subset of attributes*, like `ProductName` and `UnitPrice`. +And while we are at it, let's also order by price and only return the 10 most expensive items. +(Remember that labels, relationship-types and property-names are *case sensitive* in Neo4j.) [source,sql,id=t2_1,name=field_acces_ordering_paging,table_mappings=products:Product] ---- @@ -364,16 +375,15 @@ ORDER BY p.`unitPrice` DESC LIMIT 10 ---- -You can copy and paste the changes from SQL to Cypher, it's thankfully unsurprising. -But remember that labels, relationship-types and property-names are *case sensitive* in Neo4j. - [source,cypher,id=t2_1_expected] ---- MATCH (p:Product) RETURN p.productName, p.unitPrice ORDER BY p.unitPrice DESC LIMIT 10 ---- -Default order direction will be translated as is: +''' + +The default order direction will be translated as is: [source,sql,id=t2_2,name=order_by_default] ---- @@ -388,7 +398,7 @@ RETURN * ORDER BY m.title === `DISTINCT` projections -The `DISTINCT` keyword for projections should be handled: +The `DISTINCT` keyword for projections is handled: [source,sql,id=t3_1,name=distinct] ---- @@ -401,34 +411,31 @@ MATCH (m:Movies) RETURN DISTINCT m.released ---- -It also works with `*` projections so that: +It works with `*` projections as well: [source,sql,id=t3_2,name=distinct_star] ---- SELECT DISTINCT m.* FROM Movies m ---- -becomes. - [source,cypher,id=t3_2_expected,parseCypher=true] ---- MATCH (m:Movies) RETURN DISTINCT m {.*} AS m ---- -However, as the qualified asterisks will use meta data if the database connection is available, this +However, as the qualified asterisks will use metadata if available, the translation with a database connection is different: [source,sql,id=t3_3,name=distinct_star_with_db,metaData=Movies:title|released] ---- SELECT DISTINCT m.* FROM Movies m ---- -becomes than - [source,cypher,id=t3_3_expected,parseCypher=false] ---- MATCH (m:Movies) RETURN DISTINCT elementId(m) AS element_id, m.title AS title, m.released AS released ---- -including the Neo4j element id, which will make each row unique. Thus being sad, the `DISCTINCT` clause is of limited use with the asterisk. +Note that each row includes the Neo4j element ID, making each row unique. +This being said, the `DISCTINCT` clause is of limited use with the asterisk.