Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show hint about :auto on CALL {...} IN TRANSACTIONS #1637

Merged
merged 6 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 87 additions & 0 deletions e2e_tests/integration/auto-prefix.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2002-2021 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

describe(':auto prefix in browser', () => {
before(() => {
cy.visit(Cypress.config('url')).title().should('include', 'Neo4j Browser')
cy.wait(3000)
cy.ensureConnection()
})
beforeEach(() => {
cy.executeCommand(':clear')
})

it('shows help link when running period commit without :auto', () => {
cy.executeCommand('USING PERIODIC COMMIT RETURN "Verdanturf"')
cy.getFrames().contains('ERROR')
cy.getFrames().contains(':auto')
})

it('adding :auto enables running periodic commit', () => {
cy.executeCommand(':auto USING PERIODIC COMMIT RETURN "Laverre";')
// the only valid PERIODIC COMMIT queries require csv files on
// the server, so as a shortcut we're just looking for a new error message
cy.getFrames().contains('ERROR')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment explaining why we're looking for an error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, it definitely looks a bit odd

cy.getFrames().contains(/LOAD/i)
})

if (Cypress.config('serverVersion') >= 4.4) {
it('shows help link when running CALL IN TRANSACTIONS without :auto', () => {
cy.executeCommand(
'CALL {{} return "Dendemille" } IN TRANSACTIONS return "Dendemille";'
)
cy.getFrames().contains('ERROR')
cy.getFrames().contains(':auto')
})

it('adding :auto enables running CALL IN TRANSACTIONS', () => {
cy.executeCommand(
':auto CALL {{} return "Undella" } IN TRANSACTIONS return "Undella";'
)
cy.getFrames().should('not.contain', 'ERROR')
cy.getFrames().contains('"Undella"')
})
}

it('can use :auto command in multi-statements', () => {
cy.executeCommand('create ();')
cy.executeCommand(':clear')
const query = `:auto CREATE (t:MultiStmtTest {{}name: "Pacifidlog"}) RETURN t;:auto CREATE (t:MultiStmtTest {{}name: "Wyndon"}) RETURN t;`
cy.executeCommand(query)
cy.get('[data-testid="frame"]', { timeout: 10000 }).should('have.length', 1)
const frame = cy.get('[data-testid="frame"]', { timeout: 10000 }).first()
frame.find('[data-testid="multi-statement-list"]').should('have.length', 1)
frame
.get('[data-testid="multi-statement-list-title"]')
.should('have.length', 2)
frame.get('[data-testid="multi-statement-list-title"]').eq(0).click()
frame
.get('[data-testid="multi-statement-list-content"]', { timeout: 10000 })
.contains('SUCCESS')
frame.get('[data-testid="multi-statement-list-title"]').eq(1).click()
frame
.get('[data-testid="multi-statement-list-content"]', { timeout: 10000 })
.contains('SUCCESS')
cy.executeCommand('match (n: MultiStmtTest) return n.name')
cy.get('[role="cell"]').contains('Pacifidlog')
cy.get('[role="cell"]').contains('Wyndon')
cy.executeCommand('match (n: MultiStmtTest) delete n')
})
})
37 changes: 1 addition & 36 deletions e2e_tests/integration/multistatements.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { isEnterpriseEdition } from '../support/utils'

