Replace Bytecode with GremlinLang in JavaScript GLV#3307
Replace Bytecode with GremlinLang in JavaScript GLV#3307kirill-stepanishin wants to merge 1 commit intoapache:masterfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3307 +/- ##
============================================
- Coverage 77.87% 75.80% -2.07%
+ Complexity 13578 13271 -307
============================================
Files 1015 1020 +5
Lines 59308 59864 +556
Branches 6835 7034 +199
============================================
- Hits 46184 45379 -805
- Misses 10817 11815 +998
- Partials 2307 2670 +363 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Cole-Greer
left a comment
There was a problem hiding this comment.
Overall I think this looks really good. Most of my comments are just for context and not anything which needs action in this PR, the rest are all for minor clarifications and tweaks.
| 'materializeProperties', | ||
| 'bulkResults', | ||
| ]; | ||
| for (const strategy of strategies) { |
There was a problem hiding this comment.
I expect all of the strategy handling here will need to be adjusted in the future. I think this is good enough for now and we can revisit this as we get more pieces of the driver functioning and get more tests running.
| return this.withStrategies(new OptionsStrategy({ [key]: val })); | ||
| } | ||
| optionsStrategy[1].configuration[key] = val; | ||
| glStrategies[glStrategies.length - 1].configuration[key] = val; |
There was a problem hiding this comment.
I've got some questions around how OptionsStrategy needs to work as all the pieces of this driver come together. What you have here looks about right for now. It will become easier to reason about if any adjustments are needed here once more of the driver is complete.
| if (arg === null || arg === undefined) return 'null'; | ||
| if (typeof arg === 'boolean') return arg ? 'true' : 'false'; | ||
| if (arg instanceof Long) { | ||
| return String(arg.value); |
There was a problem hiding this comment.
I think we should probably append an L suffix on longs.
| return String(arg.value); | ||
| } | ||
| if (arg instanceof Date) { | ||
| const iso = arg.toISOString().replace('.000Z', 'Z'); |
There was a problem hiding this comment.
Was this added to be more consistent with Java, Python, and Go? I think it's ok to save a step here and keep .000Z as both are equivalent. Not sure if anyone has other opinions on this.
There was a problem hiding this comment.
I checked Java, Python, and Go, all three omit .000 for zero-millisecond dates. I did it for consistency, but I would be happy to remove it if that makes more sense
There was a problem hiding this comment.
I slightly prefer removal but that's not a strong preference. Given the lack of other opinions, I'm happy to defer to you as the PR author for this one.
| return String(arg); | ||
| } | ||
| if (typeof arg === 'string') { | ||
| const escaped = JSON.stringify(arg).slice(1, -1).replace(/'/g, "\\'"); |
There was a problem hiding this comment.
I'm not understanding what the JSON.stringify() and slice() are doing here. Could we just do a replace() directly on arg itself?
There was a problem hiding this comment.
JSON.stringify() escapes all special characters in one call, but wraps the result in double quotes and doesn't escape single quotes.
So .slice(1, -1) to strip the double quotes, .replace() to escape single quotes, then wrap in single quotes for Gremlin.
I think it also could either be done by nesting .replace() for every case or with a single .replace() + regex + replacer function, but it'd be more verbose and require manually maintaining the escape mapping.
I'll add comment to clarify.
There was a problem hiding this comment.
This is reminding me that our argument escaping is somewhat inconsistent across GLVs, we really standardize and document recommendations for escaping gremlin-lang scripts. Spinning this out into its own JIRA: https://issues.apache.org/jira/browse/TINKERPOP-3233
| // #91 | ||
| [g.withStrategies(new SubgraphStrategy({vertices: __.has('region','US-TX'), vertexProperties: __.hasNot('runways')})).V().count(), "g.withStrategies(new SubgraphStrategy(vertices:__.has('region','US-TX'),vertexProperties:__.hasNot('runways'))).V().count()"], | ||
| // #92 | ||
| [g.withStrategies(new ReadOnlyStrategy(), new SubgraphStrategy({vertices: __.has('region','US-TX'), edges: __.hasLabel('route')})).V().count(), "g.withStrategies(ReadOnlyStrategy,new SubgraphStrategy(vertices:__.has('region','US-TX'),edges:__.hasLabel('route'))).V().count()"], |
There was a problem hiding this comment.
I'm noticing the new keyword is only used in the GremlinLang if the strategy has arguments (ReadOnlyStrategy does not use new in this case, but SubgraphStrategy does). That appears to be consistent with Java but it is odd. I think that's fine for now but might be worth a revisit later.
|
|
||
| describe('JS-specific tests', function () { | ||
| it('should handle Long values', function () { | ||
| assert.strictEqual(g.V(new Long('9007199254740993')).getGremlinLang().getGremlin(), 'g.V(9007199254740993)'); |
There was a problem hiding this comment.
I think this should result in 'g.V(9007199254740993L)'
| assert.strictEqual(bytecode.stepInstructions[2][2].elementName, 'desc'); | ||
| }); | ||
|
|
||
| it('should add steps with Direction aliases from_ and to properly mapped to OUT and IN', function () { |
There was a problem hiding this comment.
I think these test cases are still valuable, although they should just assert the final GremlinLang string matches the expected result instead of all of these assertions for the bytecode. The first 2 and final cases all appear to be adequately covered by gremlin-lang-test already, but this case is not. Could you restore the test here or add this case to gremlin-lang-test?
| assert.strictEqual(vStartBytecode.stepInstructions[0][2], 2); // ID should be extracted from Vertex | ||
|
|
||
| // Test V() step in the middle of a traversal | ||
| const vMid = g.inject('foo').V(1, new Vertex(2, 'person')); |
There was a problem hiding this comment.
Similar to my above comment, I don't see an equivalent mid-traversal V() case in gremlin-lang-test
| assert.strictEqual(fromToBytecode.stepInstructions[2][0], 'to'); | ||
| assert.strictEqual(fromToBytecode.stepInstructions[2][1], 2); // ID should be extracted from Vertex | ||
| }); | ||
| it('should extract ID from Vertex objects for mergeE()', function () { |
There was a problem hiding this comment.
Similar to above, I don't see an equivalent for this in gremlin-lang-test.
|
The overall design is similar to what has been done to the other GLVs for GremlinLang so my VOTE is +1, pending resolution of other comments. |
| const iso = arg.toISOString().replace('.000Z', 'Z'); | ||
| return `datetime("${iso}")`; | ||
| } | ||
| if (typeof arg === 'number') { |
There was a problem hiding this comment.
Given JS doesn't distinguish the number types, I wonder if we should be identifying the numerical types in gremlin by asserting their sizes for adding the suffix instead of just turning it to string directly?
There was a problem hiding this comment.
Numbers will be a giant mess here as there's no clear way to assume the users intent and map number into all of the TP numeric types. I expect we will need to accept some amount of precision loss and/or non-roundtripable types. My vote is to leave this for now and handle it separately. That topic alone can easily spawn many debates around handling subtle edge cases.
|
Overall I think this is a solid start for switching to GremlinLang in JS. There will be areas that needs follow-up changes, but I'm good to leave them out of this PR. VOTE +1 |
As part of the TP4 migration, the server no longer accepts Bytecode and only processes Gremlin queries in script form.. This PR replaces
Bytecode+Translatorwith a newGremlinLangclass.Changes
GremlinLangclass that incrementally builds Gremlin query strings as steps are addedGraphTraversalSource/GraphTraversalnow accumulate intoGremlinLanginstead ofBytecodeClient.submit()only accepts strings now; all queries sent asgremlin-langlanguage.OptionsStrategyextracted intoRequestOptions(timeout, batchSize, bulkResults, etc.)Bytecode,Translator,BytecodeSerializer, and their testsIntentionally out of scope for this PR