def run(self, statement, parameters=None, **kwparameters):
        """ Run a Cypher statement within an auto-commit transaction.

        The statement is sent and the result header received
        immediately but the :class:`.StatementResult` content is
        fetched lazily as consumed by the client application.

        If a statement is executed before a previous
        :class:`.StatementResult` in the same :class:`.Session` has
        been fully consumed, the first result will be fully fetched
        and buffered. Note therefore that the generally recommended
        pattern of usage is to fully consume one result before
        executing a subsequent statement. If two results need to be
        consumed in parallel, multiple :class:`.Session` objects
        can be used as an alternative to result buffering.

        For more usage details, see :meth:`.Transaction.run`.

        :param statement: template Cypher statement
        :param parameters: dictionary of parameters
        :param kwparameters: additional keyword parameters
        :returns: :class:`.StatementResult` object
        """
        self._assert_open()
        if not statement:
            raise ValueError("Cannot run an empty statement")
        if not isinstance(statement, (str, Statement)):
            raise TypeError(
                "Statement must be a string or a Statement instance")

        if not self._connection:
            self._connect()
        cx = self._connection
        protocol_version = cx.protocol_version
        server = cx.server

        has_transaction = self.has_transaction()

        statement_text = str(statement)
        statement_metadata = getattr(statement, "metadata", None)
        statement_timeout = getattr(statement, "timeout", None)
        parameters = fix_parameters(dict(parameters or {}, **kwparameters))

        def fail(_):
            self._close_transaction()

        hydrant = DataHydrator()
        result_metadata = {
            "statement": statement_text,
            "parameters": parameters,
            "server": server,
            "protocol_version": protocol_version,
        }
        run_metadata = {
            "metadata": statement_metadata,
            "timeout": statement_timeout,
            "on_success": result_metadata.update,
            "on_failure": fail,
        }

        def done(summary_metadata):
            result_metadata.update(summary_metadata)
            bookmark = result_metadata.get("bookmark")
            if bookmark:
                self._bookmarks_in = tuple([bookmark])
                self._bookmark_out = bookmark

        self._last_result = result = BoltStatementResult(
            self, hydrant, result_metadata)

        if has_transaction:
            if statement_metadata:
                raise ValueError(
                    "Metadata can only be attached at transaction level")
            if statement_timeout:
                raise ValueError("Timeouts only apply at transaction level")
        else:
            run_metadata["bookmarks"] = self._bookmarks_in

        cx.run(statement_text, parameters, **run_metadata)
        cx.pull_all(
            on_records=lambda records: result._records.extend(
                hydrant.hydrate_records(result.keys(), records)),
            on_success=done,
            on_failure=fail,
            on_summary=lambda: result.detach(sync=False),
        )

        if not has_transaction:
            try:
                self._connection.send_all()
                self._connection.fetch_message()
            except ConnectionExpired as error:
                raise SessionExpired(*error.args)

        return result
Exemple #2
0
    def run(self, query, parameters=None, **kwparameters):
        """ Run a Cypher query within an auto-commit transaction.

        The query is sent and the result header received
        immediately but the :class:`neo4j.Result` content is
        fetched lazily as consumed by the client application.

        If a query is executed before a previous
        :class:`neo4j.Result` in the same :class:`.Session` has
        been fully consumed, the first result will be fully fetched
        and buffered. Note therefore that the generally recommended
        pattern of usage is to fully consume one result before
        executing a subsequent query. If two results need to be
        consumed in parallel, multiple :class:`.Session` objects
        can be used as an alternative to result buffering.

        For more usage details, see :meth:`.Transaction.run`.

        :param query: Cypher query
        :param parameters: dictionary of parameters
        :param kwparameters: additional keyword parameters
        :returns: :class:`neo4j.Result` object
        """
        if not query:
            raise ValueError("Cannot run an empty query")
        if not isinstance(query, (str, Query)):
            raise TypeError("query must be a string or a Query instance")

        if not self._connection:
            self._connect(self._config.default_access_mode, database=self._config.database)
        cx = self._connection
        protocol_version = cx.PROTOCOL_VERSION
        server_info = cx.server_info

        has_transaction = self.has_transaction()

        query_text = str(query)
        query_metadata = getattr(query, "metadata", None)
        query_timeout = getattr(query, "timeout", None)
        parameters = DataDehydrator.fix_parameters(dict(parameters or {}, **kwparameters))

        def fail(_):
            self._close_transaction()

        hydrant = DataHydrator()
        result_metadata = {
            "query": query_text,
            "parameters": parameters,
            "server": server_info,
            "protocol_version": protocol_version,
        }
        run_metadata = {
            "metadata": query_metadata,
            "timeout": query_timeout,
            "on_success": result_metadata.update,
            "on_failure": fail,
        }

        def done(summary_metadata):
            result_metadata.update(summary_metadata)
            bookmark = result_metadata.get("bookmark")
            if bookmark:
                self._bookmarks_in = tuple([bookmark])
                self._bookmark_out = bookmark

        self._last_result = result = Result(self, hydrant, result_metadata)

        access_mode = None
        db = None
        bookmarks = None

        if has_transaction:
            # Explicit Transaction Run does not carry any extra values. RUN "query" {parameters} {extra}
            if query_metadata:
                raise ValueError("Metadata can only be attached at transaction level")
            if query_timeout:
                raise ValueError("Timeouts only apply at transaction level")
            access_mode = None
            db = None
            bookmarks = None
        else:
            run_metadata["bookmarks"] = self._bookmarks_in
            access_mode = self._config.default_access_mode
            db = self._config.database
            bookmarks = run_metadata.get("bookmarks", self._config.bookmarks)

        # BOLT RUN
        cx.run(
            query_text,
            parameters=parameters,
            mode=access_mode,
            bookmarks=bookmarks,
            metadata=run_metadata["metadata"],
            timeout=run_metadata["timeout"],
            db=db,
            on_success=run_metadata["on_success"],
            on_failure=run_metadata["on_failure"],
        )
        # BOLT PULL
        cx.pull(
            on_records=lambda records: result._records.extend(hydrant.hydrate_records(result.keys(), records)),
            on_success=done,
            on_failure=fail,
            on_summary=lambda: result.detach(sync=False),
        )

        if not has_transaction:
            self._connection.send_all()
            self._connection.fetch_message()

        return result