/* global Cypress, cy, before, after */
Expand All @@ -26,9 +25,7 @@ describe('Multi statements', () => {
const validQuery = 'RETURN 1; :config; RETURN 2;'

before(() => {
cy.visit(Cypress.config('url'))
.title()
.should('include', 'Neo4j Browser')
cy.visit(Cypress.config('url')).title().should('include', 'Neo4j Browser')
cy.wait(3000)
cy.enableMultiStatement()
})
Expand Down Expand Up @@ -115,38 +112,6 @@ describe('Multi statements', () => {
.first()
.should('contain', 'ERROR')
})

it('can use :auto command in multi-statements', () => {
cy.executeCommand('create ();')
cy.executeCommand(':clear')
const query = `:auto CREATE (t:MultiStmtTest {{}name: "Pacifidlog"}) RETURN t;:auto CREATE (t:MultiStmtTest {{}name: "Wyndon"}) RETURN t;`
cy.executeCommand(query)
cy.get('[data-testid="frame"]', { timeout: 10000 }).should('have.length', 1)
const frame = cy.get('[data-testid="frame"]', { timeout: 10000 }).first()
frame.find('[data-testid="multi-statement-list"]').should('have.length', 1)
frame
.get('[data-testid="multi-statement-list-title"]')
.should('have.length', 2)
frame
.get('[data-testid="multi-statement-list-title"]')
.eq(0)
.click()
frame
.get('[data-testid="multi-statement-list-content"]', { timeout: 10000 })
.contains('SUCCESS')
frame
.get('[data-testid="multi-statement-list-title"]')
.eq(1)
.click()
frame
.get('[data-testid="multi-statement-list-content"]', { timeout: 10000 })
.contains('SUCCESS')
cy.executeCommand('match (n: MultiStmtTest) return n.name')
cy.get('[role="cell"]').contains('Pacifidlog')
cy.get('[role="cell"]').contains('Wyndon')
cy.executeCommand('match (n: MultiStmtTest) delete n')
})

if (Cypress.config('serverVersion') >= 4.0) {
if (isEnterpriseEdition()) {
it('Can use :use command in multi-statements', () => {
Expand Down
35 changes: 17 additions & 18 deletions e2e_tests/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,9 @@ Cypress.Commands.add(
cy.title().should('include', 'Neo4j Browser')
cy.wait(3000)

cy.get('input[data-testid="boltaddress"]')
.clear()
.type(boltUrl)
cy.get('input[data-testid="boltaddress"]').clear().type(boltUrl)

cy.get('input[data-testid="username"]')
.clear()
.type(username)
cy.get('input[data-testid="username"]').clear().type(username)
cy.get('input[data-testid="password"]').type(initialPassword)

cy.get('button[data-testid="connect"]').click()
Expand Down Expand Up @@ -71,16 +67,10 @@ Cypress.Commands.add(
cy.executeCommand(':clear')
cy.executeCommand(':server connect')

cy.get('input[data-testid="boltaddress"]')
.clear()
.type(boltUrl)
cy.get('input[data-testid="boltaddress"]').clear().type(boltUrl)

cy.get('input[data-testid="username"]')
.clear()
.type(username)
cy.get('input[data-testid="password"]')
.clear()
.type(password)
cy.get('input[data-testid="username"]').clear().type(username)
cy.get('input[data-testid="password"]').clear().type(password)

cy.get('button[data-testid="connect"]').click()
if (makeAssertions) {
Expand Down Expand Up @@ -133,9 +123,7 @@ Cypress.Commands.add('resultContains', str => {
Cypress.Commands.add('addUser', (userName, password, role, force) => {
cy.get('[id*=username]')
cy.get('[id*=username]').type(userName)
cy.get('[id*=password]')
.first()
.type(password)
cy.get('[id*=password]').first().type(password)
cy.get('[id*=password-confirm]').type(password)
cy.get('[id*=roles-selector]').select(role)
if (force === true) {
Expand Down Expand Up @@ -188,3 +176,14 @@ Cypress.Commands.add('dropUser', username => {
cy.executeCommand(':clear')
}
})

Cypress.Commands.add(
'ensureConnection',
(creds = { username: 'neo4j', password: Cypress.config('password') }) => {
cy.contains('Database access not available').then(res => {
if (res) {
cy.connect(creds.username, creds.password)
}
})
}
)
7 changes: 7 additions & 0 deletions e2e_tests/support/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ declare global {
boltUrl?: string,
makeAssertions?: boolean
): Cypress.Chainable<void>
/**
* Custom command to run cy.connect if needed
*/
ensureConnection(creds?: {
username: string
password: string
}): Cypress.Chainable<void>
/**
* Custom command to disconnect from neo4j database
*/
Expand Down
10 changes: 7 additions & 3 deletions src/browser/documentation/help/auto.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const content = (
clusters.
<br />
Some query types do however need to be sent in auto-committing
transactions, <code>USING PERIODIC COMMIT</code> is the most notable one.
transactions, <code>CALL {'{...}'} IN TRANSACTIONS </code> is the most
notable one.
</p>
<table className="table-condensed table-help">
<tbody>
Expand All @@ -47,8 +48,11 @@ const content = (
</table>
<section className="example">
<figure>
<pre>{`:auto USING PERIODIC COMMIT
LOAD CSV WITH HEADER FROM ... `}</pre>
<pre>{`:auto LOAD CSV FROM 'file:///artists.csv' AS line
CALL {
WITH line
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
} IN TRANSACTIONS`}</pre>
<figcaption>
Example usage of the <em>:auto</em> command.
</figcaption>
Expand Down
4 changes: 2 additions & 2 deletions src/browser/modules/Stream/CypherFrame/ErrorsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import {
PlayIcon
} from 'browser-components/icons/Icons'
import {
isImplicitTransactionError,
isNoDbAccessError,
isPeriodicCommitError,
isUnknownProcedureError
} from 'services/cypherErrorsHelper'
import { deepEquals } from 'services/utils'
Expand Down Expand Up @@ -93,7 +93,7 @@ export class ErrorsView extends Component<any> {
</StyledLink>
</StyledLinkContainer>
)}
{isPeriodicCommitError(error) && (
{isImplicitTransactionError(error) && (
<StyledLinkContainer>
<StyledLink onClick={() => onItemClick(bus, `:help auto`)}>
<PlayIcon />
Expand Down
14 changes: 12 additions & 2 deletions src/shared/services/cypherErrorsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ export const isNoDbAccessError = ({ code, message }: any) =>
code === 'Neo.ClientError.Security.Forbidden' &&
/Database access is not allowed/i.test(message)

export const isPeriodicCommitError = ({ code, message }: any) =>
const isCallInTransactionError = ({ code, message }: any) =>
code === 'Neo.DatabaseError.Statement.ExecutionFailed' &&
/in an implicit transaction/.test(message)

const isPeriodicCommitError = ({ code, message }: any) =>
code === 'Neo.ClientError.Statement.SemanticError' &&
/in an open transaction is not possible/i.test(message)
[
/in an open transaction is not possible/i,
/tried to execute in an explicit transaction/i
].some(reg => reg.test(message))

export const isImplicitTransactionError = (error: any) =>
isPeriodicCommitError(error) || isCallInTransactionError(error)