Skip to content

Commit

Permalink
TINKERPOP-2999 3.7.0 Remote Console Sends Incomplete Queries (#2297)
Browse files Browse the repository at this point in the history
* Add handling for multiline remote console commands

Fixes issue where multiline commands would throw error on the first line, caused by upgrade to Groovy 4
Handling pulled from Groovysh.groovy

* Update CHANGELOG.asciidoc
  • Loading branch information
ryn5 authored Oct 24, 2023
1 parent 63364f1 commit 22759b8
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This release also includes changes from <<release-3-6-5, 3.6.6>> and <<release-3
* Corrected `concat()` signatures in `gremlin-dotnet`, `Concat()` is now used instead of `Concat<object>()`. *(breaking)*
* Update `concat()` to not special treat `inject` in arguments and use `TraversalUtil.apply` on it as with any other child traversals. *(breaking)*
* Checked graph features for meta-property support before trying to serialize them in `VertexPropertySerializer` for GraphBinary.
* Fixed multiline query bug in console caused by upgrade to Groovy 4.
* Added date manipulation steps `asDate`, `dateAdd` and `dateDiff`.
* Added new data type `DT` to represent periods of time.
* Added Gherkin support for Date.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import org.apache.groovy.groovysh.Groovysh
import org.apache.groovy.groovysh.ParseCode
import org.apache.groovy.groovysh.Parser
import org.apache.groovy.groovysh.util.CommandArgumentParser
import org.apache.groovy.groovysh.util.ScriptVariableAnalyzer
import org.apache.tinkerpop.gremlin.console.commands.GremlinSetCommand
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.tools.shell.IO

Expand Down Expand Up @@ -102,11 +104,40 @@ class GremlinGroovysh extends Groovysh {
List<String> current = new ArrayList<String>(buffers.current())
current << line

String importsSpec = this.getImportStatements()

// determine if this script is complete or not - if not it's a multiline script
def status = parser.parse(current)
def status = parser.parse([importsSpec] + current)

switch (status.code) {
case ParseCode.COMPLETE:
if (!Boolean.valueOf(getPreference(INTERPRETER_MODE_PREFERENCE_KEY, 'false')) || isTypeOrMethodDeclaration(current)) {
// Evaluate the current buffer w/imports and dummy statement
List buff = [importsSpec] + [ 'true' ] + current
try {
interp.evaluate(buff)
} catch(MultipleCompilationErrorsException t) {
if (isIncompleteCaseOfAntlr4(t)) {
// treat like INCOMPLETE case
buffers.updateSelected(current)
break
}
throw t
}
} else {
// Evaluate Buffer wrapped with code storing bounded vars
try {
evaluateWithStoredBoundVars(importsSpec, current)
} catch(MultipleCompilationErrorsException t) {
if (isIncompleteCaseOfAntlr4(t)) {
// treat like INCOMPLETE case
buffers.updateSelected(current)
break
}
throw t
}
}

// concat script here because commands don't support multi-line
def script = String.join(Parser.getNEWLINE(), current)
setLastResult(mediator.currentRemote().submit([script]))
Expand Down Expand Up @@ -140,4 +171,48 @@ class GremlinGroovysh extends Groovysh {

maybeRecordResult(result)
}

private Object evaluateWithStoredBoundVars(String importsSpec, List<String> current) {
Object result
String variableBlocks = null
// To make groovysh behave more like an interpreter, we need to retrieve all bound
// vars at the end of script execution, and then update them into the groovysh Binding context.
Set<String> boundVars = ScriptVariableAnalyzer.getBoundVars(importsSpec + Parser.NEWLINE + current.join(Parser.NEWLINE), interp.classLoader)
if (boundVars) {
variableBlocks = "$COLLECTED_BOUND_VARS_MAP_VARNAME = new HashMap();"
boundVars.each({ String varname ->
// bound vars can be in global or some local scope.
// We discard locally scoped vars by ignoring MissingPropertyException
variableBlocks += """
try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
} catch (MissingPropertyException e){}"""
})
}
// Evaluate the current buffer w/imports and dummy statement
List<String> buff
if (variableBlocks) {
buff = [importsSpec] + ['try {', 'true'] + current + ['} finally {' + variableBlocks + '}']
} else {
buff = [importsSpec] + ['true'] + current
}
setLastResult(result = interp.evaluate(buff))

if (variableBlocks) {
def boundVarValues = (Map<String, Object>) interp.context.getVariable(COLLECTED_BOUND_VARS_MAP_VARNAME)
boundVarValues.each({ String name, Object value -> interp.context.setVariable(name, value) })
}

return result
}

private boolean isIncompleteCaseOfAntlr4(MultipleCompilationErrorsException t) {
// TODO antlr4 parser errors pop out here - can we rework to be like antlr2?
(
(t.message.contains('Unexpected input: ') || t.message.contains('Unexpected character: ')) && !(
t.message.contains("Unexpected input: '}'")
|| t.message.contains("Unexpected input: ')'")
|| t.message.contains("Unexpected input: ']'")
)
)
}
}

0 comments on commit 22759b8

Please sign in to comment.