CINXE.COM

Introduction

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Asciidoctor 2.0.18"> <title>Introduction</title> <style> /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*! normalize.css v2.1.2 | MIT License | git.io/normalize */ /* ========================================================================== HTML5 display definitions ========================================================================== */ /** Correct `block` display not defined in IE 8/9. */ @import url(http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.css); article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } /** Correct `inline-block` display not defined in IE 8/9. */ audio, canvas, video { display: inline-block; } /** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */ [hidden], template { display: none; } script { display: none !important; } /* ========================================================================== Base ========================================================================== */ /** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** Remove default margin. */ body { margin: 0; } /* ========================================================================== Links ========================================================================== */ /** Remove the gray background color from active links in IE 10. */ a { background: transparent; } /** Address `outline` inconsistency between Chrome and other browsers. */ a:focus { outline: thin dotted; } /** Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* ========================================================================== Typography ========================================================================== */ /** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** Address styling not present in IE 8/9, Safari 5, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */ b, strong { font-weight: bold; } /** Address styling not present in Safari 5 and Chrome. */ dfn { font-style: italic; } /** Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** Correct font family set oddly in Safari 5 and Chrome. */ code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; } /** Improve readability of pre-formatted text in all browsers. */ pre { white-space: pre-wrap; } /** Set consistent quote types. */ q { quotes: "\201C" "\201D" "\2018" "\2019"; } /** Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* ========================================================================== Embedded content ========================================================================== */ /** Remove border when inside `a` element in IE 8/9. */ img { border: 0; } /** Correct overflow displayed oddly in IE 9. */ svg:not(:root) { overflow: hidden; } /* ========================================================================== Figures ========================================================================== */ /** Address margin not present in IE 8/9 and Safari 5. */ figure { margin: 0; } /* ========================================================================== Forms ========================================================================== */ /** Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */ button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ } /** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */ button, input { line-height: normal; } /** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */ button, select { text-transform: none; } /** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */ button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */ textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ } /* ========================================================================== Tables ========================================================================== */ /** Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } meta.foundation-mq-small { font-family: "only screen and (min-width: 768px)"; width: 768px; } meta.foundation-mq-medium { font-family: "only screen and (min-width:1280px)"; width: 1280px; } meta.foundation-mq-large { font-family: "only screen and (min-width:1440px)"; width: 1440px; } *, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } html, body { font-size: 100%; } body { background: white; color: #222222; padding: 0; margin: 0; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; } a:hover { cursor: pointer; } img, object, embed { max-width: 100%; height: auto; } object, embed { height: 100%; } img { -ms-interpolation-mode: bicubic; } #map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; } .left { float: left !important; } .right { float: right !important; } .text-left { text-align: left !important; } .text-right { text-align: right !important; } .text-center { text-align: center !important; } .text-justify { text-align: justify !important; } .hide { display: none; } .antialiased, body { -webkit-font-smoothing: antialiased; } img { display: inline-block; vertical-align: middle; } textarea { height: auto; min-height: 50px; } select { width: 100%; } p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; } .subheader, #content #toctitle, .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .mathblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title, .tableblock > caption { line-height: 1.4; color: #6c818f; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; } /* Typography resets */ div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; } /* Default Link Styles */ a { color: #444444; text-decoration: underline; line-height: inherit; } a:hover, a:focus { color: #111111; } a img { border: none; } /* Default paragraph styles */ p { font-family: inherit; font-weight: normal; font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; text-rendering: optimizeLegibility; } p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; } /* Default header styles */ h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; font-weight: bold; font-style: normal; color: #465158; text-rendering: optimizeLegibility; margin-top: 1em; margin-bottom: 0.5em; line-height: 1.2125em; } h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #909ea7; line-height: 0; } h1 { font-size: 2.125em; } h2 { font-size: 1.6875em; } h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; } h4 { font-size: 1.125em; } h5 { font-size: 1.125em; } h6 { font-size: 1em; } hr { border: solid #dddddd; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; } /* Helpful Typography Defaults */ em, i { font-style: italic; line-height: inherit; } strong, b { font-weight: bold; line-height: inherit; } small { font-size: 60%; line-height: inherit; } code { font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; color: #444444; } /* Lists */ ul, ol, dl { font-size: 1em; line-height: 1.5; margin-bottom: 1.25em; list-style-position: outside; font-family: inherit; } ul, ol { margin-left: 0; } ul.no-bullet, ol.no-bullet { margin-left: 0; } /* Unordered Lists */ ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ } ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; } ul.square { list-style-type: square; } ul.circle { list-style-type: circle; } ul.disc { list-style-type: disc; } ul.no-bullet { list-style: none; } /* Ordered Lists */ ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; } /* Definition Lists */ dl dt { margin-bottom: 0.3em; font-weight: bold; } dl dd { margin-bottom: 0.75em; } /* Abbreviations */ abbr, acronym { text-transform: uppercase; font-size: 90%; color: black; border-bottom: 1px dotted #dddddd; cursor: help; } abbr { text-transform: none; } /* Blockquotes */ blockquote { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; } blockquote cite { display: block; font-size: 0.8125em; color: #748590; } blockquote cite:before { content: "\2014 \0020"; } blockquote cite a, blockquote cite a:visited { color: #748590; } blockquote, blockquote p { line-height: 1.5; color: #909ea7; } /* Microformats */ .vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #dddddd; padding: 0.625em 0.75em; } .vcard li { margin: 0; display: block; } .vcard .fn { font-weight: bold; font-size: 0.9375em; } .vevent .summary { font-weight: bold; } .vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; } @media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; } h1 { font-size: 2.75em; } h2 { font-size: 2.3125em; } h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; } h4 { font-size: 1.4375em; } } /* Print styles. Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com) */ .print-only { display: none !important; } @media print { * { background: transparent !important; color: #000 !important; /* Black prints faster: h5bp.com/s */ box-shadow: none !important; text-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; /* h5bp.com/t */ } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } @page { margin: 0.5cm; } p, h2, h3, #toctitle, .sidebarblock > .content > .title { orphans: 3; widows: 3; } h2, h3, #toctitle, .sidebarblock > .content > .title { page-break-after: avoid; } .hide-on-print { display: none !important; } .print-only { display: block !important; } .hide-for-print { display: none !important; } .show-for-print { display: inherit !important; } } /* Tables */ table { background: white; margin-bottom: 1.25em; border: solid 0 #dddddd; } table thead, table tfoot { background: none; font-weight: bold; } table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222222; text-align: left; } table tr th, table tr td { padding: 1px 8px 1px 5px; font-size: 1em; color: #222222; } table tr.even, table tr.alt, table tr:nth-of-type(even) { background: none; } table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.5; } .clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; } .clearfix:after, .float-group:after { clear: both; } *:not(pre) > code { font-size: 0.95em; padding: 0; white-space: nowrap; background-color: #f2f2f2; border: 0 solid #dddddd; -webkit-border-radius: 6px; border-radius: 6px; text-shadow: none; } pre, pre > code { line-height: 1.2; color: inherit; font-family: "Consolas", "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace; font-weight: normal; } .keyseq { color: #333333; } kbd:not(.keyseq) { display: inline-block; color: black; font-size: 0.75em; line-height: 1.4; background-color: #F7F7F7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset; margin: -0.15em 0.15em 0 0.15em; padding: 0.2em 0.6em 0.2em 0.5em; vertical-align: middle; white-space: nowrap; } .keyseq kbd:first-child { margin-left: 0; } .keyseq kbd:last-child { margin-right: 0; } .menuseq, .menu { color: black; } b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; } b.button:before { content: "["; padding: 0 3px 0 2px; } b.button:after { content: "]"; padding: 0 2px 0 3px; } p a > code:hover { color: #373737; } #header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 0.9375em; padding-right: 0.9375em; } #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; } #header:after, #content:after, #footnotes:after, #footer:after { clear: both; } #header { margin-bottom: 2.5em; } #header > h1 { color: #111111; font-weight: bold; border-bottom: 1px solid #dddddd; margin-bottom: -28px; padding-bottom: 32px; } #header span { color: #909ea7; } #header #revnumber { text-transform: capitalize; } #header br { display: none; } #header br + span { padding-left: 3px; } #header br + span:before { content: "\2013 \0020"; } #header br + span.author { padding-left: 0; } #header br + span.author:before { content: ", "; } #toc { border-bottom: 1px solid #dddddd; padding-bottom: 1.25em; } #toc > ul { margin-left: 0.25em; } #toc ul.sectlevel0 > li > a { font-style: italic; } #toc ul.sectlevel0 ul.sectlevel1 { margin-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } #toc ul { list-style-type: none; } #toctitle { color: #6c818f; } @media only screen and (min-width: 768px) { body.toc2 { padding-left: 15em; padding-right: 0; } #toc.toc2 { position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #dddddd; border-bottom: 0; z-index: 1000; padding: 1em; height: 100%; overflow: auto; } #toc.toc2 #toctitle { margin-top: 0; font-size: 1.2em; } #toc.toc2 > ul { font-size: .90em; } #toc.toc2 ul ul { margin-left: 0; padding-left: 1em; } #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; } body.toc2.toc-right { padding-left: 0; padding-right: 15em; } body.toc2.toc-right #toc.toc2 { border-right: 0; border-left: 1px solid #dddddd; left: auto; right: 0; } } @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; padding-right: 0; } #toc.toc2 { width: 20em; } #toc.toc2 #toctitle { font-size: 1.375em; } #toc.toc2 > ul { font-size: 0.95em; } #toc.toc2 ul ul { padding-left: 1.25em; } body.toc2.toc-right { padding-left: 0; padding-right: 20em; } } #content #toc { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; border-width: 0; -webkit-border-radius: 6px; border-radius: 6px; } #content #toc > :first-child { margin-top: 0; } #content #toc > :last-child { margin-bottom: 0; } #content #toc a { text-decoration: none; } #content #toctitle { font-weight: bold; font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, Arial, "Helvetica Neue", sans-serif; font-size: 1em; padding-left: 0.125em; } #footer { max-width: 100%; background-color: black; padding: 1.25em; } #footer-text { color: white; line-height: 1.35; } .sect1 { padding-bottom: 1.25em; } .sect1 + .sect1 { border-top: 1px solid #dddddd; } #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; width: 1em; margin-left: -1em; display: block; text-decoration: none; visibility: hidden; text-align: center; font-weight: normal; } #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: '\00A7'; font-size: .85em; vertical-align: text-top; display: block; margin-top: 0.05em; } #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; } #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: #465158; text-decoration: none; } #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: #3b444a; } .imageblock, .literalblock, .listingblock, .mathblock, .verseblock, .videoblock { margin-bottom: 1.25em; } .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .mathblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-align: left; font-weight: bold; } .tableblock > caption { text-align: left; font-weight: bold; white-space: nowrap; overflow: visible; max-width: 0; } table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; } .admonitionblock > table { border: 0; background: none; width: 100%; } .admonitionblock > table td.icon { text-align: center; width: 80px; } .admonitionblock > table td.icon img { max-width: none; } .admonitionblock > table td.icon .title { font-weight: bold; text-transform: uppercase; } .admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #dddddd; color: #909ea7; } .admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; } .exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: white; -webkit-border-radius: 6px; border-radius: 6px; } .exampleblock > .content > :first-child { margin-top: 0; } .exampleblock > .content > :last-child { margin-bottom: 0; } .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6, .exampleblock > .content p { color: #333333; } .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6 { line-height: 1; margin-bottom: 0.625em; } .exampleblock > .content h1.subheader, .exampleblock > .content h2.subheader, .exampleblock > .content h3.subheader, .exampleblock > .content .subheader#toctitle, .sidebarblock.exampleblock > .content > .subheader.title, .exampleblock > .content h4.subheader, .exampleblock > .content h5.subheader, .exampleblock > .content h6.subheader { line-height: 1.4; } .exampleblock.result > .content { -webkit-box-shadow: 0 1px 8px #d9d9d9; box-shadow: 0 1px 8px #d9d9d9; } .sidebarblock { border-style: solid; border-width: 1px; border-color: #d9d9d9; margin-bottom: 1.25em; padding: 1.25em; background: #f2f2f2; -webkit-border-radius: 6px; border-radius: 6px; } .sidebarblock > :first-child { margin-top: 0; } .sidebarblock > :last-child { margin-bottom: 0; } .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6, .sidebarblock p { color: #333333; } .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6 { line-height: 1; margin-bottom: 0.625em; } .sidebarblock h1.subheader, .sidebarblock h2.subheader, .sidebarblock h3.subheader, .sidebarblock .subheader#toctitle, .sidebarblock > .content > .subheader.title, .sidebarblock h4.subheader, .sidebarblock h5.subheader, .sidebarblock h6.subheader { line-height: 1.4; } .sidebarblock > .content > .title { color: #6c818f; margin-top: 0; line-height: 1.5; } .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; } .literalblock pre:not([class]), .listingblock pre:not([class]) { background: #eeeeee; } .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { border-width: 1px; border-style: solid; border-color: #cccccc; -webkit-border-radius: 6px; border-radius: 6px; padding: 0.5em; word-wrap: break-word; } .literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; } .literalblock pre > code, .literalblock pre[class] > code, .listingblock pre > code, .listingblock pre[class] > code { display: block; } @media only screen { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.76em; } } @media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.855em; } } @media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.95em; } } .listingblock pre.highlight { padding: 0; } .listingblock pre.highlight > code { padding: 0.5em; } .listingblock > .content { position: relative; } .listingblock:hover code[class*=" language-"]:before { text-transform: uppercase; font-size: 0.9em; color: #999; position: absolute; top: 0.375em; right: 0.375em; } .listingblock:hover code.asciidoc:before { content: "asciidoc"; } .listingblock:hover code.clojure:before { content: "clojure"; } .listingblock:hover code.css:before { content: "css"; } .listingblock:hover code.groovy:before { content: "groovy"; } .listingblock:hover code.html:before { content: "html"; } .listingblock:hover code.java:before { content: "java"; } .listingblock:hover code.javascript:before { content: "javascript"; } .listingblock:hover code.python:before { content: "python"; } .listingblock:hover code.ruby:before { content: "ruby"; } .listingblock:hover code.sass:before { content: "sass"; } .listingblock:hover code.scss:before { content: "scss"; } .listingblock:hover code.xml:before { content: "xml"; } .listingblock:hover code.yaml:before { content: "yaml"; } .listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; } .listingblock.terminal pre .command:not([data-prompt]):before { content: '$'; } table.pyhltable { border: 0; margin-bottom: 0; } table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; } table.pyhltable td.code { padding-left: .75em; padding-right: 0; } .highlight.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #dddddd; } .highlight.pygments .lineno { display: inline-block; margin-right: .25em; } table.pyhltable .linenodiv { background-color: transparent !important; padding-right: 0 !important; } .quoteblock { margin: 0 0 1.25em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #dddddd; } .quoteblock blockquote { margin: 0 0 1.25em 0; padding: 0 0 0.5625em 0; border: 0; } .quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; } .quoteblock .attribution { margin-top: -.25em; padding-bottom: 0.5625em; font-size: 0.8125em; color: #748590; } .quoteblock .attribution br { display: none; } .quoteblock .attribution cite { display: block; margin-bottom: 0.625em; } table thead th, table tfoot th { font-weight: bold; } table.tableblock.grid-all { border-collapse: separate; border-spacing: 1px; -webkit-border-radius: 6px; border-radius: 6px; border-top: 0 solid #dddddd; border-bottom: 0 solid #dddddd; } table.tableblock.frame-topbot, table.tableblock.frame-none { border-left: 0; border-right: 0; } table.tableblock.frame-sides, table.tableblock.frame-none { border-top: 0; border-bottom: 0; } table.tableblock td .paragraph:last-child p > p:last-child, table.tableblock th > p:last-child, table.tableblock td > p:last-child { margin-bottom: 0; } th.tableblock.halign-left, td.tableblock.halign-left { text-align: left; } th.tableblock.halign-right, td.tableblock.halign-right { text-align: right; } th.tableblock.halign-center, td.tableblock.halign-center { text-align: center; } th.tableblock.valign-top, td.tableblock.valign-top { vertical-align: top; } th.tableblock.valign-bottom, td.tableblock.valign-bottom { vertical-align: bottom; } th.tableblock.valign-middle, td.tableblock.valign-middle { vertical-align: middle; } tbody tr th { display: table-cell; line-height: 1.5; background: none; } tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: #222222; font-weight: bold; } td > div.verse { white-space: pre; } ol { margin-left: 0.25em; } ul li ol { margin-left: 0; } dl dd { margin-left: 1.125em; } dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; } ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.625em; } ul.unstyled, ol.unnumbered, ul.checklist, ul.none { list-style-type: none; } ul.unstyled, ol.unnumbered, ul.checklist { margin-left: 0.625em; } ul.checklist li > p:first-child > i[class^="icon-check"]:first-child, ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; } ul.checklist li > p:first-child > input[type="checkbox"]:first-child { position: relative; top: 1px; } ul.inline { margin: 0 auto 0.625em auto; margin-left: -1.375em; margin-right: 0; padding: 0; list-style: none; overflow: hidden; } ul.inline > li { list-style: none; float: left; margin-left: 1.375em; display: block; } ul.inline > li > * { display: block; } .unstyled dl dt { font-weight: normal; font-style: normal; } ol.arabic { list-style-type: decimal; } ol.decimal { list-style-type: decimal-leading-zero; } ol.loweralpha { list-style-type: lower-alpha; } ol.upperalpha { list-style-type: upper-alpha; } ol.lowerroman { list-style-type: lower-roman; } ol.upperroman { list-style-type: upper-roman; } ol.lowergreek { list-style-type: lower-greek; } .hdlist > table, .colist > table { border: 0; background: none; } .hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; } td.hdlist1 { padding-right: .75em; font-weight: bold; } td.hdlist1, td.hdlist2 { vertical-align: top; } .literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; } .colist > table tr > td:first-of-type { padding: 0 .75em; line-height: 1; } .colist > table tr > td:last-of-type { padding: 0.25em 0; } .qanda > ol > li > p > em:only-child { color: #373737; } .thumb, .th { line-height: 0; display: inline-block; border: solid 4px white; -webkit-box-shadow: 0 0 0 1px #dddddd; box-shadow: 0 0 0 1px #dddddd; } .imageblock.left, .imageblock[style*="float: left"] { margin: 0.25em 0.625em 1.25em 0; } .imageblock.right, .imageblock[style*="float: right"] { margin: 0.25em 0 1.25em 0.625em; } .imageblock > .title { margin-bottom: 0; } .imageblock.thumb, .imageblock.th { border-width: 6px; } .imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; } .image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; } .image.left { margin-right: 0.625em; } .image.right { margin-left: 0.625em; } a.image { text-decoration: none; } span.footnote, span.footnoteref { vertical-align: super; font-size: 0.875em; } span.footnote a, span.footnoteref a { text-decoration: none; } #footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; } #footnotes hr { width: 20%; min-width: 6.25em; margin: -.25em 0 .75em 0; border-width: 1px 0 0 0; } #footnotes .footnote { padding: 0 0.375em; line-height: 1.3; font-size: 0.875em; margin-left: 1.2em; text-indent: -1.2em; margin-bottom: .2em; } #footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; } #footnotes .footnote:last-of-type { margin-bottom: 0; } #content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; } .gist .file-data > table { border: none; background: #fff; width: 100%; margin-bottom: 0; } .gist .file-data > table td.line-data { width: 99%; } div.unbreakable { page-break-inside: avoid; } .big { font-size: larger; } .small { font-size: smaller; } .underline { text-decoration: underline; } .overline { text-decoration: overline; } .line-through { text-decoration: line-through; } .aqua { color: #00bfbf; } .aqua-background { background-color: #00fafa; } .black { color: black; } .black-background { background-color: black; } .blue { color: #0000bf; } .blue-background { background-color: #0000fa; } .fuchsia { color: #bf00bf; } .fuchsia-background { background-color: #fa00fa; } .gray { color: #606060; } .gray-background { background-color: #7d7d7d; } .green { color: #006000; } .green-background { background-color: #007d00; } .lime { color: #00bf00; } .lime-background { background-color: #00fa00; } .maroon { color: #600000; } .maroon-background { background-color: #7d0000; } .navy { color: #000060; } .navy-background { background-color: #00007d; } .olive { color: #606000; } .olive-background { background-color: #7d7d00; } .purple { color: #600060; } .purple-background { background-color: #7d007d; } .red { color: #bf0000; } .red-background { background-color: #fa0000; } .silver { color: #909090; } .silver-background { background-color: #bcbcbc; } .teal { color: #006060; } .teal-background { background-color: #007d7d; } .white { color: #bfbfbf; } .white-background { background-color: #fafafa; } .yellow { color: #bfbf00; } .yellow-background { background-color: #fafa00; } span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } .admonitionblock td.icon [class^="icon-"]:before { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; } .admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #444444; color: #333333; } .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; } .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; } .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; } .admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; } .conum { display: inline-block; color: white !important; background-color: black; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; width: 20px; height: 20px; font-size: 12px; font-weight: bold; line-height: 20px; font-family: Arial, sans-serif; font-style: normal; position: relative; top: -2px; letter-spacing: -1px; } .conum * { color: white !important; } .conum + b { display: none; } .conum:after { content: attr(data-value); } .conum:not([data-value]):empty { display: none; } .listingblock code { white-space: pre; overflow: auto; overflow-wrap: normal; /* needed for webkit browsers */ } #toc ul.sectlevel0 > li > a { font-style: normal; font-weight: bold; } h4 { color: #6c818f; } .literalblock > .content > pre, .listingblock > .content > pre { -webkit-border-radius: 6px; border-radius: 6px; margin-left: 2em; margin-right: 2em; } .admonitionblock { margin-left: 2em; margin-right: 2em; } .admonitionblock > table { border: 1px solid #609060; border-top-width: 1.5em; background-color: #e9ffe9; border-collapse: separate; -webkit-border-radius: 0; border-radius: 0; } .admonitionblock > table td.icon { padding-top: .5em; padding-bottom: .5em; } .admonitionblock > table td.content { padding: .5em 1em; color: black; font-size: .9em; border-left: none; } .sidebarblock { background-color: #e8ecef; border-color: #ccc; } .sidebarblock > .content > .title { color: #444444; } table.tableblock.grid-all { border-collapse: collapse; -webkit-border-radius: 0; border-radius: 0; } table.tableblock.grid-all th.tableblock, table.tableblock.grid-all td.tableblock { border-bottom: 1px solid #aaa; } #footer { background-color: #465158; padding: 2em; } #footer-text { color: #eee; font-size: 0.8em; text-align: center; } .tabs{position:relative;margin:40px auto;width:1024px;max-width:100%;overflow:hidden;padding-top:10px;margin-bottom:60px}.tabs input{position:absolute;z-index:1000;height:50px;left:0;top:0;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;margin:0}.tabs input:hover+label{background:#e08f24}.tabs label{background:#e9ffe9;color:#1a1a1a;font-size:15px;line-height:50px;height:60px;position:relative;top:0;padding:0 20px;float:left;display:block;letter-spacing:1px;text-transform:uppercase;font-weight:bold;text-align:center;box-shadow:2px 0 2px rgba(0,0,0,0.1),-2px 0 2px rgba(0,0,0,0.1);box-sizing:border-box;-webkit-transition:all 150ms ease 0s;transition:all 150ms ease 0s}.tabs label:hover{cursor:pointer}.tabs label:after{content:'';background:#609060;position:absolute;bottom:-2px;left:0;width:100%;height:2px;display:block}.tabs-2 input{width:50%}.tabs-2 input.tab-selector-1{left:0%}.tabs-2 input.tab-selector-2{left:50%}.tabs-2 label{width:50%}.tabs-3 input{width:33.3333333333%}.tabs-3 input.tab-selector-1{left:0%}.tabs-3 input.tab-selector-2{left:33.3333333333%}.tabs-3 input.tab-selector-3{left:66.6666666667%}.tabs-3 label{width:33.3333333333%}.tabs-4 input{width:25%}.tabs-4 input.tab-selector-1{left:0%}.tabs-4 input.tab-selector-2{left:25%}.tabs-4 input.tab-selector-3{left:50%}.tabs-4 input.tab-selector-4{left:75%}.tabs-4 label{width:25%}.tabs-5 input{width:20%}.tabs-5 input.tab-selector-1{left:0%}.tabs-5 input.tab-selector-2{left:20%}.tabs-5 input.tab-selector-3{left:40%}.tabs-5 input.tab-selector-4{left:60%}.tabs-5 input.tab-selector-5{left:80%}.tabs-5 label{width:20%}.tabs-6 input{width:16.6666666667%}.tabs-6 input.tab-selector-1{left:0%}.tabs-6 input.tab-selector-2{left:16.6666666667%}.tabs-6 input.tab-selector-3{left:33.3333333333%}.tabs-6 input.tab-selector-4{left:50%}.tabs-6 input.tab-selector-5{left:66.6666666667%}.tabs-6 input.tab-selector-6{left:83.3333333333%}.tabs-6 label{width:16.6666666667%}.tabs-7 input{width:14.2857142857%}.tabs-7 input.tab-selector-1{left:0%}.tabs-7 input.tab-selector-2{left:14.2857142857%}.tabs-7 input.tab-selector-3{left:28.5714285714%}.tabs-7 input.tab-selector-4{left:42.8571428571%}.tabs-7 input.tab-selector-5{left:57.1428571429%}.tabs-7 input.tab-selector-6{left:71.4285714286%}.tabs-7 input.tab-selector-7{left:85.7142857143%}.tabs-7 label{width:14.2857142857%}.tabs label:first-of-type{z-index:4}.tab-label-2{z-index:4}.tab-label-3{z-index:3}.tab-label-4{z-index:2}.tabs input:checked+label{background:#609060;color:#fefefe;z-index:6}.clear-shadow{clear:both}.tabcontent{height:auto;width:100%;float:left;position:relative;z-index:5;background:#eee;top:-10px;box-sizing:border-box}.tabcontent>div{position:relative;float:left;width:0;height:0;box-sizing:border-box;top:0;left:0;z-index:1;opacity:0;background:#eee}.tabcontent .CodeRay{background-color:#fefefe}.tabs .tab-selector-1:checked ~ .tabcontent .tabcontent-1{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-2:checked ~ .tabcontent .tabcontent-2{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-3:checked ~ .tabcontent .tabcontent-3{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-4:checked ~ .tabcontent .tabcontent-4{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-5:checked ~ .tabcontent .tabcontent-5{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-6:checked ~ .tabcontent .tabcontent-6{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px}.tabs .tab-selector-7:checked ~ .tabcontent .tabcontent-7{z-index:100;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1;width:100%;height:auto;width:100%;height:auto;padding-top:30px} .invisible {color: rgba(0,0,0,0); font-size: 0;} </style> <style> /*! Stylesheet for CodeRay to loosely match GitHub themes | MIT License */ pre.CodeRay{background:#f7f7f8} .CodeRay .line-numbers{border-right:1px solid;opacity:.35;padding:0 .5em 0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} .CodeRay span.line-numbers{display:inline-block;margin-right:.75em} .CodeRay .line-numbers strong{color:#000} table.CodeRay{border-collapse:separate;border:0;margin-bottom:0;background:none} table.CodeRay td{vertical-align:top;line-height:inherit} table.CodeRay td.line-numbers{text-align:right} table.CodeRay td.code{padding:0 0 0 .75em} .CodeRay .debug{color:#fff!important;background:navy!important} .CodeRay .annotation{color:#007} .CodeRay .attribute-name{color:navy} .CodeRay .attribute-value{color:#700} .CodeRay .binary{color:#509} .CodeRay .comment{color:#998;font-style:italic} .CodeRay .char{color:#04d} .CodeRay .char .content{color:#04d} .CodeRay .char .delimiter{color:#039} .CodeRay .class{color:#458;font-weight:bold} .CodeRay .complex{color:#a08} .CodeRay .constant,.CodeRay .predefined-constant{color:teal} .CodeRay .color{color:#099} .CodeRay .class-variable{color:#369} .CodeRay .decorator{color:#b0b} .CodeRay .definition{color:#099} .CodeRay .delimiter{color:#000} .CodeRay .doc{color:#970} .CodeRay .doctype{color:#34b} .CodeRay .doc-string{color:#d42} .CodeRay .escape{color:#666} .CodeRay .entity{color:#800} .CodeRay .error{color:#808} .CodeRay .exception{color:inherit} .CodeRay .filename{color:#099} .CodeRay .function{color:#900;font-weight:bold} .CodeRay .global-variable{color:teal} .CodeRay .hex{color:#058} .CodeRay .integer,.CodeRay .float{color:#099} .CodeRay .include{color:#555} .CodeRay .inline{color:#000} .CodeRay .inline .inline{background:#ccc} .CodeRay .inline .inline .inline{background:#bbb} .CodeRay .inline .inline-delimiter{color:#d14} .CodeRay .inline-delimiter{color:#d14} .CodeRay .important{color:#555;font-weight:bold} .CodeRay .interpreted{color:#b2b} .CodeRay .instance-variable{color:teal} .CodeRay .label{color:#970} .CodeRay .local-variable{color:#963} .CodeRay .octal{color:#40e} .CodeRay .predefined{color:#369} .CodeRay .preprocessor{color:#579} .CodeRay .pseudo-class{color:#555} .CodeRay .directive{font-weight:bold} .CodeRay .type{font-weight:bold} .CodeRay .predefined-type{color:inherit} .CodeRay .reserved,.CodeRay .keyword{color:#000;font-weight:bold} .CodeRay .key{color:#808} .CodeRay .key .delimiter{color:#606} .CodeRay .key .char{color:#80f} .CodeRay .value{color:#088} .CodeRay .regexp .delimiter{color:#808} .CodeRay .regexp .content{color:#808} .CodeRay .regexp .modifier{color:#808} .CodeRay .regexp .char{color:#d14} .CodeRay .regexp .function{color:#404;font-weight:bold} .CodeRay .string{color:#d20} .CodeRay .string .string .string{background:#ffd0d0} .CodeRay .string .content{color:#d14} .CodeRay .string .char{color:#d14} .CodeRay .string .delimiter{color:#d14} .CodeRay .shell{color:#d14} .CodeRay .shell .delimiter{color:#d14} .CodeRay .symbol{color:#990073} .CodeRay .symbol .content{color:#a60} .CodeRay .symbol .delimiter{color:#630} .CodeRay .tag{color:teal} .CodeRay .tag-special{color:#d70} .CodeRay .variable{color:#036} .CodeRay .insert{background:#afa} .CodeRay .delete{background:#faa} .CodeRay .change{color:#aaf;background:#007} .CodeRay .head{color:#f8f;background:#505} .CodeRay .insert .insert{color:#080} .CodeRay .delete .delete{color:#800} .CodeRay .change .change{color:#66f} .CodeRay .head .head{color:#f4f} </style> <!-- --> <!-- Matomo --> <script> var _paq = window._paq = window._paq || []; /* We explicitly disable cookie tracking to avoid privacy issues */ _paq.push(['disableCookies']); /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { var u="https://analytics.apache.org/"; _paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setSiteId', '27']); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); </script> <!-- End Matomo Code --> </head> <body class="book toc2 toc-left"> <div id="header"> <div id="toc" class="toc2"> <div id="toctitle">Table of Contents</div> <ul class="sectlevel0"> <li><a href="#_introduction">Introduction</a></li> <li><a href="#providers">Provider Documentation</a> <ul class="sectlevel1"> <li><a href="#graph-system-provider-requirements">Graph System Provider Requirements</a> <ul class="sectlevel2"> <li><a href="#graph-structure-api">Graph Structure API</a></li> <li><a href="#_implementing_gremlin_core">Implementing Gremlin-Core</a> <ul class="sectlevel3"> <li><a href="#oltp-implementations">OLTP Implementations</a></li> <li><a href="#olap-implementations">OLAP Implementations</a></li> <li><a href="#_hadoop_gremlin_usage">Hadoop-Gremlin Usage</a></li> <li><a href="#io-implementations">IO Implementations</a></li> <li><a href="#remoteconnection-implementations">RemoteConnection Implementations</a></li> <li><a href="#bulk-import-export">Bulk Import Export</a></li> </ul> </li> <li><a href="#validating-with-gremlin-test">Validating with Gremlin-Test</a> <ul class="sectlevel3"> <li><a href="#_jvm_test_suite">JVM Test Suite</a></li> <li><a href="#gherkin-tests-suite">Gherkin Test Suite</a></li> </ul> </li> <li><a href="#_accessibility_via_gremlinplugin">Accessibility via GremlinPlugin</a></li> <li><a href="#_in_depth_implementations">In-Depth Implementations</a></li> </ul> </li> <li><a href="#_graph_driver_provider_requirements">Graph Driver Provider Requirements</a> <ul class="sectlevel2"> <li><a href="#_opprocessors_arguments">OpProcessors Arguments</a> <ul class="sectlevel3"> <li><a href="#_common">Common</a></li> <li><a href="#_standard_opprocessor">Standard OpProcessor</a></li> <li><a href="#_session_opprocessor">Session OpProcessor</a></li> <li><a href="#_traversal_opprocessor">Traversal OpProcessor</a></li> </ul> </li> <li><a href="#_authentication_and_authorization">Authentication and Authorization</a></li> </ul> </li> <li><a href="#gremlin-plugins">Gremlin Plugins</a></li> </ul> </li> <li><a href="#gremlin-semantics">Gremlin Semantics</a> <ul class="sectlevel1"> <li><a href="#_types">Types</a> <ul class="sectlevel2"> <li><a href="#_numeric_type_promotion">Numeric Type Promotion</a></li> </ul> </li> <li><a href="#gremlin-semantics-concepts">Comparability, Equality, Orderability, and Equivalence</a> <ul class="sectlevel2"> <li><a href="#_ternary_boolean_logics">Ternary Boolean Logics</a> <ul class="sectlevel3"> <li><a href="#_ternary_boolean_semantics_for_and">Ternary Boolean Semantics for <code>AND</code></a></li> <li><a href="#_ternary_boolean_semantics_for_or">Ternary Boolean Semantics for <code>OR</code></a></li> <li><a href="#_ternary_boolean_semantics_for_not">Ternary Boolean Semantics for <code>NOT</code></a></li> </ul> </li> <li><a href="#gremlin-semantics-equality-comparability">Equality and Comparability</a> <ul class="sectlevel3"> <li><a href="#_equality_and_comparability_semantics_by_type">Equality and Comparability Semantics by Type</a></li> </ul> </li> <li><a href="#gremlin-semantics-orderability">Orderability</a> <ul class="sectlevel3"> <li><a href="#_key_differences_from_comparability">Key differences from Comparability</a></li> </ul> </li> <li><a href="#gremlin-semantics-equivalence">Equivalence</a></li> <li><a href="#_further_reference">Further Reference</a> <ul class="sectlevel3"> <li><a href="#_mapping_for_p">Mapping for P</a></li> <li><a href="#_see_also">See Also</a></li> </ul> </li> </ul> </li> <li><a href="#_steps">Steps</a> <ul class="sectlevel2"> <li><a href="#all-step">all()</a></li> <li><a href="#any-step">any()</a></li> <li><a href="#asDate-step">asDate()</a></li> <li><a href="#asString-step">asString()</a></li> <li><a href="#call-step">call()</a></li> <li><a href="#combine-step">combine()</a></li> <li><a href="#concat-step">concat()</a></li> <li><a href="#dateAdd-step">dateAdd()</a></li> <li><a href="#dateDiff-step">dateDiff()</a></li> <li><a href="#dedup-step">dedup()</a></li> <li><a href="#difference-step">difference()</a></li> <li><a href="#disjunct-step">disjunct()</a></li> <li><a href="#element-step">element()</a></li> <li><a href="#format-step">format()</a></li> <li><a href="#length-step">length()</a></li> <li><a href="#intersect-step">intersect()</a></li> <li><a href="#conjoin-step">conjoin()</a></li> <li><a href="#lTrim-step">lTrim()</a></li> <li><a href="#merge-step">merge()</a></li> <li><a href="#merge-e-step">mergeE()</a></li> <li><a href="#merge-v-step">mergeV()</a></li> <li><a href="#product-step">product()</a></li> <li><a href="#replace-step">replace()</a></li> <li><a href="#reverse-step">reverse()</a></li> <li><a href="#rTrim-step">rTrim()</a></li> <li><a href="#split-step">split()</a></li> <li><a href="#substring-step">substring()</a></li> <li><a href="#toLower-step">toLower()</a></li> <li><a href="#toUpper-step">toUpper()</a></li> <li><a href="#trim-step">trim()</a></li> </ul> </li> </ul> </li> <li><a href="#policies">Policies</a> <ul class="sectlevel1"> <li><a href="#policy-listing">Provider Listing Policy</a></li> <li><a href="#policy-graphics">Graphic Usage Policy</a> <ul class="sectlevel2"> <li><a href="#_character_graphics">Character Graphics</a></li> <li><a href="#_character_dress_up_graphics">Character Dress-Up Graphics</a></li> <li><a href="#_explanatory_diagrams">Explanatory Diagrams</a></li> <li><a href="#_character_scene_graphics">Character Scene Graphics</a></li> </ul> </li> </ul> </li> </ul> </div> </div> <div id="content"> <div id="preamble"> <div class="sectionbody"> <div class="imageblock"> <div class="content"> <a class="image" href="https://tinkerpop.apache.org"><img src="../../images/apache-tinkerpop-logo.png" alt="apache tinkerpop logo" width="500"></a> </div> </div> <div class="paragraph"> <p><strong>3.7.3</strong></p> </div> </div> </div> <h1 id="_introduction" class="sect0">Introduction</h1> <div class="openblock partintro"> <div class="content"> <div class="paragraph"> <p><span class="image"><img src="../../images/tinkerpop-cityscape.png" alt="tinkerpop cityscape"></span></p> </div> <div class="paragraph"> <p>This document discusses Apache TinkerPop™ implementation details that are most useful to developers who implement TinkerPop interfaces and the Gremlin language. This document may also be helpful to Gremlin users who simply want a deeper understanding of how TinkerPop works and what the behavioral semantics of Gremlin are. The <a href="#providers">Provider Section</a> outlines the various integration and extension points that TinkerPop has while the <a href="#gremlin-semantics">Gremlin Semantics Section</a> documents the Gremlin language itself.</p> </div> <div class="paragraph"> <p>Providers who rely on the TinkerPop execution engine generally receive the behaviors described in the Gremlin Semantics section for free, but those who develop their own engine or extend upon the certain features should refer to that section for the details required for a consistent Gremlin experience.</p> </div> </div> </div> <h1 id="providers" class="sect0">Provider Documentation</h1> <div class="openblock partintro"> <div class="content"> <div class="paragraph"> <p>TinkerPop exposes a set of interfaces, protocols, and tests that make it possible for third-parties to build libraries and systems that plug-in to the TinkerPop stack. TinkerPop refers to those third-parties as "providers" and this documentation is designed to help providers understand what is involved in developing code on these lower levels of the TinkerPop API.</p> </div> <div class="paragraph"> <p>This document attempts to address the needs of the different providers that have been identified:</p> </div> <div class="ulist"> <ul> <li> <p>Graph System Provider</p> <div class="ulist"> <ul> <li> <p>Graph Database Provider</p> </li> <li> <p>Graph Processor Provider</p> </li> </ul> </div> </li> <li> <p>Graph Driver Provider</p> </li> <li> <p>Graph Language Provider</p> </li> <li> <p>Graph Plugin Provider</p> </li> </ul> </div> </div> </div> <div class="sect1"> <h2 id="graph-system-provider-requirements">Graph System Provider Requirements</h2> <div class="sectionbody"> <div class="paragraph"> <p><span class="image left"><img src="../../images/tinkerpop-enabled.png" alt="tinkerpop enabled" width="140"></span> At the core of TinkerPop 3.x is a Java API. The implementation of this core API and its validation via the <code>gremlin-test</code> suite is all that is required of a graph system provider wishing to provide a TinkerPop-enabled graph engine. Once a graph system has a valid implementation, then all the applications provided by TinkerPop (e.g. Gremlin Console, Gremlin Server, etc.) and 3rd-party developers (e.g. Gremlin-Scala, Gremlin-JS, etc.) will integrate properly. Finally, please feel free to use the logo on the left to promote your TinkerPop implementation.</p> </div> <div class="sect2"> <h3 id="graph-structure-api">Graph Structure API</h3> <div class="paragraph"> <p>The graph structure API of TinkerPop provides the interfaces necessary to create a TinkerPop enabled system and exposes the basic components of a property graph to include <code>Graph</code>, <code>Vertex</code>, <code>Edge</code>, <code>VertexProperty</code> and <code>Property</code>. The structure API can be used directly as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java">Graph graph = TinkerGraph.open(); <span class="invisible">//</span><b class="conum">1</b> Vertex marko = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">1</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">marko</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>, <span class="integer">29</span>); <span class="invisible">//</span><b class="conum">2</b> Vertex vadas = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">2</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">vadas</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>, <span class="integer">27</span>); Vertex lop = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">software</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">3</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">lop</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">lang</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">java</span><span class="delimiter">&quot;</span></span>); Vertex josh = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">4</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">josh</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>, <span class="integer">32</span>); Vertex ripple = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">software</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">5</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">ripple</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">lang</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">java</span><span class="delimiter">&quot;</span></span>); Vertex peter = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>, T.id, <span class="integer">6</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">peter</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>, <span class="integer">35</span>); marko.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">knows</span><span class="delimiter">&quot;</span></span>, vadas, T.id, <span class="integer">7</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">0.5f</span>); <span class="invisible">//</span><b class="conum">3</b> marko.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">knows</span><span class="delimiter">&quot;</span></span>, josh, T.id, <span class="integer">8</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">1.0f</span>); marko.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>, lop, T.id, <span class="integer">9</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">0.4f</span>); josh.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>, ripple, T.id, <span class="integer">10</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">1.0f</span>); josh.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>, lop, T.id, <span class="integer">11</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">0.4f</span>); peter.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>, lop, T.id, <span class="integer">12</span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">weight</span><span class="delimiter">&quot;</span></span>, <span class="float">0.2f</span>);</code></pre> </div> </div> <div class="colist arabic"> <ol> <li> <p>Create a new in-memory <code>TinkerGraph</code> and assign it to the variable <code>graph</code>.</p> </li> <li> <p>Create a vertex along with a set of key/value pairs with <code>T.label</code> being the vertex label and <code>T.id</code> being the vertex id.</p> </li> <li> <p>Create an edge along with a set of key/value pairs with the edge label being specified as the first argument.</p> </li> </ol> </div> <div class="paragraph"> <p>In the above code all the vertices are created first and then their respective edges. There are two "accessor tokens": <code>T.id</code> and <code>T.label</code>. When any of these, along with a set of other key value pairs is provided to <code>Graph.addVertex(Object&#8230;&#8203;)</code> or <code>Vertex.addEdge(String,Vertex,Object&#8230;&#8203;)</code>, the respective element is created along with the provided key/value pair properties appended to it.</p> </div> <div class="paragraph"> <p>Below is a sequence of basic graph mutation operations represented in Java:</p> </div> <div class="paragraph"> <p><span class="image right"><img src="../../images/basic-mutation.png" alt="basic mutation" width="240"></span></p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="comment">// create a new graph</span> Graph graph = TinkerGraph.open(); <span class="comment">// add a software vertex with a name property</span> Vertex gremlin = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">software</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">gremlin</span><span class="delimiter">&quot;</span></span>); <span class="invisible">//</span><b class="conum">1</b> <span class="comment">// only one vertex should exist</span> <span class="keyword">assert</span>(IteratorUtils.count(graph.vertices()) == <span class="integer">1</span>) <span class="comment">// no edges should exist as none have been created</span> <span class="keyword">assert</span>(IteratorUtils.count(graph.edges()) == <span class="integer">0</span>) <span class="comment">// add a new property</span> gremlin.property(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>,<span class="integer">2009</span>) <span class="invisible">//</span><b class="conum">2</b> <span class="comment">// add a new software vertex to the graph</span> Vertex blueprints = graph.addVertex(T.label, <span class="string"><span class="delimiter">&quot;</span><span class="content">software</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">blueprints</span><span class="delimiter">&quot;</span></span>); <span class="invisible">//</span><b class="conum">3</b> <span class="comment">// connect gremlin to blueprints via a dependsOn-edge</span> gremlin.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">dependsOn</span><span class="delimiter">&quot;</span></span>,blueprints); <span class="invisible">//</span><b class="conum">4</b> <span class="comment">// now there are two vertices and one edge</span> <span class="keyword">assert</span>(IteratorUtils.count(graph.vertices()) == <span class="integer">2</span>) <span class="keyword">assert</span>(IteratorUtils.count(graph.edges()) == <span class="integer">1</span>) <span class="comment">// add a property to blueprints</span> blueprints.property(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>,<span class="integer">2010</span>) <span class="invisible">//</span><b class="conum">5</b> <span class="comment">// remove that property</span> blueprints.property(<span class="string"><span class="delimiter">&quot;</span><span class="content">created</span><span class="delimiter">&quot;</span></span>).remove() <span class="invisible">//</span><b class="conum">6</b> <span class="comment">// connect gremlin to blueprints via encapsulates</span> gremlin.addEdge(<span class="string"><span class="delimiter">&quot;</span><span class="content">encapsulates</span><span class="delimiter">&quot;</span></span>,blueprints) <span class="invisible">//</span><b class="conum">7</b> <span class="keyword">assert</span>(IteratorUtils.count(graph.vertices()) == <span class="integer">2</span>) <span class="keyword">assert</span>(IteratorUtils.count(graph.edges()) == <span class="integer">2</span>) <span class="comment">// removing a vertex removes all its incident edges as well</span> blueprints.remove() <span class="invisible">//</span><b class="conum">8</b> gremlin.remove() <span class="invisible">//</span><b class="conum">9</b> <span class="comment">// the graph is now empty</span> <span class="keyword">assert</span>(IteratorUtils.count(graph.vertices()) == <span class="integer">0</span>) <span class="keyword">assert</span>(IteratorUtils.count(graph.edges()) == <span class="integer">0</span>) <span class="comment">// tada!</span></code></pre> </div> </div> <div class="paragraph"> <p>The above code samples are just examples of how the structure API can be used to access a graph. Those APIs are then used internally by the process API (i.e. Gremlin) to access any graph that implements those structure API interfaces to execute queries. Typically, the structure API methods are not used directly by end-users.</p> </div> </div> <div class="sect2"> <h3 id="_implementing_gremlin_core">Implementing Gremlin-Core</h3> <div class="paragraph"> <p>The classes that a graph system provider should focus on implementing are itemized below. It is a good idea to study the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#tinkergraph-gremlin">TinkerGraph</a> (in-memory OLTP and OLAP in <code>tinkergraph-gremlin</code>), <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#neo4j-gremlin">Neo4jGraph</a> (OLTP w/ transactions in <code>neo4j-gremlin</code>) and/or <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#hadoop-gremlin">HadoopGraph</a> (OLAP in <code>hadoop-gremlin</code>) implementations for ideas and patterns.</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>Online Transactional Processing Graph Systems (<strong>OLTP</strong>)</p> <div class="olist loweralpha"> <ol class="loweralpha" type="a"> <li> <p>Structure API: <code>Graph</code>, <code>Element</code>, <code>Vertex</code>, <code>Edge</code>, <code>Property</code> and <code>Transaction</code> (if transactions are supported).</p> </li> <li> <p>Process API: <code>TraversalStrategy</code> instances for optimizing Gremlin traversals to the provider&#8217;s graph system (i.e. <code>TinkerGraphStepStrategy</code>).</p> </li> </ol> </div> </li> <li> <p>Online Analytics Processing Graph Systems (<strong>OLAP</strong>)</p> <div class="olist loweralpha"> <ol class="loweralpha" type="a"> <li> <p>Everything required of OLTP is required of OLAP (but not vice versa).</p> </li> <li> <p>GraphComputer API: <code>GraphComputer</code>, <code>Messenger</code>, <code>Memory</code>.</p> </li> </ol> </div> </li> </ol> </div> <div class="paragraph"> <p>Please consider the following implementation notes:</p> </div> <div class="ulist"> <ul> <li> <p>Use <code>StringHelper</code> to ensuring that the <code>toString()</code> representation of classes are consistent with other implementations.</p> </li> <li> <p>Ensure that your implementation&#8217;s <code>Features</code> (Graph, Vertex, etc.) are correct so that test cases handle particulars accordingly.</p> </li> <li> <p>Use the numerous static method helper classes such as <code>ElementHelper</code>, <code>GraphComputerHelper</code>, <code>VertexProgramHelper</code>, etc.</p> </li> <li> <p>There are a number of default methods on the provided interfaces that are semantically correct. However, if they are not efficient for the implementation, override them.</p> </li> <li> <p>Implement the <code>structure/</code> package interfaces first and then, if desired, interfaces in the <code>process/</code> package interfaces.</p> </li> <li> <p><code>ComputerGraph</code> is a <code>Wrapper</code> system that ensure proper semantics during a GraphComputer computation.</p> </li> <li> <p>The <a href="https://tinkerpop.apache.org/javadocs/3.7.3/core/">javadoc</a> is often a good resource in understanding expectations from both the user&#8217;s perspective as well as the graph provider&#8217;s perspective. Also consider examining the javadoc of TinkerGraph which is often well annotated and the interfaces and classes of the test suite itself.</p> </li> </ul> </div> <div class="sect3"> <h4 id="oltp-implementations">OLTP Implementations</h4> <div class="paragraph"> <p><span class="image right"><img src="../../images/pipes-character-1.png" alt="pipes character 1" width="110"></span> The most important interfaces to implement are in the <code>structure/</code> package. These include interfaces like <code>Graph</code>, <code>Vertex</code>, <code>Edge</code>, <code>Property</code>, <code>Transaction</code>, etc. The <code>StructureStandardSuite</code> will ensure that the semantics of the methods implemented are correct. Moreover, there are numerous <code>Exceptions</code> classes with static exceptions that should be thrown by the graph system so that all the exceptions and their messages are consistent amongst all TinkerPop implementations.</p> </div> <div class="paragraph"> <p>The following bullets provide some tips to consider when implementing the structure interfaces:</p> </div> <div class="ulist"> <ul> <li> <p><code>Graph</code></p> <div class="ulist"> <ul> <li> <p>Be sure the <code>Graph</code> implementation is named as <code>XXXGraph</code> (e.g. TinkerGraph, Neo4jGraph, HadoopGraph, etc.).</p> </li> <li> <p>This implementation needs to be <code>GraphFactory</code> compatible which means that the implementation should have a static <code>Graph open(Configuration)</code> method where the <code>Configuration</code> is an Apache Commons class of that name. Alternatively, the <code>Graph</code> implementation can have the <code>GraphFactoryClass</code> annotation which specifies a class with that static <code>Graph open(Configuration)</code> method.</p> </li> </ul> </div> </li> <li> <p><code>VertexProperty</code></p> <div class="ulist"> <ul> <li> <p>This interface is both a <code>Property</code> and an <code>Element</code> as <code>VertexProperty</code> is a first-class graph element in that it can have its own properties (i.e. meta-properties). Even if the implementation does not intend to support meta-properties, the <code>VertexProperty</code> needs to be implemented as an <code>Element</code>. <code>VertexProperty</code> should return empty iterable for properties if meta-properties is not supported.</p> </li> </ul> </div> </li> </ul> </div> </div> <div class="sect3"> <h4 id="olap-implementations">OLAP Implementations</h4> <div class="paragraph"> <p><span class="image right"><img src="../../images/furnace-character-1.png" alt="furnace character 1" width="110"></span> Implementing the OLAP interfaces may be a bit more complicated. Note that before OLAP interfaces are implemented, it is necessary for the OLTP interfaces to be, at minimal, implemented as specified in <a href="#oltp-implementations">OLTP Implementations</a>. A summary of each required interface implementation is presented below:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p><code>GraphComputer</code>: A fluent builder for specifying an isolation level, a VertexProgram, and any number of MapReduce jobs to be submitted.</p> </li> <li> <p><code>Memory</code>: A global blackboard for ANDing, ORing, INCRing, and SETing values for specified keys.</p> </li> <li> <p><code>Messenger</code>: The system that collects and distributes messages being propagated by vertices executing the VertexProgram application.</p> </li> <li> <p><code>MapReduce.MapEmitter</code>: The system that collects key/value pairs being emitted by the MapReduce applications map-phase.</p> </li> <li> <p><code>MapReduce.ReduceEmitter</code>: The system that collects key/value pairs being emitted by the MapReduce applications combine- and reduce-phases.</p> </li> </ol> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> The VertexProgram and MapReduce interfaces in the <code>process/computer/</code> package are not required by the graph system. Instead, these are interfaces to be implemented by application developers writing VertexPrograms and MapReduce jobs. </td> </tr> </table> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <div class="title">Important</div> </td> <td class="content"> TinkerPop provides two OLAP implementations: <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#tinkergraph-gremlin">TinkerGraphComputer</a> (TinkerGraph), and <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#sparkgraphcomputer">SparkGraphComputer</a> (Hadoop). Given the complexity of the OLAP system, it is good to study and copy many of the patterns used in these reference implementations. </td> </tr> </table> </div> <div class="sect4"> <h5 id="_implementing_graphcomputer">Implementing GraphComputer</h5> <div class="paragraph"> <p><span class="image right"><img src="../../images/furnace-character-3.png" alt="furnace character 3" width="150"></span> The most complex method in GraphComputer is the <code>submit()</code>-method. The method must do the following:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>Ensure the GraphComputer has not already been executed.</p> </li> <li> <p>Ensure that at least there is a VertexProgram or 1 MapReduce job.</p> </li> <li> <p>If there is a VertexProgram, validate that it can execute on the GraphComputer given the respectively defined features.</p> </li> <li> <p>Create the Memory to be used for the computation.</p> </li> <li> <p>Execute the VertexProgram.setup() method once and only once.</p> </li> <li> <p>Execute the VertexProgram.execute() method for each vertex.</p> </li> <li> <p>Execute the VertexProgram.terminate() method once and if true, repeat VertexProgram.execute().</p> </li> <li> <p>When VertexProgram.terminate() returns true, move to MapReduce job execution.</p> </li> <li> <p>MapReduce jobs are not required to be executed in any specified order.</p> </li> <li> <p>For each Vertex, execute MapReduce.map(). Then (if defined) execute MapReduce.combine() and MapReduce.reduce().</p> </li> <li> <p>Update Memory with runtime information.</p> </li> <li> <p>Construct a new <code>ComputerResult</code> containing the compute Graph and Memory.</p> </li> </ol> </div> </div> <div class="sect4"> <h5 id="_implementing_memory">Implementing Memory</h5> <div class="paragraph"> <p><span class="image left"><img src="../../images/gremlin-brain.png" alt="gremlin brain" width="175"></span> The Memory object is initially defined by <code>VertexProgram.setup()</code>. The memory data is available in the first round of the <code>VertexProgram.execute()</code> method. Each Vertex, when executing the VertexProgram, can update the Memory in its round. However, the update is not seen by the other vertices until the next round. At the end of the first round, all the updates are aggregated and the new memory data is available on the second round. This process repeats until the VertexProgram terminates.</p> </div> </div> <div class="sect4"> <h5 id="_implementing_messenger">Implementing Messenger</h5> <div class="paragraph"> <p>The Messenger object is similar to the Memory object in that a vertex can read and write to the Messenger. However, the data it reads are the messages sent to the vertex in the previous step and the data it writes are the messages that will be readable by the receiving vertices in the subsequent round.</p> </div> </div> <div class="sect4"> <h5 id="_implementing_mapreduce_emitters">Implementing MapReduce Emitters</h5> <div class="paragraph"> <p><span class="image left"><img src="../../images/hadoop-logo-notext.png" alt="hadoop logo notext" width="150"></span> The MapReduce framework in TinkerPop is similar to the model popularized by <a href="http://hadoop.apache.org">Hadoop</a>. The primary difference is that all Mappers process the vertices of the graph, not an arbitrary key/value pair. However, the vertices' edges can not be accessed&#8201;&#8212;&#8201;only their properties. This greatly reduces the amount of data needed to be pushed through the MapReduce engine as any edge information required, can be computed in the VertexProgram.execute() method. Moreover, at this stage, vertices can not be mutated, only their token and property data read. A Gremlin OLAP system needs to provide implementations for to particular classes: <code>MapReduce.MapEmitter</code> and <code>MapReduce.ReduceEmitter</code>. TinkerGraph&#8217;s implementation is provided below which demonstrates the simplicity of the algorithm (especially when the data is all within the same JVM).</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">class</span> <span class="class">TinkerMapEmitter</span>&lt;K, V&gt; <span class="directive">implements</span> MapReduce.MapEmitter&lt;K, V&gt; { <span class="directive">public</span> <span class="predefined-type">Map</span>&lt;K, <span class="predefined-type">Queue</span>&lt;V&gt;&gt; reduceMap; <span class="directive">public</span> <span class="predefined-type">Queue</span>&lt;KeyValue&lt;K, V&gt;&gt; mapQueue; <span class="directive">private</span> <span class="directive">final</span> <span class="type">boolean</span> doReduce; <span class="directive">public</span> TinkerMapEmitter(<span class="directive">final</span> <span class="type">boolean</span> doReduce) { <span class="invisible">//</span><b class="conum">1</b> <span class="local-variable">this</span>.doReduce = doReduce; <span class="keyword">if</span> (<span class="local-variable">this</span>.doReduce) <span class="local-variable">this</span>.reduceMap = <span class="keyword">new</span> <span class="predefined-type">ConcurrentHashMap</span>&lt;&gt;(); <span class="keyword">else</span> <span class="local-variable">this</span>.mapQueue = <span class="keyword">new</span> <span class="predefined-type">ConcurrentLinkedQueue</span>&lt;&gt;(); } <span class="annotation">@Override</span> <span class="directive">public</span> <span class="type">void</span> emit(K key, V value) { <span class="keyword">if</span> (<span class="local-variable">this</span>.doReduce) <span class="local-variable">this</span>.reduceMap.computeIfAbsent(key, k -&gt; <span class="keyword">new</span> <span class="predefined-type">ConcurrentLinkedQueue</span>&lt;&gt;()).add(value); <span class="invisible">//</span><b class="conum">2</b> <span class="keyword">else</span> <span class="local-variable">this</span>.mapQueue.add(<span class="keyword">new</span> KeyValue&lt;&gt;(key, value)); <span class="invisible">//</span><b class="conum">3</b> } <span class="directive">protected</span> <span class="type">void</span> complete(<span class="directive">final</span> MapReduce&lt;K, V, ?, ?, ?&gt; mapReduce) { <span class="keyword">if</span> (!<span class="local-variable">this</span>.doReduce &amp;&amp; mapReduce.getMapKeySort().isPresent()) { <span class="invisible">//</span><b class="conum">4</b> <span class="directive">final</span> <span class="predefined-type">Comparator</span>&lt;K&gt; comparator = mapReduce.getMapKeySort().get(); <span class="directive">final</span> <span class="predefined-type">List</span>&lt;KeyValue&lt;K, V&gt;&gt; list = <span class="keyword">new</span> <span class="predefined-type">ArrayList</span>&lt;&gt;(<span class="local-variable">this</span>.mapQueue); <span class="predefined-type">Collections</span>.sort(list, <span class="predefined-type">Comparator</span>.comparing(KeyValue::getKey, comparator)); <span class="local-variable">this</span>.mapQueue.clear(); <span class="local-variable">this</span>.mapQueue.addAll(list); } <span class="keyword">else</span> <span class="keyword">if</span> (mapReduce.getMapKeySort().isPresent()) { <span class="directive">final</span> <span class="predefined-type">Comparator</span>&lt;K&gt; comparator = mapReduce.getMapKeySort().get(); <span class="directive">final</span> <span class="predefined-type">List</span>&lt;<span class="predefined-type">Map</span>.Entry&lt;K, <span class="predefined-type">Queue</span>&lt;V&gt;&gt;&gt; list = <span class="keyword">new</span> <span class="predefined-type">ArrayList</span>&lt;&gt;(); list.addAll(<span class="local-variable">this</span>.reduceMap.entrySet()); <span class="predefined-type">Collections</span>.sort(list, <span class="predefined-type">Comparator</span>.comparing(<span class="predefined-type">Map</span>.Entry::getKey, comparator)); <span class="local-variable">this</span>.reduceMap = <span class="keyword">new</span> <span class="predefined-type">LinkedHashMap</span>&lt;&gt;(); list.forEach(entry -&gt; <span class="local-variable">this</span>.reduceMap.put(entry.getKey(), entry.getValue())); } } }</code></pre> </div> </div> <div class="colist arabic"> <ol> <li> <p>If the MapReduce job has a reduce, then use one data structure (<code>reduceMap</code>), else use another (<code>mapList</code>). The difference being that a reduction requires a grouping by key and therefore, the <code>Map&lt;K,Queue&lt;V&gt;&gt;</code> definition. If no reduction/grouping is required, then a simple <code>Queue&lt;KeyValue&lt;K,V&gt;&gt;</code> can be leveraged.</p> </li> <li> <p>If reduce is to follow, then increment the Map with a new value for the key. <code>MapHelper</code> is a TinkerPop class with static methods for adding data to a Map.</p> </li> <li> <p>If no reduce is to follow, then simply append a KeyValue to the queue.</p> </li> <li> <p>When the map phase is complete, any map-result sorting required can be executed at this point.</p> </li> </ol> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">class</span> <span class="class">TinkerReduceEmitter</span>&lt;OK, OV&gt; <span class="directive">implements</span> MapReduce.ReduceEmitter&lt;OK, OV&gt; { <span class="directive">protected</span> <span class="predefined-type">Queue</span>&lt;KeyValue&lt;OK, OV&gt;&gt; reduceQueue = <span class="keyword">new</span> <span class="predefined-type">ConcurrentLinkedQueue</span>&lt;&gt;(); <span class="annotation">@Override</span> <span class="directive">public</span> <span class="type">void</span> emit(<span class="directive">final</span> OK key, <span class="directive">final</span> OV value) { <span class="local-variable">this</span>.reduceQueue.add(<span class="keyword">new</span> KeyValue&lt;&gt;(key, value)); } <span class="directive">protected</span> <span class="type">void</span> complete(<span class="directive">final</span> MapReduce&lt;?, ?, OK, OV, ?&gt; mapReduce) { <span class="keyword">if</span> (mapReduce.getReduceKeySort().isPresent()) { <span class="directive">final</span> <span class="predefined-type">Comparator</span>&lt;OK&gt; comparator = mapReduce.getReduceKeySort().get(); <span class="directive">final</span> <span class="predefined-type">List</span>&lt;KeyValue&lt;OK, OV&gt;&gt; list = <span class="keyword">new</span> <span class="predefined-type">ArrayList</span>&lt;&gt;(<span class="local-variable">this</span>.reduceQueue); <span class="predefined-type">Collections</span>.sort(list, <span class="predefined-type">Comparator</span>.comparing(KeyValue::getKey, comparator)); <span class="local-variable">this</span>.reduceQueue.clear(); <span class="local-variable">this</span>.reduceQueue.addAll(list); } } }</code></pre> </div> </div> <div class="paragraph"> <p>The method <code>MapReduce.reduce()</code> is defined as:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">void</span> reduce(<span class="directive">final</span> OK key, <span class="directive">final</span> <span class="predefined-type">Iterator</span>&lt;OV&gt; values, <span class="directive">final</span> ReduceEmitter&lt;OK, OV&gt; emitter) { ... }</code></pre> </div> </div> <div class="paragraph"> <p>In other words, for the TinkerGraph implementation, iterate through the entrySet of the <code>reduceMap</code> and call the <code>reduce()</code> method on each entry. The <code>reduce()</code> method can emit key/value pairs which are simply aggregated into a <code>Queue&lt;KeyValue&lt;OK,OV&gt;&gt;</code> in an analogous fashion to <code>TinkerMapEmitter</code> when no reduce is to follow. These two emitters are tied together in <code>TinkerGraphComputer.submit()</code>.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java">... for (<span class="directive">final</span> MapReduce mapReduce : mapReducers) { <span class="keyword">if</span> (mapReduce.doStage(MapReduce.Stage.MAP)) { <span class="directive">final</span> TinkerMapEmitter&lt;?, ?&gt; mapEmitter = <span class="keyword">new</span> TinkerMapEmitter&lt;&gt;(mapReduce.doStage(MapReduce.Stage.REDUCE)); <span class="directive">final</span> SynchronizedIterator&lt;Vertex&gt; vertices = <span class="keyword">new</span> SynchronizedIterator&lt;&gt;(<span class="local-variable">this</span>.graph.vertices()); workers.setMapReduce(mapReduce); workers.mapReduceWorkerStart(MapReduce.Stage.MAP); workers.executeMapReduce(workerMapReduce -&gt; { <span class="keyword">while</span> (<span class="predefined-constant">true</span>) { <span class="directive">final</span> Vertex vertex = vertices.next(); <span class="keyword">if</span> (<span class="predefined-constant">null</span> == vertex) <span class="keyword">return</span>; workerMapReduce.map(ComputerGraph.mapReduce(vertex), mapEmitter); } }); workers.mapReduceWorkerEnd(MapReduce.Stage.MAP); <span class="comment">// sort results if a map output sort is defined</span> mapEmitter.complete(mapReduce); <span class="comment">// no need to run combiners as this is single machine</span> <span class="keyword">if</span> (mapReduce.doStage(MapReduce.Stage.REDUCE)) { <span class="directive">final</span> TinkerReduceEmitter&lt;?, ?&gt; reduceEmitter = <span class="keyword">new</span> TinkerReduceEmitter&lt;&gt;(); <span class="directive">final</span> SynchronizedIterator&lt;<span class="predefined-type">Map</span>.Entry&lt;?, <span class="predefined-type">Queue</span>&lt;?&gt;&gt;&gt; keyValues = <span class="keyword">new</span> SynchronizedIterator((<span class="predefined-type">Iterator</span>) mapEmitter.reduceMap.entrySet().iterator()); workers.mapReduceWorkerStart(MapReduce.Stage.REDUCE); workers.executeMapReduce(workerMapReduce -&gt; { <span class="keyword">while</span> (<span class="predefined-constant">true</span>) { <span class="directive">final</span> <span class="predefined-type">Map</span>.Entry&lt;?, <span class="predefined-type">Queue</span>&lt;?&gt;&gt; entry = keyValues.next(); <span class="keyword">if</span> (<span class="predefined-constant">null</span> == entry) <span class="keyword">return</span>; workerMapReduce.reduce(entry.getKey(), entry.getValue().iterator(), reduceEmitter); } }); workers.mapReduceWorkerEnd(MapReduce.Stage.REDUCE); reduceEmitter.complete(mapReduce); <span class="comment">// sort results if a reduce output sort is defined</span> mapReduce.addResultToMemory(<span class="local-variable">this</span>.memory, reduceEmitter.reduceQueue.iterator()); <span class="invisible">//</span><b class="conum">1</b> } <span class="keyword">else</span> { mapReduce.addResultToMemory(<span class="local-variable">this</span>.memory, mapEmitter.mapQueue.iterator()); <span class="invisible">//</span><b class="conum">2</b> } } } ...</code></pre> </div> </div> <div class="colist arabic"> <ol> <li> <p>Note that the final results of the reducer are provided to the Memory as specified by the application developer&#8217;s <code>MapReduce.addResultToMemory()</code> implementation.</p> </li> <li> <p>If there is no reduce stage, the map-stage results are inserted into Memory as specified by the application developer&#8217;s <code>MapReduce.addResultToMemory()</code> implementation.</p> </li> </ol> </div> </div> </div> <div class="sect3"> <h4 id="_hadoop_gremlin_usage">Hadoop-Gremlin Usage</h4> <div class="paragraph"> <p>Hadoop-Gremlin is centered around <code>InputFormats</code> and <code>OutputFormats</code>. If a 3rd-party graph system provider wishes to leverage Hadoop-Gremlin (and its respective <code>GraphComputer</code> engines), then they need to provide, at minimum, a Hadoop2 <code>InputFormat&lt;NullWritable,VertexWritable&gt;</code> for their graph system. If the provider wishes to persist computed results back to their graph system (and not just to HDFS via a <code>FileOutputFormat</code>), then a graph system specific <code>OutputFormat&lt;NullWritable,VertexWritable&gt;</code> must be developed as well.</p> </div> <div class="paragraph"> <p>Conceptually, <code>HadoopGraph</code> is a wrapper around a <code>Configuration</code> object. There is no "data" in the <code>HadoopGraph</code> as the <code>InputFormat</code> specifies where and how to get the graph data at OLAP (and OLTP) runtime. Thus, <code>HadoopGraph</code> is a small object with little overhead. Graph system providers should realize <code>HadoopGraph</code> as the gateway to the OLAP features offered by Hadoop-Gremlin. For example, a graph system specific <code>Graph.compute(Class&lt;? extends GraphComputer&gt; graphComputerClass)</code>-method may look as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> &lt;C <span class="directive">extends</span> GraphComputer&gt; C compute(<span class="directive">final</span> <span class="predefined-type">Class</span>&lt;C&gt; graphComputerClass) <span class="directive">throws</span> <span class="exception">IllegalArgumentException</span> { <span class="keyword">try</span> { <span class="keyword">if</span> (AbstractHadoopGraphComputer.class.isAssignableFrom(graphComputerClass)) <span class="keyword">return</span> graphComputerClass.getConstructor(HadoopGraph.class).newInstance(<span class="local-variable">this</span>); <span class="keyword">else</span> <span class="keyword">throw</span> Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(graphComputerClass); } <span class="keyword">catch</span> (<span class="directive">final</span> <span class="exception">Exception</span> e) { <span class="keyword">throw</span> <span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>(e.getMessage(),e); } }</code></pre> </div> </div> <div class="paragraph"> <p>Note that the configurations for Hadoop are assumed to be in the <code>Graph.configuration()</code> object. If this is not the case, then the <code>Configuration</code> provided to <code>HadoopGraph.open()</code> should be dynamically created within the <code>compute()</code>-method. It is in the provided configuration that <code>HadoopGraph</code> gets the various properties which determine how to read and write data to and from Hadoop. For instance, <code>gremlin.hadoop.graphReader</code> and <code>gremlin.hadoop.graphWriter</code>.</p> </div> <div class="sect4"> <h5 id="_graphfilteraware_interface">GraphFilterAware Interface</h5> <div class="paragraph"> <p><a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#graph-filter">Graph filters</a> by OLAP processors to only pull a subgraph of the full graph from the graph data source. For instance, the example below constructs a <code>GraphFilter</code> that will only pull the "knows"-graph amongst people into the <code>GraphComputer</code> for processing.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java">graph.compute().vertices(hasLabel(<span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>)).edges(bothE(<span class="string"><span class="delimiter">&quot;</span><span class="content">knows</span><span class="delimiter">&quot;</span></span>))</code></pre> </div> </div> <div class="paragraph"> <p>If the provider has a custom <code>InputRDD</code>, they can implement <code>GraphFilterAware</code> and that graph filter will be provided to their <code>InputRDD</code> at load time. For providers that use an <code>InputFormat</code>, state but the graph filter can be accessed from the configuration as such:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">if</span> (configuration.containsKey(Constants.GREMLIN_HADOOP_GRAPH_FILTER)) <span class="local-variable">this</span>.graphFilter = VertexProgramHelper.deserialize(configuration, Constants.GREMLIN_HADOOP_GRAPH_FILTER);</code></pre> </div> </div> </div> <div class="sect4"> <h5 id="_persistresultgraphaware_interface">PersistResultGraphAware Interface</h5> <div class="paragraph"> <p>A graph system provider&#8217;s <code>OutputFormat</code> should implement the <code>PersistResultGraphAware</code> interface which determines which persistence options are available to the user. For the standard file-based <code>OutputFormats</code> provided by Hadoop-Gremlin (e.g. <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#gryo-io-format"><code>GryoOutputFormat</code></a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#graphson-io-format"><code>GraphSONOutputFormat</code></a>, and <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#script-io-format"><code>ScriptInputOutputFormat</code></a>) <code>ResultGraph.ORIGINAL</code> is not supported as the original graph data files are not random access and are, in essence, immutable. Thus, these file-based <code>OutputFormats</code> only support <code>ResultGraph.NEW</code> which creates a copy of the data specified by the <code>Persist</code> enum.</p> </div> </div> </div> <div class="sect3"> <h4 id="io-implementations">IO Implementations</h4> <div class="paragraph"> <p>If a <code>Graph</code> requires custom serializers for IO to work properly, implement the <code>Graph.io</code> method. A typical example of where a <code>Graph</code> would require such a custom serializers is if their identifier system uses non-primitive values, such as OrientDB&#8217;s <code>Rid</code> class. From basic serialization of a single <code>Vertex</code> all the way up the stack to Gremlin Server, the need to know how to handle these complex identifiers is an important requirement.</p> </div> <div class="paragraph"> <p>The first step to implementing custom serializers is to first implement the <code>IoRegistry</code> interface and register the custom classes and serializers to it. Each <code>Io</code> implementation has different requirements for what it expects from the <code>IoRegistry</code>:</p> </div> <div class="ulist"> <ul> <li> <p><strong>GraphML</strong> - No custom serializers expected/allowed.</p> </li> <li> <p><strong>GraphSON</strong> - Register a Jackson <code>SimpleModule</code>. The <code>SimpleModule</code> encapsulates specific classes to be serialized, so it does not need to be registered to a specific class in the <code>IoRegistry</code> (use <code>null</code>).</p> </li> <li> <p><strong>Gryo</strong> - Expects registration of one of three objects:</p> <div class="ulist"> <ul> <li> <p>Register just the custom class with a <code>null</code> Kryo <code>Serializer</code> implementation - this class will use default "field-level" Kryo serialization.</p> </li> <li> <p>Register the custom class with a specific Kryo `Serializer' implementation.</p> </li> <li> <p>Register the custom class with a <code>Function&lt;Kryo, Serializer&gt;</code> for those cases where the Kryo <code>Serializer</code> requires the <code>Kryo</code> instance to get constructed.</p> </li> </ul> </div> </li> </ul> </div> <div class="paragraph"> <p>This implementation should provide a zero-arg constructor as the stack may require instantiation via reflection. Consider extending <code>AbstractIoRegistry</code> for convenience as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">class</span> <span class="class">MyGraphIoRegistry</span> <span class="directive">extends</span> AbstractIoRegistry { <span class="directive">public</span> MyGraphIoRegistry() { register(GraphSONIo.class, <span class="predefined-constant">null</span>, <span class="keyword">new</span> MyGraphSimpleModule()); register(GryoIo.class, MyGraphIdClass.class, <span class="keyword">new</span> MyGraphIdSerializer()); } }</code></pre> </div> </div> <div class="paragraph"> <p>In the <code>Graph.io</code> method, provide the <code>IoRegistry</code> object to the supplied <code>Builder</code> and call the <code>create</code> method to return that <code>Io</code> instance as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> &lt;I <span class="directive">extends</span> Io&gt; I io(<span class="directive">final</span> Io.Builder&lt;I&gt; builder) { <span class="keyword">return</span> (I) builder.graph(<span class="local-variable">this</span>).registry(myGraphIoRegistry).create(); }}</code></pre> </div> </div> <div class="paragraph"> <p>In this way, <code>Graph</code> implementations can pre-configure custom serializers for IO interactions and users will not need to know about those details. Following this pattern will ensure proper execution of the test suite as well as simplified usage for end-users.</p> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <div class="title">Important</div> </td> <td class="content"> Proper implementation of IO is critical to successful <code>Graph</code> operations in Gremlin Server. The Test Suite does have "serialization" tests that provide some assurance that an implementation is working properly, but those tests cannot make assertions against any specifics of a custom serializer. It is the responsibility of the implementer to test the specifics of their custom serializers. </td> </tr> </table> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> Consider separating serializer code into its own module, if possible, so that clients that use the <code>Graph</code> implementation remotely don&#8217;t need a full dependency on the entire <code>Graph</code> - just the IO components and related classes being serialized. </td> </tr> </table> </div> <div class="paragraph"> <p>There is an important implication to consider when the addition of a custom serializer. Presumably, the custom serializer was written for the JVM to be deployed with a <code>Graph</code> instance. For example, a graph may expose a geographical type like a <code>Point</code> or something similar. The library that contains <code>Point</code> assuming users expected to deserialize back to a <code>Point</code> would need to have the library with <code>Point</code> and the &#8220;PointSerializer&#8221; class available to them. In cases where that deployment approach is not desirable, it is possible to coerce a class like <code>Point</code> to a type that is already in the list of types supported in TinkerPop. For example, <code>Point</code> could be coerced one-way to <code>Map</code> of keys "x" and "y". Of course, on the client side, users would have to construct a <code>Map</code> for a <code>Point</code> which isn&#8217;t quite as user-friendly.</p> </div> <div class="paragraph"> <p>If doing a type coercion is not desired, then it is important to remember that writing a <code>Point</code> class and related serializer in Java is not sufficient for full support of Gremlin, as users of non-JVM Gremlin Language Variants (GLV) will not be able to consume them. Getting full support would mean writing similar classes for each GLV. While developing those classes is not hard, it also means more code to support.</p> </div> <div class="sect4"> <h5 id="_supporting_gremlin_python_io">Supporting Gremlin-Python IO</h5> <div class="paragraph"> <p>The serialization system of Gremlin-Python provides ways to add new types by creating serializers and deserializers in Python and registering them with the <code>RemoteConnection</code>.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="python"><span class="keyword">class</span> <span class="class">MyType</span>(<span class="predefined">object</span>): GRAPHSON_PREFIX = <span class="string"><span class="delimiter">&quot;</span><span class="content">providerx</span><span class="delimiter">&quot;</span></span> GRAPHSON_BASE_TYPE = <span class="string"><span class="delimiter">&quot;</span><span class="content">MyType</span><span class="delimiter">&quot;</span></span> GRAPHSON_TYPE = GraphSONUtil.formatType(GRAPHSON_PREFIX, GRAPHSON_BASE_TYPE) <span class="keyword">def</span> <span class="function">__init__</span>(<span class="predefined-constant">self</span>, x, y): <span class="predefined-constant">self</span>.x = x <span class="predefined-constant">self</span>.y = y <span class="decorator">@classmethod</span> <span class="keyword">def</span> <span class="function">objectify</span>(cls, value, reader): <span class="keyword">return</span> cls(value[<span class="string"><span class="delimiter">'</span><span class="content">x</span><span class="delimiter">'</span></span>], value[<span class="string"><span class="delimiter">'</span><span class="content">y</span><span class="delimiter">'</span></span>]) <span class="decorator">@classmethod</span> <span class="keyword">def</span> <span class="function">dictify</span>(cls, value, writer): <span class="keyword">return</span> GraphSONUtil.typedValue(cls.GRAPHSON_BASE_TYPE, {<span class="string"><span class="delimiter">'</span><span class="content">x</span><span class="delimiter">'</span></span>: value.x, <span class="string"><span class="delimiter">'</span><span class="content">y</span><span class="delimiter">'</span></span>: value.y}, cls.GRAPHSON_PREFIX) graphson_reader = GraphSONReader({MyType.GRAPHSON_TYPE: MyType}) graphson_writer = GraphSONWriter({MyType: MyType}) connection = DriverRemoteConnection(<span class="string"><span class="delimiter">'</span><span class="content">ws://localhost:8182/gremlin</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">g</span><span class="delimiter">'</span></span>, graphson_reader=graphson_reader, graphson_writer=graphson_writer)</code></pre> </div> </div> </div> <div class="sect4"> <h5 id="_supporting_gremlin_net_io">Supporting Gremlin.Net IO</h5> <div class="paragraph"> <p>The serialization system of Gremlin.Net provides ways to add new types by creating serializers and deserializers in any .NET language and registering them with the <code>GremlinClient</code>.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="csharp">internal class MyType { public static string GraphsonPrefix = &quot;providerx&quot;; public static string GraphsonBaseType = &quot;MyType&quot;; public static string GraphsonType = GraphSONUtil.FormatTypeName(GraphsonPrefix, GraphsonBaseType); public MyType(int x, int y) { X = x; Y = y; } public int X { get; } public int Y { get; } } internal class MyClassWriter : IGraphSONSerializer { public Dictionary&lt;string, dynamic&gt; Dictify(dynamic objectData, GraphSONWriter writer) { MyType myType = objectData; var valueDict = new Dictionary&lt;string, object&gt; { {&quot;x&quot;, myType.X}, {&quot;y&quot;, myType.Y} }; return GraphSONUtil.ToTypedValue(nameof(TestClass), valueDict, MyType.GraphsonPrefix); } } internal class MyTypeReader : IGraphSONDeserializer { public dynamic Objectify(JsonElement graphsonObject, GraphSONReader reader) { var x = reader.ToObject(graphsonObject.GetProperty(&quot;x&quot;)); var y = reader.ToObject(graphsonObject.GetProperty(&quot;y&quot;)); return new MyType(x, y); } } var graphsonReader = new GraphSON3Reader( new Dictionary&lt;string, IGraphSONDeserializer&gt; {{MyType.GraphsonType, new MyTypeReader()}}); var graphsonWriter = new GraphSON3Writer( new Dictionary&lt;Type, IGraphSONSerializer&gt; {{typeof(MyType), new MyClassWriter()}}); var gremlinClient = new GremlinClient(new GremlinServer(&quot;localhost&quot;, 8182), new GraphSON2MessageSerializer());</code></pre> </div> </div> </div> </div> <div class="sect3"> <h4 id="remoteconnection-implementations">RemoteConnection Implementations</h4> <div class="paragraph"> <p>A <code>RemoteConnection</code> is an interface that is important for usage on traversal sources configured using the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#connecting-via-drivers">withRemote()</a> option. A <code>Traversal</code> that is generated from that source will apply a <code>RemoteStrategy</code> which will inject a <code>RemoteStep</code> to its end. That step will then send the <code>Bytecode</code> of the <code>Traversal</code> over the <code>RemoteConnection</code> to get the results that it will iterate.</p> </div> <div class="paragraph"> <p>There is one method to implement on <code>RemoteConnection</code>:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> &lt;E&gt; CompletableFuture&lt;RemoteTraversal&lt;?, E&gt;&gt; submitAsync(<span class="directive">final</span> Bytecode bytecode) <span class="directive">throws</span> RemoteConnectionException;</code></pre> </div> </div> <div class="paragraph"> <p>Note that it returns a <code>RemoteTraversal</code>. This interface should also be implemented and in most cases implementers can simply extend the <code>AbstractRemoteTraversal</code>.</p> </div> <div class="paragraph"> <p>TinkerPop provides the <code>DriverRemoteConnection</code> as a useful and <a href="https://github.com/apache/tinkerpop/blob/3.7.3/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/remote">example implementation</a>. <code>DriverRemoteConnection</code> serializes the <code>Traversal</code> as Gremlin bytecode and then submits it for remote processing on Gremlin Server. Gremlin Server rebinds the <code>Traversal</code> to a configured <code>Graph</code> instance and then iterates the results back as it would normally do.</p> </div> <div class="paragraph"> <p>Implementing <code>RemoteConnection</code> is not something routinely done for those implementing <code>gremlin-core</code>. It is only something required if there is a need to exploit remote traversal submission. If a graph provider has a "graph server" similar to Gremlin Server that can accept bytecode-based requests on its own protocol, then that would be one example of a reason to implement this interface.</p> </div> </div> <div class="sect3"> <h4 id="bulk-import-export">Bulk Import Export</h4> <div class="paragraph"> <p>When it comes to doing "bulk" operations, the diverse nature of the available graph databases and their specific capabilities, prevents TinkerPop from doing a good job of generalizing that capability well. TinkerPop thus maintains two positions on the concept of import and export:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>TinkerPop refers users to the bulk import/export facilities of specific graph providers as they tend to be more efficient and easier to use than the options TinkerPop has tried to generalize in the past.</p> </li> <li> <p>TinkerPop encourages graph providers to expose those capabilities via <code>g.io()</code> and the <code>IoStep</code> by way of a <code>TraversalStrategy</code>.</p> </li> </ol> </div> <div class="paragraph"> <p>That said, for graph providers that don&#8217;t have a special bulk loading feature, they can either rely on the default OLTP (single-threaded) <code>GraphReader</code> and <code>GraphWriter</code> options that are embedded in <code>IoStep</code> or get a basic bulk loader from TinkerPop using the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#clonevertexprogram">CloneVertexProgram</a>. Simply provide a <code>InputFormat</code> and <code>OutputFormat</code> that can be referenced by a <code>HadoopGraph</code> instance as discussed in the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#clonevertexprogram">Reference Documentation</a>.</p> </div> </div> </div> <div class="sect2"> <h3 id="validating-with-gremlin-test">Validating with Gremlin-Test</h3> <div class="paragraph"> <p><span class="image"><img src="../../images/gremlin-edumacated.png" alt="gremlin edumacated" width="225"></span></p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;dependency&gt;</span> <span class="tag">&lt;groupId&gt;</span>org.apache.tinkerpop<span class="tag">&lt;/groupId&gt;</span> <span class="tag">&lt;artifactId&gt;</span>gremlin-test<span class="tag">&lt;/artifactId&gt;</span> <span class="tag">&lt;version&gt;</span>3.7.3<span class="tag">&lt;/version&gt;</span> <span class="tag">&lt;/dependency&gt;</span></code></pre> </div> </div> <div class="paragraph"> <p>Providers currently have two approaches to consider when validating their TinkerPop implementations. The first approach comes from the wholly JVM oriented original test suite which was developed in the early days of TinkerPop 3.x design and development. The second approach is available as of 3.6.0, is Gherkin-based and originates from the Gremlin Language Variant test suite which is language agnostic.</p> </div> <div class="paragraph"> <p>The first approach is more complete and more opinionated as to how an implementation should behave and in many ways helpful in getting an implementation semantically correct from the ground up (i.e. first getting the <code>Graph</code> Structure API implemented well by getting the Structure Suite to pass which will almost inevitably ensure that the most of the Gremlin language oriented tests in the Process Suite pass early on). On the other hand, the fact that this test suite is rigorous also can make it harder to implement especially if your graph already exists and behaves in a certain fashion.</p> </div> <div class="paragraph"> <p>The second approach only validates Gremlin semantics which is ultimately what users concern themselves with as that is the method by which they will interact with a provider&#8217;s <code>Graph</code>. This test suite is less concerned with how a TinkerPop implementation does what it does, so long as it succeeds at processing Gremlin traversals. There is significant overlap between this test suite and the aforementioned Process Suite.</p> </div> <div class="paragraph"> <p>At this time, it would be wise for providers to implement both approaches as the goal for TinkerPop is to move away from the rigors of the JVM Structure and Process Suites in favor of Gherkin. Over time, the Structure and Process Suites will be deprecated and removed.</p> </div> <div class="sect3"> <h4 id="_jvm_test_suite">JVM Test Suite</h4> <div class="paragraph"> <p>The operational semantics of any OLTP or OLAP implementation are validated by <code>gremlin-test</code>. To implement these tests, provide test case implementations as shown below, where <code>XXX</code> below denotes the name of the graph implementation (e.g. TinkerGraph, Neo4jGraph, HadoopGraph, etc.).</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="comment">// Structure API tests</span> <span class="annotation">@RunWith</span>(StructureStandardSuite.class) <span class="annotation">@GraphProviderClass</span>(provider = XXXGraphProvider.class, graph = XXXGraph.class) <span class="directive">public</span> <span class="type">class</span> <span class="class">XXXStructureStandardTest</span> {} <span class="comment">// Process API tests</span> <span class="annotation">@RunWith</span>(ProcessComputerSuite.class) <span class="annotation">@GraphProviderClass</span>(provider = XXXGraphProvider.class, graph = XXXGraph.class) <span class="directive">public</span> <span class="type">class</span> <span class="class">XXXProcessComputerTest</span> {} <span class="annotation">@RunWith</span>(ProcessStandardSuite.class) <span class="annotation">@GraphProviderClass</span>(provider = XXXGraphProvider.class, graph = XXXGraph.class) <span class="directive">public</span> <span class="type">class</span> <span class="class">XXXProcessStandardTest</span> {}</code></pre> </div> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <div class="title">Important</div> </td> <td class="content"> It is as important to look at "ignored" tests as it is to look at ones that fail. The <code>gremlin-test</code> suite utilizes the <code>Feature</code> implementation exposed by the <code>Graph</code> to determine which tests to execute. If a test utilizes features that are not supported by the graph, it will ignore them. While that may be fine, implementers should validate that the ignored tests are appropriately bypassed and that there are no mistakes in their feature definitions. Moreover, implementers should consider filling gaps in their own test suites, especially when IO-related tests are being ignored. </td> </tr> </table> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> If it is expensive to construct a new <code>Graph</code> instance, consider implementing <code>GraphProvider.getStaticFeatures()</code> which can help by caching a static feature set for instances produced by that <code>GraphProvider</code> and allow the test suite to avoid that construction cost if the test is ignored. </td> </tr> </table> </div> <div class="paragraph"> <p>The only test-class that requires any code investment is the <code>GraphProvider</code> implementation class. This class is a used by the test suite to construct <code>Graph</code> configurations and instances and provides information about the implementation itself. In most cases, it is best to simply extend <code>AbstractGraphProvider</code> as it provides many default implementations of the <code>GraphProvider</code> interface.</p> </div> <div class="paragraph"> <p>Finally, specify the test suites that will be supported by the <code>Graph</code> implementation using the <code>@Graph.OptIn</code> annotation. See the <code>TinkerGraph</code> implementation below as an example:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_STRUCTURE_STANDARD) <span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD) <span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER) <span class="directive">public</span> <span class="type">class</span> <span class="class">TinkerGraph</span> <span class="directive">implements</span> Graph {</code></pre> </div> </div> <div class="paragraph"> <p>Only include annotations for the suites the implementation will support. Note that implementing the suite, but not specifying the appropriate annotation will prevent the suite from running (an obvious error message will appear in this case when running the mis-configured suite).</p> </div> <div class="paragraph"> <p>There are times when there may be a specific test in the suite that the implementation cannot support (despite the features it implements) or should not otherwise be executed. It is possible for implementers to "opt-out" of a test by using the <code>@Graph.OptOut</code> annotation. This annotation can be applied to either a <code>Graph</code> instance or a <code>GraphProvider</code> instance (the latter would typically be used for "opting out" for a particular <code>Graph</code> configuration that was under test). The following is an example of this annotation usage as taken from <code>HadoopGraph</code>:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD) <span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER) <span class="annotation">@Graph</span>.OptOut( test = <span class="string"><span class="delimiter">&quot;</span><span class="content">org.apache.tinkerpop.gremlin.process.graph.step.map.MatchTest$Traversals</span><span class="delimiter">&quot;</span></span>, method = <span class="string"><span class="delimiter">&quot;</span><span class="content">g_V_matchXa_hasXname_GarciaX__a_inXwrittenByX_b__a_inXsungByX_bX</span><span class="delimiter">&quot;</span></span>, reason = <span class="string"><span class="delimiter">&quot;</span><span class="content">Hadoop-Gremlin is OLAP-oriented and for OLTP operations, linear-scan joins are required. This particular tests takes many minutes to execute.</span><span class="delimiter">&quot;</span></span>) <span class="annotation">@Graph</span>.OptOut( test = <span class="string"><span class="delimiter">&quot;</span><span class="content">org.apache.tinkerpop.gremlin.process.graph.step.map.MatchTest$Traversals</span><span class="delimiter">&quot;</span></span>, method = <span class="string"><span class="delimiter">&quot;</span><span class="content">g_V_matchXa_inXsungByX_b__a_inXsungByX_c__b_outXwrittenByX_d__c_outXwrittenByX_e__d_hasXname_George_HarisonX__e_hasXname_Bob_MarleyXX</span><span class="delimiter">&quot;</span></span>, reason = <span class="string"><span class="delimiter">&quot;</span><span class="content">Hadoop-Gremlin is OLAP-oriented and for OLTP operations, linear-scan joins are required. This particular tests takes many minutes to execute.</span><span class="delimiter">&quot;</span></span>) <span class="annotation">@Graph</span>.OptOut( test = <span class="string"><span class="delimiter">&quot;</span><span class="content">org.apache.tinkerpop.gremlin.process.computer.GraphComputerTest</span><span class="delimiter">&quot;</span></span>, method = <span class="string"><span class="delimiter">&quot;</span><span class="content">shouldNotAllowBadMemoryKeys</span><span class="delimiter">&quot;</span></span>, reason = <span class="string"><span class="delimiter">&quot;</span><span class="content">Hadoop does a hard kill on failure and stops threads which stops test cases. Exception handling semantics are correct though.</span><span class="delimiter">&quot;</span></span>) <span class="annotation">@Graph</span>.OptOut( test = <span class="string"><span class="delimiter">&quot;</span><span class="content">org.apache.tinkerpop.gremlin.process.computer.GraphComputerTest</span><span class="delimiter">&quot;</span></span>, method = <span class="string"><span class="delimiter">&quot;</span><span class="content">shouldRequireRegisteringMemoryKeys</span><span class="delimiter">&quot;</span></span>, reason = <span class="string"><span class="delimiter">&quot;</span><span class="content">Hadoop does a hard kill on failure and stops threads which stops test cases. Exception handling semantics are correct though.</span><span class="delimiter">&quot;</span></span>) <span class="directive">public</span> <span class="type">class</span> <span class="class">HadoopGraph</span> <span class="directive">implements</span> Graph {</code></pre> </div> </div> <div class="paragraph"> <p>The above examples show how to ignore individual tests. It is also possible to:</p> </div> <div class="ulist"> <ul> <li> <p>Ignore an entire test case (i.e. all the methods within the test) by setting the <code>method</code> to "*".</p> </li> <li> <p>Ignore a "base" test class such that test that extend from those classes will all be ignored.</p> </li> <li> <p>Ignore a <code>GraphComputer</code> test based on the type of <code>GraphComputer</code> being used. Specify the "computer" attribute on the <code>OptOut</code> (which is an array specification) which should have a value of the <code>GraphComputer</code> implementation class that should ignore that test. This attribute should be left empty for "standard" execution and by default all <code>GraphComputer</code> implementations will be included in the <code>OptOut</code> so if there are multiple implementations, explicitly specify the ones that should be excluded.</p> </li> </ul> </div> <div class="paragraph"> <p>Also note that some of the tests in the Gremlin Test Suite are parameterized tests and require an additional level of specificity to be properly ignored. To ignore these types of tests, examine the name template of the parameterized tests. It is defined by a Java annotation that looks like this:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Parameterized</span>.Parameters(name = <span class="string"><span class="delimiter">&quot;</span><span class="content">expect({0})</span><span class="delimiter">&quot;</span></span>)</code></pre> </div> </div> <div class="paragraph"> <p>The annotation above shows that the name of each parameterized test will be prefixed with "expect" and have parentheses wrapped around the first parameter (at index 0) value supplied to each test. This information can only be garnered by studying the test set up itself. Once the pattern is determined and the specific unique name of the parameterized test is identified, add it to the <code>specific</code> property on the <code>OptOut</code> annotation in addition to the other arguments.</p> </div> <div class="paragraph"> <p>These annotations help provide users a level of transparency into test suite compliance (via the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#describe-graph">describeGraph()</a> utility function). It also allows implementers to have a lot of flexibility in terms of how they wish to support TinkerPop. For example, maybe there is a single test case that prevents an implementer from claiming support of a <code>Feature</code>. The implementer could choose to either not support the <code>Feature</code> or to support it but "opt-out" of the test with a "reason" as to why so that users understand the limitation.</p> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <div class="title">Important</div> </td> <td class="content"> Before using <code>OptOut</code> be sure that the reason for using it is sound and it is more of a last resort. It is possible that a test from the suite doesn&#8217;t properly represent the expectations of a feature, is too broad or narrow for the semantics it is trying to enforce or simply contains a bug. Please consider raising issues in the developer mailing list with such concerns before assuming <code>OptOut</code> is the only answer. </td> </tr> </table> </div> <div class="admonitionblock important"> <table> <tr> <td class="icon"> <div class="title">Important</div> </td> <td class="content"> There are no tests that specifically validate complete compliance with Gremlin Server. Generally speaking, a <code>Graph</code> that passes the full Test Suite, should be compliant with Gremlin Server. The one area where problems can occur is in serialization. Always ensure that IO is properly implemented, that custom serializers are tested fully and ultimately integration test the <code>Graph</code> with an actual Gremlin Server instance. </td> </tr> </table> </div> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <div class="title">Warning</div> </td> <td class="content"> Configuring tests to run in parallel might result in errors that are difficult to debug as there is some shared state in test execution around graph configuration. It is therefore recommended that parallelism be turned off for the test suite (the Maven SureFire Plugin is configured this way by default). It may also be important to include this setting, <code>&lt;reuseForks&gt;false&lt;/reuseForks&gt;</code>, in the SureFire configuration if tests are failing in an unexplainable way. </td> </tr> </table> </div> <div class="admonitionblock warning"> <table> <tr> <td class="icon"> <div class="title">Warning</div> </td> <td class="content"> For graph implementations that require a schema, take note that TinkerPop tests were originally developed without too much concern for these types of graphs. While most tests utilize the standard toy graphs there are instances where tests will utilize their own independent schema that stands alone from all other tests. It may be necessary to create schemas specific to certain tests in those situations. </td> </tr> </table> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> When running the <code>gremlin-test</code> suite against your implementation, you may need to set <code>build.dir</code> as an environment variable, depending on your project layout. Some tests require this to find a writable directory for creating temporary files. The value is typically set to the project build directory. For example using the Maven SureFire Plugin, this is done via the configuration argLine with <code>-Dbuild.dir=${project.build.directory}</code>. </td> </tr> </table> </div> <div class="sect4"> <h5 id="_checking_resource_leaks">Checking Resource Leaks</h5> <div class="paragraph"> <p>The TinkerPop query engine retrieves data by interfacing with the provider using iterators. These iterators (depending on the provider) may hold up resources in the underlying storage layer and hence, it is critical to close them after the query is finished.</p> </div> <div class="paragraph"> <p>TinkerPop provides you with the ability to test for such resource leaks by checking for leaks when you run the Gremlin-Test suites against your implementation. To enable this leak detection, providers should increment the <code>StoreIteratorCounter</code> whenever a resource is opened and decrement it when it is closed. A reference implementation is provided with TinkerGraph as <code>TinkerGraphIterator.java</code>.</p> </div> <div class="paragraph"> <p>Assertions for leak detection are enabled by default when running the test suite. They can be temporarily disabled by way of a system property - simply set `-DtestIteratorLeaks=false".</p> </div> </div> </div> <div class="sect3"> <h4 id="gherkin-tests-suite">Gherkin Test Suite</h4> <div class="paragraph"> <p>The Gherkin Test Suite is a language agnostic set of tests that verify Gremlin semantics. It provides a unified set of tests that validate many TinkerPop components internally. The tests themselves can be found in <code>gremlin-tests/features</code> (<a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features">here</a>) with their syntax described in the <a href="https://tinkerpop.apache.org/docs/3.7.3/dev/developer/#gremlin-language-test-cases">TinkerPop Developer Documentation</a>.</p> </div> <div class="paragraph"> <p>TinkerPop provides some infrastructure, for JVM based graphs, to help make it easier for providers to implement these tests against their implementations. This infrastructure is built on <code>cucumber-java</code> which is a dependency of <code>gremlin-test</code>. There are two main components to implementing the tests:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>A <code>org.apache.tinkerpop.gremlin.features.World</code> implementation which is a class in <code>gremlin-test</code>.</p> </li> <li> <p>A JUnit test class that will act as the runner for the tests with the appropriate annotations</p> </li> </ol> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> It may be helpful to get familiar with <a href="https://cucumber.io/docs/installation/java/">Cucumber</a> before proceeding with an implementation. </td> </tr> </table> </div> <div class="paragraph"> <p>The <code>World</code> implementation provides context to the tests and allows providers to intercept test events that might be important to proper execution specific to their implementations. The most important part of implementing <code>World</code> is properly implementing the <code>GraphTraversalSource getGraphTraversalSource(GraphData)</code> method which provides to the test the <code>GraphTraversalSource</code> to execute the test against.</p> </div> <div class="paragraph"> <p>The JUnit test class is really just the test runner. It is a simple class which must include some Cucumber annotations. The following is just an example as taken from TinkerGraph:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@RunWith</span>(Cucumber.class) <span class="annotation">@CucumberOptions</span>( tags = <span class="string"><span class="delimiter">&quot;</span><span class="content">not @RemoteOnly</span><span class="delimiter">&quot;</span></span>, glue = { <span class="string"><span class="delimiter">&quot;</span><span class="content">org.apache.tinkerpop.gremlin.features</span><span class="delimiter">&quot;</span></span> }, features = { <span class="string"><span class="delimiter">&quot;</span><span class="content">classpath:/org/apache/tinkerpop/gremlin/test/features</span><span class="delimiter">&quot;</span></span> }, plugin = {<span class="string"><span class="delimiter">&quot;</span><span class="content">progress</span><span class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span class="content">junit:target/cucumber.xml</span><span class="delimiter">&quot;</span></span>, objectFactory = GuiceFactory.class})</code></pre> </div> </div> <div class="paragraph"> <p>The <code>@CucumberOptions</code> that are used are mostly implementation specific, so it will be up to the provider to make some choices as to what is right for their environment. For TinkerGraph, it needed to ignore Gherkin tests with the <code>@RemoteOnly</code> tag (the full list of possible tags can be found <a href="https://tinkerpop.apache.org/docs/3.7.3/dev/developer/#gherkin-tags">here</a>), as will most providers. The "glue" will be the same for all test implementers as it refers to a package containing TinkerPop&#8217;s test infrastructure in <code>gremlin-test</code> (unless of course, a provider needs to develop their own infrastructure for some reason). The "features" is the path to the actual Gherkin test files that should be made available locally. The files can be referenced on the classpath assuming <code>gremlin-test</code> is a dependency. The "plugin" defines a JUnit style output, which happens to be understood by Maven.</p> </div> <div class="paragraph"> <p>The "objectFactory" is the last component. Cucumber relies on dependency injection to get a <code>World</code> implementation into the test infrastructure. Providers may choose from multiple available implementations, but TinkerPop chose to use Guice. To follow this approach include the following module:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;dependency&gt;</span> <span class="tag">&lt;groupId&gt;</span>com.google.inject<span class="tag">&lt;/groupId&gt;</span> <span class="tag">&lt;artifactId&gt;</span>guice<span class="tag">&lt;/artifactId&gt;</span> <span class="tag">&lt;version&gt;</span>4.2.3<span class="tag">&lt;/version&gt;</span> <span class="tag">&lt;scope&gt;</span>test<span class="tag">&lt;/scope&gt;</span> <span class="tag">&lt;/dependency&gt;</span></code></pre> </div> </div> <div class="paragraph"> <p>Following the Neo4jGraph implementation, there are two classes to construct:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">class</span> <span class="class">ServiceModule</span> <span class="directive">extends</span> AbstractModule { <span class="annotation">@Override</span> <span class="directive">protected</span> <span class="type">void</span> configure() { bind(World.class).to(Neo4jGraphWorld.class); } } <span class="directive">public</span> <span class="type">class</span> <span class="class">WorldInjectorSource</span> <span class="directive">implements</span> InjectorSource { <span class="annotation">@Override</span> <span class="directive">public</span> Injector getInjector() { <span class="keyword">return</span> Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), <span class="keyword">new</span> ServiceModule()); } }</code></pre> </div> </div> <div class="paragraph"> <p>The key here is that the <code>Neo4jGraphWorld</code> implementation gets bound to <code>World</code> in the <code>ServiceModule</code> and there is a <code>WorldInjectorSource</code> that specifies the <code>ServiceModule</code> to Cucumber. As a final step, the provider&#8217;s test resources needs a <code>cucumber.properties</code> file with an entry that specifies the <code>InjectorSource</code> so that Guice can find it. Here is the example taken from TinkerGraph where the <code>WorldInjectorSource</code> is inner class of <code>TinkerGraphFeatureTest</code> itself.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">guice.injector-source=org.apache.tinkerpop.gremlin.neo4j.Neo4jGraphFeatureTest$WorldInjectorSource</code></pre> </div> </div> <div class="paragraph"> <p>In the event that a single <code>World</code> configuration is insufficient, it may be necessary to develop a custom <code>ObjectFactory</code>. An easy way to do this is to create a class that extends from the <code>AbstractGuiceFactory</code> in <code>gremlin-test</code> and provide that class to the <code>@CucumberOptions</code>. This approach does rely on the <code>ServiceLoader</code> which means it will be important to include a <code>io.cucumber.core.backend.ObjectFactory</code> file in <code>META-INF/services</code> and an entry that registers the custom implementation. Please see the TinkerGraph test code for further information on this approach.</p> </div> <div class="paragraph"> <p>If implementing the Gherkin tests, providers can choose to opt-in to the slimmed down version of the normal JVM process test suite to help alleviate test duplication between the two frameworks:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_STANDARD) <span class="annotation">@Graph</span>.OptIn(Graph.OptIn.SUITE_PROCESS_LIMITED_COMPUTER)</code></pre> </div> </div> </div> </div> <div class="sect2"> <h3 id="_accessibility_via_gremlinplugin">Accessibility via GremlinPlugin</h3> <div class="paragraph"> <p><span class="image left"><img src="../../images/gremlin-plugin.png" alt="gremlin plugin" width="100"></span> The applications distributed with TinkerPop do not distribute with any graph system implementations besides TinkerGraph. If your implementation is stored in a Maven repository (e.g. Maven Central Repository), then it is best to provide a <a href="#gremlin-plugins"><code>GremlinPlugin</code></a> implementation so the respective jars can be downloaded according and when required by the user. Neo4j&#8217;s GremlinPlugin is provided below for reference.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"> <span class="keyword">package</span> <span class="namespace">org.apache.tinkerpop.gremlin.neo4j.jsr223</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.AbstractGremlinPlugin</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.DefaultImportCustomizer</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.process.traversal.LabelP</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jEdge</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jElement</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraphVariables</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jHelper</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jProperty</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jVertex</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jVertexProperty</span>; <span class="comment">/** * @author Stephen Mallette (http://stephen.genoprime.com) * @deprecated See: https://tinkerpop.apache.org/docs/3.5.7/reference/#neo4j-gremlin */</span> <span class="annotation">@Deprecated</span> <span class="directive">public</span> <span class="directive">final</span> <span class="type">class</span> <span class="class">Neo4jGremlinPlugin</span> <span class="directive">extends</span> AbstractGremlinPlugin { <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> NAME = <span class="string"><span class="delimiter">&quot;</span><span class="content">tinkerpop.neo4j</span><span class="delimiter">&quot;</span></span>; <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> ImportCustomizer imports; <span class="directive">static</span> { <span class="keyword">try</span> { imports = DefaultImportCustomizer.build() .addClassImports(Neo4jEdge.class, Neo4jElement.class, Neo4jGraph.class, Neo4jGraphVariables.class, Neo4jHelper.class, Neo4jProperty.class, Neo4jVertex.class, Neo4jVertexProperty.class, LabelP.class) .addMethodImports(LabelP.class.getMethod(<span class="string"><span class="delimiter">&quot;</span><span class="content">of</span><span class="delimiter">&quot;</span></span>, <span class="predefined-type">String</span>.class)).create(); } <span class="keyword">catch</span> (<span class="exception">Exception</span> ex) { <span class="keyword">throw</span> <span class="keyword">new</span> <span class="exception">RuntimeException</span>(ex); } } <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> Neo4jGremlinPlugin instance = <span class="keyword">new</span> Neo4jGremlinPlugin(); <span class="directive">public</span> Neo4jGremlinPlugin() { <span class="local-variable">super</span>(NAME, imports); } <span class="directive">public</span> <span class="directive">static</span> Neo4jGremlinPlugin instance() { <span class="keyword">return</span> instance; } <span class="annotation">@Override</span> <span class="directive">public</span> <span class="type">boolean</span> requireRestart() { <span class="keyword">return</span> <span class="predefined-constant">true</span>; } }</code></pre> </div> </div> <div class="paragraph"> <p>With the above plugin implementations, users can now download respective binaries for Gremlin Console, Gremlin Server, etc.</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="groovy">gremlin&gt; g = Neo4jGraph.open(<span class="string"><span class="delimiter">'</span><span class="content">/tmp/neo4j</span><span class="delimiter">'</span></span>) No such <span class="key">property</span>: Neo4jGraph <span class="keyword">for</span> <span class="type">class</span>: <span class="class">groovysh_evaluate</span> Display stack trace? [yN] gremlin&gt; :install org.apache.tinkerpop neo4j-gremlin 3.7.3 ==&gt;<span class="key">loaded</span>: [org.apache.tinkerpop, neo4j-gremlin, <span class="error">…</span>] gremlin&gt; :plugin use tinkerpop.neo4j ==&gt;tinkerpop.neo4j activated gremlin&gt; g = Neo4jGraph.open(<span class="string"><span class="delimiter">'</span><span class="content">/tmp/neo4j</span><span class="delimiter">'</span></span>) ==&gt;neo4jgraph[EmbeddedGraphDatabase [<span class="regexp"><span class="delimiter">/</span><span class="content">tmp</span><span class="delimiter">/</span></span>neo4j]]</code></pre> </div> </div> </div> <div class="sect2"> <h3 id="_in_depth_implementations">In-Depth Implementations</h3> <div class="paragraph"> <p><span class="image right"><img src="../../images/gremlin-painting.png" alt="gremlin painting" width="200"></span> The graph system implementation details presented thus far are minimum requirements necessary to yield a valid TinkerPop implementation. However, there are other areas that a graph system provider can tweak to provide an implementation more optimized for their underlying graph engine. Typical areas of focus include:</p> </div> <div class="ulist"> <ul> <li> <p>Traversal Strategies: A <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#traversalstrategy">TraversalStrategy</a> can be used to alter a traversal prior to its execution. A typical example is converting a pattern of <code>g.V().has('name','marko')</code> into a global index lookup for all vertices with name "marko". In this way, a <code>O(|V|)</code> lookup becomes an <code>O(log(|V|))</code>. Please review <code>TinkerGraphStepStrategy</code> for ideas.</p> </li> <li> <p>Step Implementations: Every <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#graph-traversal-steps">step</a> is ultimately referenced by the <code>GraphTraversal</code> interface. It is possible to extend <code>GraphTraversal</code> to use a graph system specific step implementation. Note that while it is sometimes possible to develop custom step implementations by extending from a TinkerPop step (typically, <code>AddVertexStep</code> and other <code>Mutating</code> steps), it&#8217;s important to consider that doing so introduces some greater risk for code breaks on upgrades as opposed to other areas of the code base. As steps are more internal features of TinkerPop, they might be subject to breaking API and behavioral changes that would be less likely to be accepted by more public facing interfaces.</p> </li> </ul> </div> </div> </div> </div> <div class="sect1"> <h2 id="_graph_driver_provider_requirements">Graph Driver Provider Requirements</h2> <div class="sectionbody"> <div class="imageblock"> <div class="content"> <img src="../../images/gremlin-server-protocol.png" alt="gremlin server protocol" width="325"> </div> </div> <div class="paragraph"> <p>One of the roles for <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#gremlin-server">Gremlin Server</a> is to provide a bridge from TinkerPop to non-JVM languages (e.g. Go, Python, etc.). Developers can build language bindings (or driver) that provide a way to submit Gremlin scripts to Gremlin Server and get back results. Given the extensible nature of Gremlin Server, it is difficult to provide an authoritative guide to developing a driver. It is however possible to describe the core communication protocol using the standard out-of-the-box configuration which should provide enough information to develop a driver for a specific language.</p> </div> <div class="imageblock right"> <div class="content"> <img src="../../images/gremlin-server-flow.png" alt="gremlin server flow" width="300"> </div> </div> <div class="paragraph"> <p>Gremlin Server is distributed with a configuration that utilizes <a href="http://en.wikipedia.org/wiki/WebSocket">WebSocket</a> with a custom sub-protocol. Under this configuration, Gremlin Server accepts requests containing a Gremlin script, evaluates that script and then streams back the results. The notion of "streaming" is depicted in the diagram to the right.</p> </div> <div class="paragraph"> <p>The diagram shows an incoming request to process the Gremlin script of <code>g.V()</code>. Gremlin Server evaluates that script, getting an <code>Iterator</code> of vertices as a result, and steps through each <code>Vertex</code> within it. The vertices are batched together given the <code>resultIterationBatchSize</code> configuration. In this case, that value must be <code>2</code> given that each "response" contains two vertices. Each response is serialized given the requested serializer type (JSON is likely best for non-JVM languages) and written back to the requesting client immediately. Gremlin Server does not wait for the entire result to be iterated, before sending back a response. It will send the responses as they are realized.</p> </div> <div class="paragraph"> <p>This approach allows for the processing of large result sets without having to serialize the entire result into memory for the response. It places a bit of a burden on the developer of the driver however, because it becomes necessary to provide a way to reconstruct the entire result on the client side from all of the individual responses that Gremlin Server returns for a single request. Again, this description of Gremlin Server&#8217;s "flow" is related to the out-of-the-box configuration. It is quite possible to construct other flows, that might be more amenable to a particular language or style of processing.</p> </div> <div class="paragraph"> <p>It is recommended but not required that a driver include a <code>User-Agent</code> header as part of any web socket handshake request to Gremlin Server. Gremlin Server uses the user agent in building usage metrics as well as debugging. The standard format for connection user agents is:</p> </div> <div class="paragraph"> <p><code>"[Application Name] [GLV Name].[Version] [Language Runtime Version] [OS].[Version] [CPU Architecture]"</code> For example: <code>"MyTestApplication Gremlin-Java.3.5.4 11.0.16.1 Mac_OS_X.12.6.1 aarch64"</code></p> </div> <div class="paragraph"> <p>To formulate a request to Gremlin Server, a <code>RequestMessage</code> needs to be constructed. The <code>RequestMessage</code> is a generalized representation of a request that carries a set of "standard" values in addition to optional ones that are dependent on the operation being performed. A <code>RequestMessage</code> has these fields:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">requestId</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A <a href="http://en.wikipedia.org/wiki/Globally_unique_identifier">UUID</a> representing the unique identification for the request.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">op</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The name of the "operation" to execute based on the available <code>OpProcessor</code> configured in the Gremlin Server. To evaluate a script, use <code>eval</code>.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">processor</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The name of the <code>OpProcessor</code> to utilize. The default <code>OpProcessor</code> for evaluating scripts is unnamed and therefore script evaluation purposes, this value can be an empty string.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">args</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A <code>Map</code> of arbitrary parameters to pass to Gremlin Server. The requirements for the contents of this <code>Map</code> are dependent on the <code>op</code> selected.</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>This message can be serialized in any fashion that is supported by Gremlin Server. New serialization methods can be plugged in by implementing a <code>ServiceLoader</code> enabled <code>MessageSerializer</code>, however Gremlin Server provides for JSON serialization by default which will be good enough for purposes of most developers building drivers. A <code>RequestMessage</code> to evaluate a script with variable bindings looks like this in JSON:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="js">{ <span class="key"><span class="delimiter">&quot;</span><span class="content">requestId</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="content">1d6d02bd-8e56-421d-9438-3bd6d0079ff1</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">op</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="content">eval</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">processor</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">args</span><span class="delimiter">&quot;</span></span>:{<span class="key"><span class="delimiter">&quot;</span><span class="content">gremlin</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="content">g.V(x).out()</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">bindings</span><span class="delimiter">&quot;</span></span>:{<span class="key"><span class="delimiter">&quot;</span><span class="content">x</span><span class="delimiter">&quot;</span></span>:<span class="integer">1</span>}, <span class="key"><span class="delimiter">&quot;</span><span class="content">language</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="content">gremlin-groovy</span><span class="delimiter">&quot;</span></span>}}</code></pre> </div> </div> <div class="paragraph"> <p>The above JSON represents the "body" of the request to send to Gremlin Server. When sending this "body" over WebSocket, Gremlin Server can accept a packet frame using a "text" (1) or a "binary" (2) opcode. Using "text" is a bit more limited in that Gremlin Server will always process the body of that request as JSON. Generally speaking "text" is just for testing purposes.</p> </div> <div class="paragraph"> <p>The preferred method for sending requests to Gremlin Server is to use the "binary" opcode. In this case, a "header" will need be sent in addition to to the "body". The "header" basically consists of a "mime type" so that Gremlin Server knows how to deserialize the <code>RequestMessage</code>. So, the actual byte array sent to Gremlin Server would be formatted as follows:</p> </div> <div class="imageblock"> <div class="content"> <img src="../../images/gremlin-server-request.png" alt="gremlin server request"> </div> </div> <div class="paragraph"> <p>The first byte represents the length of the "mime type" string value that follows. Given the default configuration of Gremlin Server, this value should be set to <code>application/json</code>. The "payload" represents the JSON message above encoded as bytes.</p> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> Gremlin Server will only accept masked packets as it pertains to a WebSocket packet header construction. </td> </tr> </table> </div> <div class="paragraph"> <p>When Gremlin Server receives that request, it will decode it given the "mime type", pass it to the requested <code>OpProcessor</code> which will execute the <code>op</code> defined in the message. In this case, it will evaluate the script <code>g.V(x).out()</code> using the <code>bindings</code> supplied in the <code>args</code> and stream back the results in a series of <code>ResponseMessages</code>. A <code>ResponseMessage</code> looks like this:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">requestId</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The identifier of the <code>RequestMessage</code> that generated this <code>ResponseMessage</code>.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">status</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The <code>status</code> contains a <code>Map</code> of three keys: <code>code</code> which refers to a <code>ResultCode</code> that is somewhat analogous to an <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP status code</a>, <code>attributes</code> that represent a <code>Map</code> of protocol-level information, and <code>message</code> which is just a human-readable <code>String</code> usually associated with errors.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">result</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The <code>result</code> contains a <code>Map</code> of two keys: <code>data</code> which refers to the actual data returned from the server (the type of data is determined by the operation requested) and <code>meta</code> which is a <code>Map</code> of meta-data related to the response.</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>In this case the <code>ResponseMessage</code> returned to the client would look something like this:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="js">{<span class="key"><span class="delimiter">&quot;</span><span class="content">result</span><span class="delimiter">&quot;</span></span>:{<span class="key"><span class="delimiter">&quot;</span><span class="content">data</span><span class="delimiter">&quot;</span></span>:[{<span class="key"><span class="delimiter">&quot;</span><span class="content">id</span><span class="delimiter">&quot;</span></span>: <span class="integer">2</span>,<span class="key"><span class="delimiter">&quot;</span><span class="content">label</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">person</span><span class="delimiter">&quot;</span></span>,<span class="key"><span class="delimiter">&quot;</span><span class="content">type</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">vertex</span><span class="delimiter">&quot;</span></span>,<span class="key"><span class="delimiter">&quot;</span><span class="content">properties</span><span class="delimiter">&quot;</span></span>: [ {<span class="key"><span class="delimiter">&quot;</span><span class="content">id</span><span class="delimiter">&quot;</span></span>: <span class="integer">2</span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">value</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">vadas</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">label</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>}, {<span class="key"><span class="delimiter">&quot;</span><span class="content">id</span><span class="delimiter">&quot;</span></span>: <span class="integer">3</span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">value</span><span class="delimiter">&quot;</span></span>: <span class="integer">27</span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">label</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>}]}, ], <span class="key"><span class="delimiter">&quot;</span><span class="content">meta</span><span class="delimiter">&quot;</span></span>:{}}, <span class="key"><span class="delimiter">&quot;</span><span class="content">requestId</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="content">1d6d02bd-8e56-421d-9438-3bd6d0079ff1</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">status</span><span class="delimiter">&quot;</span></span>:{<span class="key"><span class="delimiter">&quot;</span><span class="content">code</span><span class="delimiter">&quot;</span></span>:<span class="integer">206</span>,<span class="key"><span class="delimiter">&quot;</span><span class="content">attributes</span><span class="delimiter">&quot;</span></span>:{},<span class="key"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>:<span class="string"><span class="delimiter">&quot;</span><span class="delimiter">&quot;</span></span>}}</code></pre> </div> </div> <div class="paragraph"> <p>Gremlin Server is capable of streaming results such that additional responses will arrive over the WebSocket connection until the iteration of the result on the server is complete. Each successful incremental message will have a <code>ResultCode</code> of <code>206</code>. Termination of the stream will be marked by a final <code>200</code> status code. Note that all messages without a <code>206</code> represent terminating conditions for a request. The following table details the various status codes that Gremlin Server will send:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Code</th> <th class="tableblock halign-left valign-top">Name</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SUCCESS</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server successfully processed a request to completion - there are no messages remaining in this stream.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">204</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">NO CONTENT</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server processed the request but there is no result to return (e.g. an <code>Iterator</code> with no elements) - there are no messages remaining in this stream.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">206</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">PARTIAL CONTENT</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server successfully returned some content, but there is more in the stream to arrive - wait for a <code>SUCCESS</code> to signify the end of the stream.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">401</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">UNAUTHORIZED</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The request attempted to access resources that the requesting user did not have access to.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">403</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">FORBIDDEN</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server could authenticate the request, but will not fulfill it.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">407</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">AUTHENTICATE</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A challenge from the server for the client to authenticate its request.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">497</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">REQUEST ERROR SERIALIZATION</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The request message contained an object that was not serializable.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">498</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">REQUEST ERROR MALFORMED REQUEST</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The request message was not properly formatted which means it could not be parsed at all or the "op" code was not recognized such that Gremlin Server could properly route it for processing. Check the message format and retry the request.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">499</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">REQUEST ERROR INVALID REQUEST ARGUMENTS</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The request message was parseable, but the arguments supplied in the message were in conflict or incomplete. Check the message format and retry the request.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">500</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A general server error occurred that prevented the request from being processed.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">595</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR FAIL STEP</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A server error that is produced when the <code>fail()</code> step is triggered. The returned exception will include information consistent with the <code>Failure</code> interface.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">596</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR TEMPORARY</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A server error occurred, but it was temporary in nature and therefore the client is free to retry it&#8217;s request as-is with the potential for success.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">597</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR EVALUATION</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The script submitted for processing evaluated in the <code>ScriptEngine</code> with errors and could not be processed. Check the script submitted for syntax errors or other problems and then resubmit.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">598</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR TIMEOUT</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server exceeded one of the timeout settings for the request and could therefore only partially responded or did not respond at all.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">599</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">SERVER ERROR SERIALIZATION</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The server was not capable of serializing an object that was returned from the script supplied on the request. Either transform the object into something Gremlin Server can process within the script or install mapper serialization classes to Gremlin Server.</p></td> </tr> </tbody> </table> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> Please refer to the <a href="https://tinkerpop.apache.org/docs/3.7.3/dev/io">IO Reference Documentation</a> for more examples of <code>RequestMessage</code> and <code>ResponseMessage</code> instances. </td> </tr> </table> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> Tinkerpop provides a test server which may be useful for testing drivers. Details can be found <a href="https://tinkerpop.apache.org/docs/current/dev/developer/#gremlin-socket-server-tests">here</a> </td> </tr> </table> </div> <div class="sect2"> <h3 id="_opprocessors_arguments">OpProcessors Arguments</h3> <div class="paragraph"> <p>The following sections define a non-exhaustive list of available operations and arguments for embedded <code>OpProcessors</code> (i.e. ones packaged with Gremlin Server).</p> </div> <div class="sect3"> <h4 id="_common">Common</h4> <div class="paragraph"> <p>All <code>OpProcessor</code> instances support these arguments.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">batchSize</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Int</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">When the result is an iterator this value defines the number of iterations each <code>ResponseMessage</code> should contain - overrides the <code>resultIterationBatchSize</code> server setting.</p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_standard_opprocessor">Standard OpProcessor</h4> <div class="paragraph"> <p>The "standard" <code>OpProcessor</code> handles requests for the primary function of Gremlin Server - executing Gremlin. Requests made to this <code>OpProcessor</code> are "sessionless" in the sense that a request must encapsulate the entirety of a transaction. There is no state maintained between requests. A transaction is started when the script is first evaluated and is committed when the script completes (or rolled back if an error occurred).</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">processor</p></td> <td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph"> <p>As this is the default <code>OpProcessor</code> this value can be set to an empty string.</p> </div></div></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">op</p></td> <td class="tableblock halign-left valign-top"><div class="content"><table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>authentication</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A request that contains the response to a server challenge for authentication.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>eval</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Evaluate a Gremlin script provided as a <code>String</code>.</p></td> </tr> </tbody> </table></div></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>authentication</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">sasl</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The response to the server authentication challenge. This value is dependent on the SASL authentication mechanism required by the server and is Base64 encoded.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">saslMechanism</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The SASL mechanism: <code>PLAIN</code> or <code>GSSAPI</code>. Note that it is up to the server implementation to use or disregard this setting (default implementation in Gremlin Server ignores it).</p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>eval</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">gremlin</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The Gremlin script to evaluate.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">bindings</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Map</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A map of key/value pairs to apply as variables in the context of the Gremlin script.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">language</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The flavor of Gremlin used (e.g. <code>gremlin-groovy</code>).</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">aliases</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Map</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A map of key/value pairs that allow globally bound <code>Graph</code> and <code>TraversalSource</code> objects to be aliased to different variable names for purposes of the current request. The value represents the name of the global variable and its key represents the new binding name as it will be referenced in the Gremlin query. For example, if the Gremlin Server defines two <code>TraversalSource</code> instances named <code>g1</code> and <code>g2</code>, it would be possible to send an alias pair with key of "g" and value of "g2" and thus allow the script to refer to "g2" simply as "g".</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">evaluationTimeout</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Long</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">An override for the server setting that determines the maximum time to wait for a script to execute on the server.</p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_session_opprocessor">Session OpProcessor</h4> <div class="paragraph"> <p>The "session" <code>OpProcessor</code> handles requests for the primary function of Gremlin Server - executing Gremlin. It is like the "standard" <code>OpProcessor</code>, but instead maintains state between sessions and allows the option to leave all transaction management up to the calling client. It is important that clients that open sessions, commit or roll them back, however Gremlin Server will try to clean up such things when a session is killed that has been abandoned. It is important to consider that a session can only be maintained with a single machine. In the event that multiple Gremlin Server are deployed, session state is not shared among them.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">processor</p></td> <td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph"> <p>This value should be set to <code>session</code></p> </div></div></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">op</p></td> <td class="tableblock halign-left valign-top"><div class="content"><table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>authentication</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A request that contains the response to a server challenge for authentication.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>eval</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Evaluate a Gremlin script provided as a <code>String</code>.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>close</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Deprecated. Gremlin-Server will only return a <code>NO CONTENT</code> message.</p></td> </tr> </tbody> </table></div></td> </tr> </tbody> </table> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> The "close" message related to sessions was deprecated as of 3.3.11. Closing sessions now relies on closing the connections. The function to accept <code>close</code> message on the server was removed in 3.5.0, but has been added back as of 3.5.2. Servers wishing to be compatible with older versions of the driver need only send back a <code>NO_CONTENT</code> for this message (which is what Gremlin Server does as of 3.5.0). Drivers wishing to be compatible with servers prior to 3.3.11 may continue to send the message on calls to <code>close()</code>, otherwise such code can be removed. </td> </tr> </table> </div> <div class="paragraph"> <p><strong><code>authentication</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">saslMechanism</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The SASL mechanism: <code>PLAIN</code> or <code>GSSAPI</code>. Note that it is up to the server implementation to use or disregard this setting (default implementation in Gremlin Server ignores it).</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">sasl</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The response to the server authentication challenge. This value is dependent on the SASL authentication mechanism required by the server and is Base64 encoded.</p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>eval</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">gremlin</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The Gremlin script to evaluate.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">session</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The session identifier for the current session - typically this value should be a UUID (the session will be created if it doesn&#8217;t exist).</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">manageTransaction</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Boolean</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">When set to <code>true</code> the transaction for the current request is auto-committed or rolled-back as are done with sessionless requests - defaulted to <code>false</code>.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">bindings</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Map</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A map of key/value pairs to apply as variables in the context of the Gremlin script.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">evaluationTimeout</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Long</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">An override for the server setting that determines the maximum time to wait for a script to execute on the server.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">language</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">The flavor of Gremlin used (e.g. <code>gremlin-groovy</code>)</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">aliases</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Map</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A map of key/value pairs that allow globally bound <code>Graph</code> and <code>TraversalSource</code> objects to be aliased to different variable names for purposes of the current request. The value represents the name the global variable and its key represents the new binding name as it will be referenced in the Gremlin query. For example, if the Gremlin Server defines two <code>TraversalSource</code> instances named <code>g1</code> and <code>g2</code>, it would be possible to send an alias pair with key of "g" and value of "g2" and thus allow the script to refer to "g2" simply as "g".</p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>close</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">session</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The session identifier for the session to close.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">force</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Boolean</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Determines if the session should be force closed when the client is closed. Force closing will not attempt to close open transactions from existing running jobs and leave it to the underlying graph to decided how to proceed with those orphaned transactions. Setting this to <code>true</code> tends to lead to faster close operation and release of resources which can be desirable if Gremlin Server has a long session timeout and a long script evaluation timeout as attempts to close long run jobs can occur more rapidly. If not provided, this value is <code>false</code>.</p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_traversal_opprocessor">Traversal OpProcessor</h4> <div class="paragraph"> <p>Both the Standard and Session OpProcessors allow for Gremlin scripts to be submitted to the server. The <code>TraversalOpProcessor</code> however allows Gremlin <code>Bytecode</code> to be submitted to the server. Supporting this <code>OpProcessor</code> makes it possible for a <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#gremlin-drivers-variants">Gremlin Language Variant</a> to submit a <code>Traversal</code> directly to Gremlin Server in the native language of the GLV without having to use a script in a different language.</p> </div> <div class="paragraph"> <p>Unlike Standard and Session OpProcessors, the Traversal OpProcessor does not simply return the results of the <code>Traversal</code>. It instead returns <code>Traverser</code> objects which allows the client to take advantage of <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#barrier-step">bulking</a>. To describe this interaction more directly, the returned <code>Traverser</code> will represent some value from the <code>Traversal</code> result and the number of times it is represented in the full stream of results. So, if a <code>Traversal</code> happens to return the same vertex twenty times it won&#8217;t return twenty instances of the same object. It will return one in <code>Traverser</code> with the <code>bulk</code> value set to twenty. Under this model, the amount of processing and network overhead can be reduced considerably.</p> </div> <div class="paragraph"> <p>To demonstrate consider this example:</p> </div> <section class="tabs tabs-2"> <input id="tab-1729797260-1" type="radio" name="radio-set-1729797260-1" class="tab-selector-1" checked="checked" /> <label for="tab-1729797260-1" class="tab-label-1">console (groovy)</label> <input id="tab-1729797260-2" type="radio" name="radio-set-1729797260-1" class="tab-selector-2" /> <label for="tab-1729797260-2" class="tab-label-2">groovy</label> <div class="tabcontent"> <div class="tabcontent-1"> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="groovy">gremlin&gt; cluster = Cluster.open() ==&gt;localhost/<span class="float">127.0</span><span class="float">.0</span><span class="float">.1</span>:<span class="integer">8182</span> gremlin&gt; client = cluster.connect() ==&gt;org.apache.tinkerpop.gremlin.driver.Client<span class="error">$</span>ClusteredClient<span class="error">@</span><span class="integer">3</span>ee9eaa7 gremlin&gt; aliased = client.alias(<span class="string"><span class="delimiter">&quot;</span><span class="content">g</span><span class="delimiter">&quot;</span></span>) ==&gt;org.apache.tinkerpop.gremlin.driver.Client<span class="error">$</span>AliasClusteredClient<span class="error">@</span><span class="integer">41</span>c983d3 gremlin&gt; g = traversal().withEmbedded(org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph.instance()) <span class="comment">//</span>// <b class="conum">(1)</b> ==&gt;graphtraversalsource[emptygraph[empty], standard] gremlin&gt; rs = aliased.submit(g.V().both().barrier().both().barrier()).all().get() <span class="comment">//</span>// <b class="conum">(2)</b> ==&gt;result{object=v[<span class="integer">1</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} ==&gt;result{object=v[<span class="integer">4</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} ==&gt;result{object=v[<span class="integer">6</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} ==&gt;result{object=v[<span class="integer">5</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} ==&gt;result{object=v[<span class="integer">3</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} ==&gt;result{object=v[<span class="integer">2</span>] <span class="type">class</span>=<span class="class">org</span>.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser} gremlin&gt; aliased.submit(g.V().both().barrier().both().barrier().count()).all().get().get(<span class="integer">0</span>).getInt() <span class="comment">//</span>// <b class="conum">(3)</b> ==&gt;<span class="integer">30</span> gremlin&gt; rs.collect{[<span class="key">value</span>: <span class="local-variable">it</span>.getObject().get(), <span class="key">bulk</span>: <span class="local-variable">it</span>.getObject().bulk()]} <span class="comment">//</span>// <b class="conum">(4)</b> ==&gt;[<span class="key">value</span>:v[<span class="integer">1</span>],<span class="key">bulk</span>:<span class="integer">7</span>] ==&gt;[<span class="key">value</span>:v[<span class="integer">4</span>],<span class="key">bulk</span>:<span class="integer">7</span>] ==&gt;[<span class="key">value</span>:v[<span class="integer">6</span>],<span class="key">bulk</span>:<span class="integer">3</span>] ==&gt;[<span class="key">value</span>:v[<span class="integer">5</span>],<span class="key">bulk</span>:<span class="integer">3</span>] ==&gt;[<span class="key">value</span>:v[<span class="integer">3</span>],<span class="key">bulk</span>:<span class="integer">7</span>] ==&gt;[<span class="key">value</span>:v[<span class="integer">2</span>],<span class="key">bulk</span>:<span class="integer">3</span>]</code></pre> </div> </div> </div> </div> <div class="tabcontent"> <div class="tabcontent-2"> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="groovy">cluster = Cluster.open() client = cluster.connect() aliased = client.alias(<span class="string"><span class="delimiter">&quot;</span><span class="content">g</span><span class="delimiter">&quot;</span></span>) g = traversal().withEmbedded(org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph.instance()) <span class="comment">//</span>// <b class="conum">(1)</b> rs = aliased.submit(g.V().both().barrier().both().barrier()).all().get() <span class="comment">//</span>// <b class="conum">(2)</b> aliased.submit(g.V().both().barrier().both().barrier().count()).all().get().get(<span class="integer">0</span>).getInt() <span class="comment">//</span>// <b class="conum">(3)</b> rs.collect{[<span class="key">value</span>: <span class="local-variable">it</span>.getObject().get(), <span class="key">bulk</span>: <span class="local-variable">it</span>.getObject().bulk()]} <span class="invisible">//</span><b class="conum">4</b></code></pre> </div> </div> </div> </div> </section> <div class="colist arabic"> <ol> <li> <p>All commands through this step are just designed to demonstrate bulking with Gremlin Server and don&#8217;t represent a real-world way that this feature would be used.</p> </li> <li> <p>Submit a <code>Traversal</code> that happens to ensure that the server uses bulking. Note that a <code>Traverser</code> is returned and that there are only six results.</p> </li> <li> <p>In actuality, however, if this same <code>Traversal</code> is iterated there are thirty results. Without bulking, the previous request would have sent back thirty traversers.</p> </li> <li> <p>Note that the sum of the bulk of each <code>Traverser</code> is thirty.</p> </li> </ol> </div> <div class="paragraph"> <p>The full iteration of a <code>Traversal</code> is thus left to the client. It must interpret the bulk on the <code>Traverser</code> and unroll it to represent the actual number of times it exists when iterated. The unrolling is typically handled directly within TinkerPop&#8217;s remote traversal implementations.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">processor</p></td> <td class="tableblock halign-left valign-top"><div class="content"><div class="paragraph"> <p>This value should be set to <code>traversal</code></p> </div></div></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">op</p></td> <td class="tableblock halign-left valign-top"><div class="content"><table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 23.0769%;"> <col style="width: 76.9231%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>authentication</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A request that contains the response to a server challenge for authentication.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>bytecode</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">A request that contains the <code>Bytecode</code> representation of a <code>Traversal</code>.</p></td> </tr> </tbody> </table></div></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>authentication</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">sasl</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The response to the server authentication challenge. This value is dependent on the SASL authentication mechanism required by the server and is Base64 encoded.</p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong><code>bytecode</code> operation arguments</strong></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 15.3846%;"> <col style="width: 15.3846%;"> <col style="width: 69.2308%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Key</th> <th class="tableblock halign-left valign-top">Type</th> <th class="tableblock halign-left valign-top">Description</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">gremlin</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> The <code>Bytecode</code> representation of a <code>Traversal</code>.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">aliases</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Map</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Required</strong> A map with a single key/value pair that refers to a globally bound <code>TraversalSource</code> object to be aliased to different variable names for purposes of the current request. The value represents the name of the global variable and its key represents the new binding name as it will be referenced in the Gremlin query. For example, if the Gremlin Server defines two <code>TraversalSource</code> instances named <code>g1</code>, it would be possible to send an alias pair with key of "g" and value of "g1" and thus allow the script to refer to "g1" simply as "g". Note that unlike users of <code>alias</code> in other contexts, in this case, the key can <strong>only</strong> be set to "g" and there can be only one key value pair present (since only one <code>Traversal</code> is being submitted, there is no sense to having more than a single alias).</p></td> </tr> </tbody> </table> </div> </div> <div class="sect2"> <h3 id="_authentication_and_authorization">Authentication and Authorization</h3> <div class="paragraph"> <p>Gremlin Server supports <a href="https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer">SASL-based</a> authentication. A SASL implementation provides a series of challenges and responses that a driver must comply with in order to authenticate. Gremlin Server supports the "PLAIN" SASL mechanism, which is a cleartext password system, for all <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#gremlin-drivers-variants">Gremlin Language Variants</a>. Other SASL mechanisms supported for selected clients are listed in the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#security">security section of the Gremlin Server reference documentation</a>.</p> </div> <div class="paragraph"> <p>When authentication is enabled, an incoming request is intercepted before it is evaluated by the <code>ScriptEngine</code>. The request is saved on the server and a <code>AUTHENTICATE</code> challenge response (status code <code>407</code>) is returned to the client.</p> </div> <div class="paragraph"> <p>The client will detect the <code>AUTHENTICATE</code> and respond with an <code>authentication</code> for the <code>op</code> and an <code>arg</code> named <code>sasl</code>. In case of the "PLAIN" SASL mechanism the <code>arg</code> contains the password. The password should be either, an encoded sequence of UTF-8 bytes, delimited by 0 (US-ASCII NUL), where the form is : <code>&lt;NUL&gt;username&lt;NUL&gt;password</code>, or a Base64 encoded string of the former (which in this instance would be <code>AHVzZXJuYW1lAHBhc3N3b3Jk</code>). Should Gremlin Server be able to authenticate with the provided credentials, the server will return the results of the original request as it normally does without authentication. If it cannot authenticate given the challenge response from the client, it will return <code>UNAUTHORIZED</code> (status code <code>401</code>).</p> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> Gremlin Server does not support the "authorization identity" as described in <a href="https://tools.ietf.org/html/rfc4616">RFC4616</a>. </td> </tr> </table> </div> <div class="paragraph"> <p>In addition to authenticating users at the start of a connection, Gremlin Server allows providers to authorize users on a per request basis. If <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#_configuring_2">a java class is configured</a> that implements the <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/server/authz/Authorizer.html">Authorizer interface</a>, Gremlin Server passes each request to this <code>Authorizer</code>. The <code>Authorizer</code> can deny authorization for the request by throwing an exception and Gremlin Server returns <code>UNAUTHORIZED</code> (status code <code>401</code>) to the client. The <code>Authorizer</code> authorizes the request by returning the original request or the request with some additional constraints. Gremlin Server proceeds with the returned request and on its turn returns the result of the request to the client. More details on implementing authorization can be found in the <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#security">reference documentation for Gremlin Server security</a>.</p> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> While Gremlin Server supports this authorization feature it is not a feature that TinkerPop requires of graph providers as part of the agreement between client and server. </td> </tr> </table> </div> </div> </div> </div> <div class="sect1"> <h2 id="gremlin-plugins">Gremlin Plugins</h2> <div class="sectionbody"> <div class="paragraph"> <p><span class="image"><img src="../../images/gremlin-plugin.png" alt="gremlin plugin" width="125"></span></p> </div> <div class="paragraph"> <p>Plugins provide a way to expand the features of a <code>GremlinScriptEngine</code>, which stands at that core of both Gremlin Console and Gremlin Server. Providers may wish to create plugins for a variety of reasons, but some common examples include:</p> </div> <div class="ulist"> <ul> <li> <p>Initialize the <code>GremlinScriptEngine</code> application with important classes so that the user doesn&#8217;t need to type their own imports.</p> </li> <li> <p>Place specific objects in the bindings of the <code>GremlinScriptEngine</code> for the convenience of the user.</p> </li> <li> <p>Bootstrap the <code>GremlinScriptEngine</code> with custom functions so that they are ready for usage at startup.</p> </li> </ul> </div> <div class="paragraph"> <p>The first step to developing a plugin is to implement the <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/GremlinPlugin.html">GremlinPlugin</a> interface:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"> <span class="keyword">package</span> <span class="namespace">org.apache.tinkerpop.gremlin.jsr223</span>; <span class="keyword">import</span> <span class="include">java.util.Optional</span>; <span class="comment">/** * A plugin interface that is used by the {@link GremlinScriptEngineManager} to configure special {@link Customizer} * instances that will alter the features of any {@link GremlinScriptEngine} created by the manager itself. * * @author Stephen Mallette (http://stephen.genoprime.com) */</span> <span class="directive">public</span> <span class="type">interface</span> <span class="class">GremlinPlugin</span> { <span class="comment">/** * The name of the module. This name should be unique (use a namespaced approach) as naming clashes will * prevent proper module operations. Modules developed by TinkerPop will be prefixed with &quot;tinkerpop.&quot; * For example, TinkerPop's implementation of Spark would be named &quot;tinkerpop.spark&quot;. If Facebook were * to do their own implementation the implementation might be called &quot;facebook.spark&quot;. */</span> <span class="directive">public</span> <span class="predefined-type">String</span> getName(); <span class="comment">/** * Some modules may require a restart of the plugin host for the classloader to pick up the features. This is * typically true of modules that rely on {@code Class.forName()} to dynamically instantiate classes from the * root classloader (e.g. JDBC drivers that instantiate via @{code DriverManager}). */</span> <span class="directive">public</span> <span class="keyword">default</span> <span class="type">boolean</span> requireRestart() { <span class="keyword">return</span> <span class="predefined-constant">false</span>; } <span class="comment">/** * Gets the list of all {@link Customizer} implementations to assign to a new {@link GremlinScriptEngine}. This is * the same as doing {@code getCustomizers(null)}. */</span> <span class="directive">public</span> <span class="keyword">default</span> Optional&lt;<span class="predefined-type">Customizer</span><span class="type">[]</span>&gt; getCustomizers(){ <span class="keyword">return</span> getCustomizers(<span class="predefined-constant">null</span>); } <span class="comment">/** * Gets the list of {@link Customizer} implementations to assign to a new {@link GremlinScriptEngine}. The * implementation should filter the returned {@code Customizers} according to the supplied name of the * Gremlin-enabled {@code ScriptEngine}. By providing a filter, {@code GremlinModule} developers can have the * ability to target specific {@code ScriptEngines}. * * @param scriptEngineName The name of the {@code ScriptEngine} or null to get all the available {@code Customizers} */</span> <span class="directive">public</span> Optional&lt;<span class="predefined-type">Customizer</span><span class="type">[]</span>&gt; getCustomizers(<span class="directive">final</span> <span class="predefined-type">String</span> scriptEngineName); }</code></pre> </div> </div> <div class="paragraph"> <p>The most simple plugin and the one most commonly implemented will likely be one that just provides a list of classes for import. This type of plugin is the easiest way for implementers of the TinkerPop Structure and Process APIs to make their implementations available to users. The TinkerGraph implementation has just such a plugin:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"> <span class="keyword">package</span> <span class="namespace">org.apache.tinkerpop.gremlin.tinkergraph.jsr223</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.AbstractGremlinPlugin</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.DefaultImportCustomizer</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerMapEmitter</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerMemory</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerMessenger</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerReduceEmitter</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerWorkerPool</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerEdge</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerElement</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraphVariables</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerHelper</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV1</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV2</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerProperty</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex</span>; <span class="keyword">import</span> <span class="include">org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertexProperty</span>; <span class="comment">/** * @author Stephen Mallette (http://stephen.genoprime.com) */</span> <span class="directive">public</span> <span class="directive">final</span> <span class="type">class</span> <span class="class">TinkerGraphGremlinPlugin</span> <span class="directive">extends</span> AbstractGremlinPlugin { <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> NAME = <span class="string"><span class="delimiter">&quot;</span><span class="content">tinkerpop.tinkergraph</span><span class="delimiter">&quot;</span></span>; <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> ImportCustomizer imports = DefaultImportCustomizer.build() .addClassImports(TinkerEdge.class, TinkerElement.class, TinkerFactory.class, TinkerGraph.class, TinkerGraphVariables.class, TinkerHelper.class, TinkerIoRegistryV1.class, TinkerIoRegistryV2.class, TinkerIoRegistryV3.class, TinkerProperty.class, TinkerVertex.class, TinkerVertexProperty.class, TinkerGraphComputer.class, TinkerGraphComputerView.class, TinkerMapEmitter.class, TinkerMemory.class, TinkerMessenger.class, TinkerReduceEmitter.class, TinkerWorkerPool.class).create(); <span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> TinkerGraphGremlinPlugin instance = <span class="keyword">new</span> TinkerGraphGremlinPlugin(); <span class="directive">public</span> TinkerGraphGremlinPlugin() { <span class="local-variable">super</span>(NAME, imports); } <span class="directive">public</span> <span class="directive">static</span> TinkerGraphGremlinPlugin instance() { <span class="keyword">return</span> instance; } }</code></pre> </div> </div> <div class="paragraph"> <p>This plugin extends from the abstract base class of <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/AbstractGremlinPlugin.html">AbstractGremlinPlugin</a> which provides some default implementations of the <code>GremlinPlugin</code> methods. It simply allows those who extend from it to be able to just supply the name of the module and a list of <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/Customizer.html">Customizer</a> instances to apply to the <code>GremlinScriptEngine</code>. In this case, the TinkerGraph plugin just needs an <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.html">ImportCustomizer</a> which describes the list of classes to import when the plugin is activated and applied to the <code>GremlinScriptEngine</code>.</p> </div> <div class="paragraph"> <p>The <code>ImportCustomizer</code> is just one of several provided <code>Customizer</code> implementations that can be used in conjunction with plugin development:</p> </div> <div class="ulist"> <ul> <li> <p><a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/BindingsCustomizer.html">BindingsCustomizer</a> - Inject a key/value pair into the global bindings of the <code>GremlinScriptEngine</code> instances</p> </li> <li> <p><a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/ImportCustomizer.html">ImportCustomizer</a> - Add imports to a <code>GremlinScriptEngine</code></p> </li> <li> <p><a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/ScriptCustomizer.html">ScriptCustomizer</a> - Execute a script on a <code>GremlinScriptEngine</code> at startup</p> </li> </ul> </div> <div class="paragraph"> <p>Individual <code>GremlinScriptEngine</code> instances may have their own <code>Customizer</code> instances that can be used only with that engine - e.g. <code>gremlin-groovy</code> has some that are specific to controlling the Groovy compiler configuration. Developing a new <code>Customizer</code> implementation is not really possible without changes to TinkerPop, as the framework is not designed to respond to external ones. The base <code>Customizer</code> implementations listed above should cover most needs.</p> </div> <div class="paragraph"> <p>A <code>GremlinPlugin</code> must support one of two instantiation models so that it can be instantiated from configuration files for use in various situations - e.g. Gremlin Server. The first option is to use a static initializer given a method with the following signature:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="directive">static</span> GremlinPlugin instance()</code></pre> </div> </div> <div class="paragraph"> <p>The limitation with this approach is that it does not provide a way to supply any configuration to the plugin so it tends to only be useful for fairly simplistic plugins. The more advanced approach is to provide a "builder" given a method with the following signature:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="directive">static</span> Builder build()</code></pre> </div> </div> <div class="paragraph"> <p>It doesn&#8217;t really matter what kind of class is returned from <code>build</code> so long as it follows a "Builder" pattern, where methods on that object return an instance of itself, so that builder methods can be chained together prior to calling a final <code>create</code> method as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> GremlinPlugin create()</code></pre> </div> </div> <div class="paragraph"> <p>Please see the <a href="https://tinkerpop.apache.org/javadocs/3.7.3/full/org/apache/tinkerpop/gremlin/jsr223/ImportGremlinPlugin.html">ImportGremlinPlugin</a> for an example of what implementing a <code>Builder</code> might look like in this context.</p> </div> <div class="paragraph"> <p>Note that the plugin provides a unique name for the plugin which follows a namespaced pattern as <em>namespace</em>.<em>plugin-name</em> (e.g. "tinkerpop.hadoop" - "tinkerpop" is the reserved namespace for TinkerPop maintained plugins).</p> </div> <div class="paragraph"> <p>For plugins that will work with Gremlin Console, there is one other step to follow to ensure that the <code>GremlinPlugin</code> will work there. The console loads <code>GremlinPlugin</code> instances via <a href="http://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html">ServiceLoader</a> and therefore need a resource file added to the jar file where the plugin exists. Add a file called <code>org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin</code> to <code>META-INF/services</code>. In the case of the TinkerGraph plugin above, that file will have this line in it:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java">org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin</code></pre> </div> </div> <div class="paragraph"> <p>Once the plugin is packaged, there are two ways to test it out:</p> </div> <div class="olist arabic"> <ol class="arabic"> <li> <p>Copy the jar and its dependencies to the Gremlin Console path and start it. It is preferrable that the plugin is copied to the <code>/ext/<em>plugin_name</em></code> directory.</p> </li> <li> <p>Start Gremlin Console and try the <code>:install</code> command: <code>:install com.company my-plugin 1.0.0</code>.</p> </li> </ol> </div> <div class="paragraph"> <p>In either case, once one of these two approaches is taken, the jars and their dependencies are available to the Console. The next step is to "activate" the plugin by doing <code>:plugin use my-plugin</code>, where "my-plugin" refers to the name of the plugin to activate.</p> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> When <code>:install</code> is used logging dependencies related to <a href="http://www.slf4j.org/">SLF4J</a> are filtered out so as not to introduce multiple logger bindings (which generates warning messages to the logs). </td> </tr> </table> </div> <div class="paragraph"> <p>Plugins can also tie into the <code>:remote</code> and <code>:submit</code> commands. Recall that a <code>:remote</code> represents a different context within which Gremlin is executed, when issued with <code>:submit</code>. It is encouraged to use this integration point when possible, as opposed to registering new commands that can otherwise follow the <code>:remote</code> and <code>:submit</code> pattern. To expose this integration point as part of a plugin, implement the <code>RemoteAcceptor</code> interface:</p> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> Be good to the users of plugins and prevent dependency conflicts. Maintaining a conflict free plugin is most easily done by using the <a href="http://maven.apache.org/enforcer/maven-enforcer-plugin/">Maven Enforcer Plugin</a>. </td> </tr> </table> </div> <div class="admonitionblock tip"> <table> <tr> <td class="icon"> <div class="title">Tip</div> </td> <td class="content"> Consider binding the plugin&#8217;s minor version to the TinkerPop minor version so that it&#8217;s easy for users to figure out plugin compatibility. Otherwise, clearly document a compatibility matrix for the plugin somewhere that users can find it. </td> </tr> </table> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="java"> <span class="keyword">package</span> <span class="namespace">org.apache.tinkerpop.gremlin.jsr223.console</span>; <span class="keyword">import</span> <span class="include">java.io.Closeable</span>; <span class="keyword">import</span> <span class="include">java.util.List</span>; <span class="keyword">import</span> <span class="include">java.util.Map</span>; <span class="comment">/** * The Gremlin Console supports the {@code :remote} and {@code :submit} commands which provide standardized ways * for plugins to provide &quot;remote connections&quot; to resources and a way to &quot;submit&quot; a command to those resources. * A &quot;remote connection&quot; does not necessarily have to be a remote server. It simply refers to a resource that is * external to the console. * &lt;p/&gt; * By implementing this interface and returning an instance of it through * {@link ConsoleCustomizer#getRemoteAcceptor(GremlinShellEnvironment)} a plugin can hook into those commands and * provide remoting features. * * @author Stephen Mallette (http://stephen.genoprime.com) */</span> <span class="directive">public</span> <span class="type">interface</span> <span class="class">RemoteAcceptor</span> <span class="directive">extends</span> <span class="predefined-type">Closeable</span> { <span class="directive">public</span> <span class="directive">static</span> <span class="directive">final</span> <span class="predefined-type">String</span> RESULT = <span class="string"><span class="delimiter">&quot;</span><span class="content">result</span><span class="delimiter">&quot;</span></span>; <span class="comment">/** * Gets called when {@code :remote} is used in conjunction with the &quot;connect&quot; option. It is up to the * implementation to decide how additional arguments on the line should be treated after &quot;connect&quot;. * * @return an object to display as output to the user * @throws RemoteException if there is a problem with connecting */</span> <span class="directive">public</span> <span class="predefined-type">Object</span> connect(<span class="directive">final</span> <span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; args) <span class="directive">throws</span> <span class="exception">RemoteException</span>; <span class="comment">/** * Gets called when {@code :remote} is used in conjunction with the {@code config} option. It is up to the * implementation to decide how additional arguments on the line should be treated after {@code config}. * * @return an object to display as output to the user * @throws RemoteException if there is a problem with configuration */</span> <span class="directive">public</span> <span class="predefined-type">Object</span> configure(<span class="directive">final</span> <span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; args) <span class="directive">throws</span> <span class="exception">RemoteException</span>; <span class="comment">/** * Gets called when {@code :submit} is executed. It is up to the implementation to decide how additional * arguments on the line should be treated after {@code :submit}. * * @return an object to display as output to the user * @throws RemoteException if there is a problem with submission */</span> <span class="directive">public</span> <span class="predefined-type">Object</span> submit(<span class="directive">final</span> <span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; args) <span class="directive">throws</span> <span class="exception">RemoteException</span>; <span class="comment">/** * If the {@code RemoteAcceptor} is used in the Gremlin Console, then this method might be called to determine * if it can be used in a fashion that supports the {@code :remote console} command. By default, this value is * set to {@code false}. * &lt;p/&gt; * A {@code RemoteAcceptor} should only return {@code true} for this method if it expects that all activities it * supports are executed through the {@code :submit} command. If the users interaction with the remote requires * working with both local and remote evaluation at the same time, it is likely best to keep this method return * {@code false}. A good example of this type of plugin would be the Gephi Plugin which uses {@code :remote config} * to configure a local {@code TraversalSource} to be used and expects calls to {@code :submit} for the same body * of analysis. */</span> <span class="directive">public</span> <span class="keyword">default</span> <span class="type">boolean</span> allowRemoteConsole() { <span class="keyword">return</span> <span class="predefined-constant">false</span>; } }</code></pre> </div> </div> <div class="paragraph"> <p>The <code>RemoteAcceptor</code> can be bound to a <code>GremlinPlugin</code> by adding a <code>ConsoleCustomizer</code> implementation to the list of <code>Customizer</code> instances that are returned from the <code>GremlinPlugin</code>. The <code>ConsoleCustomizer</code> will only be executed when in use with the Gremlin Console plugin host. Simply instantiate and return a <code>RemoteAcceptor</code> in the <code>ConsoleCustomizer.getRemoteAcceptor(GremlinShellEnvironment)</code> method. Generally speaking, each call to <code>getRemoteAcceptor(GremlinShellEnvironment)</code> should produce a new instance of a <code>RemoteAcceptor</code>.</p> </div> </div> </div> <h1 id="gremlin-semantics" class="sect0">Gremlin Semantics</h1> <div class="openblock partintro"> <div class="content"> <div class="paragraph"> <p><span class="image"><img src="../../images/tinkerpop-meeting-room.png" alt="tinkerpop meeting room"></span></p> </div> <div class="paragraph"> <p>This section provides details on Gremlin language operational semantics. Describing these semantics and reinforcing them with tests in the Gremlin test suite makes it easier for providers to implement the language and for the TinkerPop Community to have better consistency in their user experiences. While the general Gremlin test suite offers an integrated approach to testing Gremlin queries, the <code>@StepClassSemantics</code> oriented tests found <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features">here</a> are especially focused to the definitions found in this section. References to the location of specific tests can be found throughout the sub-sections below.</p> </div> </div> </div> <div class="sect1"> <h2 id="_types">Types</h2> <div class="sectionbody"> <div class="paragraph"> <p>The TinkerPop query execution runtime is aligned with Java primitives and handles the following primitive types:</p> </div> <div class="ulist"> <ul> <li> <p>Boolean</p> </li> <li> <p>Integer</p> <div class="ulist"> <ul> <li> <p>Byte (int8)</p> </li> <li> <p>Short (int16)</p> </li> <li> <p>Integer (int32)</p> </li> <li> <p>Long (int64)</p> </li> <li> <p>BigInteger</p> </li> </ul> </div> </li> <li> <p>Decimal</p> <div class="ulist"> <ul> <li> <p>Float (32-bit) (including +/-Infinity and NaN)</p> </li> <li> <p>Double (64-bit) (including +/-Infinity and NaN)</p> </li> <li> <p>BigDecimal</p> </li> </ul> </div> </li> <li> <p>String</p> </li> <li> <p>UUID</p> </li> <li> <p>Date</p> </li> <li> <p><code>nulltype</code></p> <div class="ulist"> <ul> <li> <p>Has only one value in its type space - the "undefined" value <code>null</code></p> </li> </ul> </div> </li> </ul> </div> <div class="admonitionblock note"> <table> <tr> <td class="icon"> <div class="title">Note</div> </td> <td class="content"> TinkerPop has a bit of a JVM-centric view of types as it was developed within that ecosystem. </td> </tr> </table> </div> <div class="paragraph"> <p>Graph providers may not support all of these types depending on the architecture and implementation. Therefore TinkerPop must provide a way for Graph providers to override the behavior while it has its own default behavior. Also when some types are not supported Graph providers needs to map unsupported types into supported types internally. This mapping can be done in either information-preserving manner or non-preserving manner. Graph providers must tell which mapping they support through <code>Graph.Features</code> as well as which types they support.</p> </div> <div class="ulist"> <ul> <li> <p>Which primitive types are supported</p> <div class="ulist"> <ul> <li> <p>Boolean, Integer, Float, String, UUID and Date</p> </li> <li> <p>TinkerPop by default supports all of them</p> </li> </ul> </div> </li> <li> <p>Which integer types are supported</p> <div class="ulist"> <ul> <li> <p>TinkerPop by default supports int8 (Byte), int16 (Short), int32 (Integer), int64 (Long) and BigInteger in Java</p> </li> </ul> </div> </li> <li> <p>Which float types are supported</p> <div class="ulist"> <ul> <li> <p>TinkerPop by default supports all as float, double, and BigDecimal in Java</p> </li> </ul> </div> </li> </ul> </div> <div class="paragraph"> <p>In addition to these, there are composite types as follows:</p> </div> <div class="ulist"> <ul> <li> <p>Graph Element</p> <div class="ulist"> <ul> <li> <p>Vertex</p> </li> <li> <p>Edge</p> </li> <li> <p>VertexProperty</p> </li> </ul> </div> </li> <li> <p>Property (edge properties and meta properties)</p> <div class="ulist"> <ul> <li> <p>(Key, Value) pair</p> </li> <li> <p>Key is String, Value is any of the primitive types defined above</p> </li> </ul> </div> </li> <li> <p>Path</p> </li> <li> <p>List</p> </li> <li> <p>Map</p> </li> <li> <p>Map.Entry</p> </li> <li> <p>Set</p> </li> </ul> </div> <div class="sect2"> <h3 id="_numeric_type_promotion">Numeric Type Promotion</h3> <div class="paragraph"> <p>TinkerPop performs type promotion a.k.a type casting for Numbers. Numbers are Byte, Short, Integer, Long, Float, Double, BigInteger, and BigDecimal. In general, numbers are compared using semantic equivalence, without regard for their specific type, e.g. <code>1 == 1.0</code>.</p> </div> <div class="paragraph"> <p>Numeric types are promoted as follows:</p> </div> <div class="ulist"> <ul> <li> <p>First determine whether to use floating point or not. If any numbers in the comparison are floating point then we convert all of them to floating point.</p> </li> <li> <p>Next determine the maximum bit size of the numerics being compared.</p> </li> <li> <p>If any floating point are present:</p> <div class="ulist"> <ul> <li> <p>If the maximum bit size is 32 (up to Integer/Float), we compare as Float</p> </li> <li> <p>If the maximum bit size is 64 (up to Long/Double), we compare as Double</p> </li> <li> <p>Otherwise we compare as BigDecimal</p> </li> </ul> </div> </li> <li> <p>If no floating point are present:</p> <div class="ulist"> <ul> <li> <p>If the maximum bit size is 8 we compare as Byte</p> </li> <li> <p>If the maximum bit size is 16 we compare as Short</p> </li> <li> <p>If the maximum bit size is 32 we compare as Integer</p> </li> <li> <p>If the maximum bit size is 64 we compare as Long</p> </li> <li> <p>Otherwise we compare as BigInteger</p> </li> </ul> </div> </li> </ul> </div> <div class="paragraph"> <p>BigDecimal and BigInteger may not be supported depending on the language and Storage, therefore the behavior of type casting for these two types can vary depending on a Graph provider.</p> </div> </div> </div> </div> <div class="sect1"> <h2 id="gremlin-semantics-concepts">Comparability, Equality, Orderability, and Equivalence</h2> <div class="sectionbody"> <div class="paragraph"> <p>This section of the document attempts to more clearly define the semantics for different types of value comparison within the Gremlin language and reference implementation. There are four concepts related to value comparison:</p> </div> <div class="paragraph"> <p><strong>Equality</strong></p> </div> <div class="paragraph"> <p>Equality semantics is used by the equality operators (<code>P.eq/neq</code>) and contains operators derived from them (<code>P.within/without</code>). It is also used for implicit <code>P.eq</code> comparisons, for example <code>g.V().has("age", 25)</code> - equality semantics are used to look up vertices by <code>age</code> when considering the value.</p> </div> <div class="paragraph"> <p><strong>Comparability</strong></p> </div> <div class="paragraph"> <p>Comparability semantics is used by the compare operators (<code>P.lt/lte/gt/gte</code>) and operators derived from them (<code>P.inside/outside/between</code>) and defines the semantics of how to compare two values.</p> </div> <div class="paragraph"> <p><strong>Orderability</strong></p> </div> <div class="paragraph"> <p>Orderability semantics defines how two values are compared in the context of an <code>order()</code> operation. These semantics have important differences from Comparability.</p> </div> <div class="paragraph"> <p><strong>Equivalence</strong></p> </div> <div class="paragraph"> <p>Equivalence semantics are slightly different from Equality and are used for operations such as <code>dedup()</code> and <code>group()</code>. Key differences include handling of numeric types and NaN.</p> </div> <div class="paragraph"> <p>Both Equality and Equivalence can be understood as complete, i.e. the result of equality and equivalence checks is always either <code>TRUE</code> or <code>FALSE</code> (in particular, it never returns <code>nulltype</code> or throws an exception). Similarly, Orderability can be also understood as complete - any two values can be compared without error for ordering purposes. Comparability semantics are not complete with respect to binary boolean semantics, and as such, Gremlin introduces a ternary boolean semantics for Comparability that includes a third boolean state - <code>ERROR</code>, with its own well-defined semantics.</p> </div> <div class="sect2"> <h3 id="_ternary_boolean_logics">Ternary Boolean Logics</h3> <div class="paragraph"> <p>When evaluating boolean value expressions, we sometimes encounter situations that cannot be proved as either <code>TRUE</code> or <code>FALSE</code>. Common <code>ERROR</code> cases are Comparability against <code>NaN</code>, cross-type Comparability (e.g. <code>String</code> vs <code>Numeric</code>), or other invalid arguments to other boolean value expressions.</p> </div> <div class="paragraph"> <p>Rather than throwing an exception and halting the traversal, we extend normal binary boolean logics and introduce a third boolean option - <code>ERROR</code>. How this <code>ERROR</code> result is handled is Graph provider dependent. For the reference implementation, <code>ERROR</code> is an internal representation only and will not be propagated back to the client as an exception - it will eventually hit a binary reduction operation and be reduced to <code>FALSE</code> (thus quietly filters the solution that produced the <code>ERROR</code>). Before that happens though, it will be treated as its own boolean value with its own semantics that can be used in other boolean value expressions, such as Connective predicates (<code>P.and/or</code>) and negation (<code>P.not</code>).</p> </div> <div class="sect3"> <h4 id="_ternary_boolean_semantics_for_and">Ternary Boolean Semantics for <code>AND</code></h4> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">A</th> <th class="tableblock halign-left valign-top">B</th> <th class="tableblock halign-left valign-top">AND</th> <th class="tableblock halign-left valign-top">Intuition</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE &amp;&amp; X == X</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE &amp;&amp; X == FALSE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X &amp;&amp; TRUE == X</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X &amp;&amp; FALSE == FALSE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X &amp;&amp; X == X</code></p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_ternary_boolean_semantics_for_or">Ternary Boolean Semantics for <code>OR</code></h4> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">A</th> <th class="tableblock halign-left valign-top">B</th> <th class="tableblock halign-left valign-top">OR</th> <th class="tableblock halign-left valign-top">Intuition</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE || X == TRUE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE || X == X</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X || TRUE == TRUE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X || FALSE == X</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>X || X == X</code></p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_ternary_boolean_semantics_for_not">Ternary Boolean Semantics for <code>NOT</code></h4> <div class="paragraph"> <p>The <code>NOT</code> predicate inverts <code>TRUE</code> and <code>FALSE</code>, respectively, but maintains <code>ERROR</code> values. The key idea is that, for an <code>ERROR</code> value, we can neither prove nor disprove the expression, and hence stick with <code>ERROR</code>.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 50%;"> <col style="width: 50%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Argument</th> <th class="tableblock halign-left valign-top">Result</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> </tr> </tbody> </table> </div> </div> <div class="sect2"> <h3 id="gremlin-semantics-equality-comparability">Equality and Comparability</h3> <div class="paragraph"> <p><a href="#Equality">Equality</a> and <a href="#Comparability">Comparability</a> can be understood to be semantically aligned with one another. As mentioned above, <a href="#Equality">Equality</a> is used for <code>P.eq/neq</code> (and derived predicates) and <a href="#Comparability">Comparability</a> is used for <code>P.lt/lte/gt/gte</code> (and derived predicates). If we define Comparability using a <code>compare()</code> function over <code>A</code> and <code>B</code> as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">If (A, B) are Comparable per Gremlin semantics, then: For A &lt; B, Comparability.compare(A, B) &lt; 0 For A &gt; B, Comparability.compare(A, B) &gt; 0 For A == B, Comparability.compare(A, B) == 0 If (A, B) not Comparable, then: Comparability.compare(A, B) =&gt; ERROR</code></pre> </div> </div> <div class="paragraph"> <p>Then we can define Equality using an <code>equals()</code> function over <code>A</code> and <code>B</code> that acts as a strict binary reduction of <code>Comparability.compare(A, B) == 0</code>:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">For any (A, B): Comparability.compare(A, B) == 0 implies Equality.equals(A, B) == TRUE Comparability.compare(A, B) &lt;&gt; 0 implies Equality.equals(A, B) == FALSE Comparability.compare(A, B) =&gt; ERROR implies Equality.equals(A, B) == FALSE</code></pre> </div> </div> <div class="paragraph"> <p>The following table illustrates how <a href="#Equality">Equality</a> and <a href="#Comparability">Comparability</a> operate under various classes of comparison:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> <col style="width: 25%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Class</th> <th class="tableblock halign-left valign-top">Arguments</th> <th class="tableblock halign-left valign-top"><a href="#Comparability">Comparability</a></th> <th class="tableblock halign-left valign-top"><a href="#Equality">Equality</a></th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparisons Involving <code>NaN</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>(NaN,X)</code><br></p> <p class="tableblock">where <code>X</code> = any value, including <code>NaN</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code><br></p> <p class="tableblock">Comparing <code>NaN</code> to anything (including itself) cannot be evaluated.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparisons Involving <code>null</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>(null,null)</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>compare() == 0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>(null, X)</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code><br></p> <p class="tableblock">Since <code>nulltype</code> is its own type, this falls under the umbrella of cross-type comparisons.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparisons within the same type family (i.e. String vs. String, Number vs. Number, etc.)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>(X, Y)</code><br></p> <p class="tableblock">where <code>X</code> and <code>Y</code> of same type</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Result of <code>compare()</code> depends on type semantics, defined below.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>TRUE</code> iff <code>compare() == 0</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparisons across types (i.e. String vs. Number)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>(X, Y)</code><br></p> <p class="tableblock">where <code>X</code> and <code>Y</code> of different type</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FALSE</code></p></td> </tr> </tbody> </table> <div class="sect3"> <h4 id="_equality_and_comparability_semantics_by_type">Equality and Comparability Semantics by Type</h4> <div class="paragraph"> <p>For <a href="#Equality">Equality</a> and <a href="#Comparability">Comparability</a> evaluation of values within the same type family, we define the semantics per type family as follows.</p> </div> <div class="sect4"> <h5 id="_number">Number</h5> <div class="paragraph"> <p>Numbers are compared using type promotion, described above. As such, <code>1 == 1.0</code>.</p> </div> <div class="paragraph"> <p>Edge cases:</p> </div> <div class="ulist"> <ul> <li> <p><code>-0.0 == 0.0 == +0.0</code></p> </li> <li> <p><code>+INF == +INF</code>, <code>-INF == -INF</code>, <code>-INF != +INF</code></p> <div class="ulist"> <ul> <li> <p><code>Float.±Infinity</code> and <code>Double.±Infinity</code> adhere to the same type promotion rules.</p> </li> </ul> </div> </li> <li> <p>As described above <code>NaN</code> is not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a> to any Number (including itself).</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Equality.feature">Equality Tests - Scenarios prefixed with "Primitives_Number_"</a></p> </div> </div> <div class="sect4"> <h5 id="_nulltype">nulltype</h5> <div class="paragraph"> <p>As described in the table above, <code>null == null</code>, but is not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a> to any non-<code>null</code> value.</p> </div> </div> <div class="sect4"> <h5 id="_boolean">Boolean</h5> <div class="paragraph"> <p>For Booleans, <code>TRUE == TRUE</code>, <code>FALSE == FALSE</code>, <code>TRUE != FALSE</code>, and <code>FALSE &lt; TRUE</code>.</p> </div> </div> <div class="sect4"> <h5 id="_string">String</h5> <div class="paragraph"> <p>We assume the common lexicographical order over unicode strings. <code>A</code> and <code>B</code> are compared lexicographically, and <code>A == B</code> if <code>A</code> and <code>B</code> are lexicographically equal.</p> </div> </div> <div class="sect4"> <h5 id="_uuid">UUID</h5> <div class="paragraph"> <p>UUID is evaluated based on its String representation. However, <code>UUID("b46d37e9-755c-477e-9ab6-44aabea51d50")</code> and the String <code>"b46d37e9-755c-477e-9ab6-44aabea51d50"</code> are not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a>.</p> </div> </div> <div class="sect4"> <h5 id="_date">Date</h5> <div class="paragraph"> <p>Dates are evaluated based on the numerical comparison of Unix Epoch time.</p> </div> </div> <div class="sect4"> <h5 id="_graph_element_vertex_edge_vertexproperty">Graph Element (Vertex / Edge / VertexProperty)</h5> <div class="paragraph"> <p>If they are the same type of Element, these are compared by the value of their <code>T.id</code> according to the semantics for the particular primitive type used for ids (implementation-specific). Elements of different types are not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a>.</p> </div> </div> <div class="sect4"> <h5 id="_property">Property</h5> <div class="paragraph"> <p>Properties are compared first by key (String semantics), then by value, according to the semantics for the particular primitive type of the value. Properties with values in different type families are not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a>.</p> </div> </div> <div class="sect4"> <h5 id="_list">List</h5> <div class="paragraph"> <p>Lists are compared pairwise, element-by-element, in their natural list order. For each element, if the pairs are <a href="#gremlin-semantics-equality-comparability">Equal</a>, we simply move on to the next element pair until we encounter a pair whose <code>Comparability.compare()</code> value is non-zero (<code>-1</code>, <code>1</code>, or <code>ERROR</code>), and we return that value. Lists can be evaluated for <a href="#gremlin-semantics-equality-comparability">Equality and Comparability</a> even if they contain multiple types of elements, so long as their elements are pairwise comparable per <a href="#gremlin-semantics-equality-comparability">Equality/Comparability</a> semantics. During this element by element comparison, if iteration <code>A</code> exhausts its elements before iteration <code>B</code> then <code>A &lt; B</code>, and vice-versa.</p> </div> <div class="paragraph"> <p>Empty lists are equal to other empty lists and less than non-empty lists.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top"><code>A</code></th> <th class="tableblock halign-left valign-top"><code>B</code></th> <th class="tableblock halign-left valign-top"><code>compare(A,B)</code></th> <th class="tableblock halign-left valign-top"><code>P</code></th> <th class="tableblock halign-left valign-top">Reason</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.eq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">empty lists are equal</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>-1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.lt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">empty &lt; non-empty</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.gt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">non-empty &gt; empty</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.eq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">pairwise equal</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,4]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>-1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.lt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">pairwise equal until last element: <code>3 &lt; 4</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3,4]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>-1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.lt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>A</code> exhausts first</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3,4]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2,3]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.gt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>B</code> exhausts first</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,2]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1.0,2.0]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.eq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">type promotion</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,"a"]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1,"b"]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>-1</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.lt</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">pairwise <a href="#Comparability">Comparable</a> and <code>"a" &lt; "b"</code></p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>[1]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>["a"]</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>ERROR</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.neq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">cross-type comparison</p></td> </tr> </tbody> </table> </div> <div class="sect4"> <h5 id="_path">Path</h5> <div class="paragraph"> <p><a href="#gremlin-semantics-equality-comparability">Equality and Comparability</a> semantics for <code>Paths</code> are similar to those for <code>Lists</code>, described above (though <code>Paths</code> and <code>Lists</code> are still of different types and thus not <a href="#Equality">Equal</a> and not <a href="#Comparability">Comparable</a>).</p> </div> </div> <div class="sect4"> <h5 id="_set">Set</h5> <div class="paragraph"> <p><code>Sets</code> are compared pairwise, element-by-element, in the same way as <code>Lists</code>, but they are compared in sorted order using <a href="#gremlin-semantics-orderability">Orderability</a> semantics to sort (described further below). We use <a href="#gremlin-semantics-orderability">Orderability</a> semantics for ordering so that <code>Sets</code> containing multiple element types can be properly sorted before being compared.</p> </div> <div class="paragraph"> <p>For example:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top"><code>A</code></th> <th class="tableblock halign-left valign-top"><code>B</code></th> <th class="tableblock halign-left valign-top"><code>compare(A,B)</code></th> <th class="tableblock halign-left valign-top"><code>P</code></th> <th class="tableblock halign-left valign-top">Reason</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>{1, 2}</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>{2, 1}</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.eq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">sort before compare</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>{1, "foo"}</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>{"foo", 1}</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>0</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>P.eq</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">we use <a href="#gremlin-semantics-orderability">Orderability</a> semantics to sort across types</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>Sets do introduce a bit of semantic stickiness, in that on the one hand they do respect type promotion semantics for Equality and Comparability:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">{1, 2} == {1.0, 2.0}</code></pre> </div> </div> <div class="paragraph"> <p>But on the other hand they also allow two elements that would be equal (and thus duplicates) according to type promotion:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">{1, 1.0, 2} is a valid set and != {1, 2}</code></pre> </div> </div> <div class="paragraph"> <p>We allow some "wiggle-room" in the implementation for providers to decide how to handle this logical inconsistency. The reference implementation allows for semantically equivalent numerics to appear in a set (e.g <code>{1, 1.0}</code>), while at the same time evaluating the same semantically equivalent numerics as equal during pairwise comparison across sets (e.g. <code>{1,2} == {1.0,2.0}</code>).</p> </div> </div> <div class="sect4"> <h5 id="_map">Map</h5> <div class="paragraph"> <p>'Map' semantics can be thought of as similar to <code>Set</code> semantics for the entry set the comprises the <code>Map</code>. So again, we compare pairwise, entry-by-entry, in the same way as <code>Lists</code>, and again, we first sort the entries using <a href="#gremlin-semantics-orderability">Orderability</a> semantics. Map entries are compared first by key, then by value using the <a href="#_equality_and_comparability_semantics_by_type">Equality and Comparability</a> semantics that apply to the specific type of key and value.</p> </div> <div class="paragraph"> <p>Maps semantics have the same logical inconsistency as set semantics, because of type promotion. Again, we leave room for providers to decide how to handle this in their implementation. The reference implementation allows for semantically equivalent keys to appear in a map (e.g. <code>1</code> and <code>1.0</code> can both be keys in the same map), but when comparing maps we treat pairwise entries with semantically equivalent keys as the same.</p> </div> </div> </div> </div> <div class="sect2"> <h3 id="gremlin-semantics-orderability">Orderability</h3> <div class="paragraph"> <p><a href="#gremlin-semantics-equality-comparability">Equality and Comparability</a> were described in depth in the sections above, and their semantics map to the <code>P</code> predicates. <a href="#Comparability">Comparability</a> in particular is limited to comparison of values within the same type family. Comparability is complete within a given type (except for <code>NaN</code>, which results in <code>ERROR</code> for any comparison), but returns <code>ERROR</code> for comparisons across types (e.g., an integer cannot be compared to a string).</p> </div> <div class="paragraph"> <p>Orderability semantics are very similar to Comparability for the most part, except that Orderability will never result in <code>ERROR</code> for comparison of any two values - even if two values are incomparable according to Comparability semantics we will still be able to determine their respective order. This allows for a total order across all Gremlin values. In the reference implementation, any step using <code>Order.asc</code> or <code>Order.desc</code> (e.g. OrderGlobalStep, OrderLocalStep) will follow these semantics.</p> </div> <div class="paragraph"> <p>To achieve this globally complete order, we need to address any cases in Comparability that produce a comparison <code>ERROR</code>, we must define a global order across type families, and we must provide semantics for ordering "unknown" values (for cases of in-process JVM implementations, like the TinkerGraph).</p> </div> <div class="paragraph"> <p>We define the type space, and the global order across the type space as follows:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">1. nulltype 2. Boolean 3. Number 4. Date 5. String 6. Vertex 7. Edge 8. VertexProperty 9. Property 10. Path 11. Set 12. List 13. Map 14. Unknown</code></pre> </div> </div> <div class="paragraph"> <p>Values in different type spaces will be ordered according to their priority (e.g. all Numbers &lt; all Strings).</p> </div> <div class="paragraph"> <p>Within a given type space, Orderability determines if two values are ordered at the same position or one value is positioned before or after the another. When the position is identical, which value comes first (in other words, whether it should perform stable sort) depends on graph providers' implementation.</p> </div> <div class="paragraph"> <p>To allow for this total ordering, we must also address the cases in <a href="#gremlin-semantics-equality-comparability">Comparability</a> that produce an comparison <code>ERROR</code>:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top"><code>ERROR</code> Scenario</th> <th class="tableblock halign-left valign-top">Comparability</th> <th class="tableblock halign-left valign-top">Orderability</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparison against <code>NaN</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>NaN</code> not comparable to anything, including itself.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>NaN</code> appears after <code>+Infinity</code> in the numeric type space.</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparison across types</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Cannot compare values of different types. This includes the <code>nulltype</code>.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Subject to a total type ordering where every value of type A appears before or after every value of Type B per the priorty list above.</p></td> </tr> </tbody> </table> <div class="sect3"> <h4 id="_key_differences_from_comparability">Key differences from Comparability</h4> <div class="paragraph"> <p>One key difference to note is that we use Orderability semantics to compare values within containers (<code>List</code>, <code>Set</code>, <code>Path</code>, <code>Map</code>, <code>Property</code>) rather than using Comparability semantics (i.e. Orderability all the way down).</p> </div> <div class="sect4"> <h5 id="_numeric_ordering">Numeric Ordering</h5> <div class="paragraph"> <p>Same as Comparability, except <code>NaN</code> is equivalent to <code>NaN</code> and is greater than all other Numbers, including <code>+Infinity</code>. Additionally, because of type promotion (<code>1</code> == <code>1.0</code>), numbers of the same value but of different numeric types will not have a stable sort order (<code>1</code> can appear either before or after <code>1.0</code>).</p> </div> </div> <div class="sect4"> <h5 id="_property_2">Property</h5> <div class="paragraph"> <p>Same as Comparability, except Orderability semantics are used for the property value.</p> </div> </div> <div class="sect4"> <h5 id="_iterables_path_list_set_map">Iterables (Path, List, Set, Map)</h5> <div class="paragraph"> <p>Same as Comparability, except Orderability semantics apply for the pairwise element-by-element comparisons.</p> </div> </div> <div class="sect4"> <h5 id="_unknown_types">Unknown Types</h5> <div class="paragraph"> <p>For Orderability semantics, we allow for the possibility of "unknown" types. If the "unknown" arguments are of the same type, we use <code>java.lang.Object#equals()</code> and <code>java.lang.Comparable</code> (if implemented) to determine their natural order. If the unknown arguments are of different types or do not define a natural order, we order first by Class, then by <code>Object.toString()</code>.</p> </div> </div> </div> </div> <div class="sect2"> <h3 id="gremlin-semantics-equivalence">Equivalence</h3> <div class="paragraph"> <p>Equivalence defines how TinkerPop deals with two values to be grouped or de-duplicated. Specifically it is necessary for the <code>dedup()</code> and <code>group()</code> steps in Gremlin.</p> </div> <div class="paragraph"> <p>For example:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="text">// deduplication needs equivalence over two property values gremlin&gt; g.V().dedup().by(&quot;name&quot;) // grouping by equivalence over two property values gremlin&gt; g.V().group().by(&quot;age&quot;)</code></pre> </div> </div> <div class="paragraph"> <p>Like Equality, Equivalence checks always return <code>true</code> or <code>false</code>, never <code>nulltype</code> or <code>error</code>, nor do they produce exceptions. For the most part Equivalence and Equality are the same, with the following key differences:</p> </div> <div class="ulist"> <ul> <li> <p>Equivalence ignores type promotion semantics, i.e. two values of different types (e.g. 2^^int vs. 2.0^^float) are always considered to be non-equivalent.</p> </li> <li> <p><code>NaN</code> Equivalence is the reverse of Equality: <code>NaN</code> is equivalent to <code>NaN</code> and not Equivalent to any other Number.</p> </li> </ul> </div> </div> <div class="sect2"> <h3 id="_further_reference">Further Reference</h3> <div class="sect3"> <h4 id="_mapping_for_p">Mapping for P</h4> <div class="paragraph"> <p>The following table maps the notions proposed above to the various <code>P</code> operators:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 50%;"> <col style="width: 50%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Predicate</th> <th class="tableblock halign-left valign-top">Concept</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.eq</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.neq</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.within</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.without</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.lt</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.gt</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.lte</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality, Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.gte</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality, Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.inside</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.outside</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Comparability</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">P.between</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Equality, Comparability</p></td> </tr> </tbody> </table> </div> <div class="sect3"> <h4 id="_see_also">See Also</h4> <div class="paragraph"> <p><a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Equality.feature">Equality Tests</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Comparability.feature">Comparability Tests</a></p> </div> </div> </div> </div> </div> <div class="sect1"> <h2 id="_steps">Steps</h2> <div class="sectionbody"> <div class="paragraph"> <p>While TinkerPop has a full test suite for validating functionality of Gremlin, tests alone aren&#8217;t always exhaustive or fully demonstrative of Gremlin step semantics. It is also hard to simply read the tests to understand exactly how a step is meant to behave. This section discusses the semantics for individual steps to help users and providers understand implementation expectations.</p> </div> <div class="sect2"> <h3 id="all-step">all()</h3> <div class="paragraph"> <p><strong>Description:</strong> Filters array data from the Traversal Stream if all of the array&#8217;s items match the supplied predicate.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>all(P predicate)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code>/<code>array</code>/<code>Iterable</code>/<code>Iterator</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code>/<code>array</code>/<code>Iterable</code>/<code>Iterator</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>predicate</code> - The predicate to use to test each value in the array data.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>Each value will be tested using the supplied predicate. Empty lists always pass through and null/non-list traversers will be filtered out of the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>A GremlinTypeErrorException will be thrown if one occurs and no other value evaluates to false.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#all-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="any-step">any()</h3> <div class="paragraph"> <p><strong>Description:</strong> Filters array data from the Traversal Stream if any of the array&#8217;s items match the supplied predicate.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>any(P predicate)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code>/<code>array</code>/<code>Iterable</code>/<code>Iterator</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code>/<code>array</code>/<code>Iterable</code>/<code>Iterator</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>predicate</code> - The predicate to use to test each value in the array data.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>Each value will be tested using the supplied predicate. Empty lists, null traversers, and non-list traversers will be filtered out of the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>A GremlinTypeErrorException will be thrown if one occurs and no other value evaluates to true.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#any-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="asDate-step">asDate()</h3> <div class="paragraph"> <p><strong>Description:</strong> Parse the value of incoming traverser as date. Supported ISO-8601 strings and Unix time numbers.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>asDate()</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p>Incoming date remains unchanged.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * If the incoming traverser is a non-String/Number/Date value then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsDateStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#asDate-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="asString-step">asString()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the value of incoming traverser as strings, or if <code>Scope.local</code> is specified, returns each element inside incoming list traverser as string.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>asString()</code> | <code>asString(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual traverser, casting all (except <code>null</code>) to string. The <code>local</code> scope will behave like <code>global</code> for everything except lists, where it will cast individual non-<code>null</code> elements inside the list into string and return a list of string instead.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> None</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#asString-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="call-step">call()</h3> <div class="paragraph"> <p><strong>Description:</strong> Provides support for provider-specific service calls.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>call()</code> | <code>call(String service, Map params)</code> | <code>call(String service, Traversal childTraversal)</code> | <code>call(String service, Map params, Traversal childTraversal)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>with()</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>service</code> - The name of the service call.</p> </li> <li> <p><code>params</code> - A collection of static parameters relevant to the particular service call. Keys and values can be any type currently supported by the Gremlin type system.</p> </li> <li> <p><code>childTraversal</code> - A traversal used to dynamically build at query time a collection of parameters relevant to the service call.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>with(key, value)</code> - Sets an additional static parameter relevant to the service call. Key and value can be any type currently supported by the Gremlin type system.</p> </li> <li> <p><code>with(key, Traversal)</code> - Sets an additional dynamic parameter relevant to the service call. Key can be any type currently supported by the Gremlin type system.</p> </li> </ul> </div> <div class="paragraph"> <p>How static and dynamic parameters are merged is a detail left to the provider implementation. The reference implementation (<code>CallStep</code>) uses effectively a "left to right" merge of the parameters - it starts with the static parameter <code>Map</code> argument, then merges in the parameters from the dynamic <code>Traversal</code> argument, then merges in each <code>with</code> modulation one by one in the order they appear.</p> </div> <div class="paragraph"> <p>Service calls in the reference implementation can be specified as <code>Start</code> (start of traversal), <code>Streaming</code> (mid-traversal flat map step), and <code>Barrier</code> (mid-traversal barrier step). Furthermore, the <code>Barrier</code> type can be all-at-once or with a maximum chunk size. A single service can support more than one of these modes, and if it does, must provide semantics for how to configure the mode at query time via parameters.</p> </div> <div class="paragraph"> <p>Providers using the reference implementation to support service call with need to provide a <code>ServiceFactory</code> for each named service that can create <code>Service</code> instances for execution during traversal. The <code>ServiceFactory</code> is a singleton that is registered with the <code>ServiceRegistry</code> located on the provider <code>Graph</code>. The <code>Service</code> instance is local to each traversal, although providers can choose to re-use instances across traversals provided there is no state.</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>Providers using the reference implementation can return <code>Traverser</code> output or raw value output - the <code>CallStep</code> will handle either case appropriately. In the case of a <code>Streaming</code> service, where there is exactly one input to each call, the reference implementation can preserve <code>Path</code> information by splitting the input <code>Traverser</code> when receiving raw output from the call. In the case of <code>Barrier</code> however, it is the responsiblity of the <code>Service</code> to preserve <code>Path</code> information by producing its own <code>Traversers</code> as output, since the <code>CallStep</code> cannot match input and ouput across a barrier. The ability to split input <code>Traversers</code> and generate output is provided by the reference implementation&#8217;s <code>ServiceCallContext</code> object, which is supplied to the <code>Service</code> during execution.</p> </div> <div class="paragraph"> <p>There are three execution methods in the reference implementation service call API:</p> </div> <div class="ulist"> <ul> <li> <p><code>execute(ServiceCallContext, Map)</code> - execute a service call to start a traversal</p> </li> <li> <p><code>execute(ServiceCallContext, Traverser, Map)</code> - execute a service call mid-traversal streaming (one input)</p> </li> <li> <p><code>execute(ServiceCallContext, TraverserSet, Map)</code> - execute a service call mid-traversal barrier</p> </li> </ul> </div> <div class="paragraph"> <p>The Map is the merged collection of all static and dynamic parameters. In the case of <code>Barrier</code> execution, notice that there is one <code>Map</code> for many input. Since the <code>call()</code> API support dynamic parameters, this implies that all input must reduce to the same set of parameters for <code>Barrier</code> execution. In the reference implementation, if more than one parameter set is detected, this will cause an execution and the traversal will halt. Providers that implement their own version of a call operation may decide on other strategies to handle this case - for example it may be sensible to group traversers by Map in the case where multiple parameter sets are detected.</p> </div> <div class="paragraph"> <p>The no-arg version of the <code>call()</code> API is meant to be a directory service and should only be used to start a traversal. The reference implementation provides a default version, with will produce a list of service names or a service description if run with <code>verbose=true</code>. Providers using the own implementation of the call operation must provide their own directory listing service with the service name <code>"--list"</code>.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If a named service does not support the execution mode implied by the traversal, for example, using a <code>Streaming</code> or <code>Barrier</code> step as a traversal source, this will result in an <code>UnsupportedOperationException</code>.</p> </li> <li> <p>As mentioned above, dynamic property parameters (<code>Traversals</code>) that reduce to more than one property set for a chunk of input is not supported in the reference implementation and will result in an <code>UnsupportedOperationException</code>.</p> </li> <li> <p>Use of the reference implementation&#8217;s built-in directory service - <code>call()</code> or <code>call("--list")</code> - mid-traversal will result in an <code>UnsupportedOperationException</code>.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CallStep.java">CallStep</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/Service.java">Service</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/service/ServiceRegistry.java">ServiceRegistry</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#call-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="combine-step">combine()</h3> <div class="paragraph"> <p><strong>Description:</strong> Appends one list to the other and returns the result to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>combine(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an Iterable or an array) or a traversal that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>A list is returned after the combine operation is applied so duplicates are allowed. <code>Merge</code> can be used instead if duplicates aren&#8217;t wanted. This step only applies to list types which means that non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CombineStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#combine-step">reference</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#merge-step">merge() reference</a></p> </div> </div> <div class="sect2"> <h3 id="concat-step">concat()</h3> <div class="paragraph"> <p><strong>Description:</strong> Concatenates the incoming String traverser with the input String arguments, and return the joined String.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>concat()</code> | <code>concat(String&#8230;&#8203; concatStrings)</code> | <code>concat(Traversal concatTraveral, Traversal&#8230;&#8203; otherConcatTraverals)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>concatStrings</code> - Varargs of <code>String</code>. If one or more String values are provided, they will be concatenated together with the incoming traverser. If no argument is provided, the String value from the incoming traverser is returned.</p> </li> <li> <p><code>concatTraveral</code> - A <code>Traversal</code> whose must value resolve to a <code>String</code>. The first result returned from the traversal will be concatenated with the incoming traverser.</p> </li> <li> <p><code>otherConcatTraverals</code> - Varargs of <code>Traversal</code>. Each <code>Traversal</code> value must resolve to a <code>String</code>. The first result returned from each traversal will be concatenated with the incoming traverser and the previous traversal arguments.</p> </li> </ul> </div> <div class="paragraph"> <p>Any <code>null</code> String values will be skipped when concatenated with non-<code>null</code> String values. If two <code>null</code> value are concatenated, the <code>null</code> value will be propagated and returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConcatStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#concat-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="dateAdd-step">dateAdd()</h3> <div class="paragraph"> <p><strong>Description:</strong> Increase value of input Date.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>dateAdd(DT dateToken, Integer value)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Date</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Date</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>dateToken</code> - Date token enum. Supported values <code>second</code>, <code>minute</code>, <code>hour</code>, <code>day</code>.</p> </li> <li> <p><code>value</code> - The number of units, specified by the DT Token, to add to the incoming values. May be negative for subtraction.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser is a non-Date value then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DateAddStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#dateAdd-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="dateDiff-step">dateDiff()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the difference between two Dates in epoch time.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>dateDiff(Date value)</code> | <code>dateDiff(Traversal dateTraversal)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Date</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Date</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>value</code> - Date for subtraction.</p> </li> <li> <p><code>dateTraversal</code> - The <code>Traversal</code> value must resolve to a <code>Date</code>. The first result returned from the traversal will be subtracted with the incoming traverser.</p> </li> </ul> </div> <div class="paragraph"> <p>If argument resolves as <code>null</code> then incoming date will not be changed.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser is a non-Date value then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DateDiffStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#dateDiff-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="dedup-step">dedup()</h3> <div class="paragraph"> <p><strong>Description:</strong> Removes repeatedly seen results from the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>dedup()</code> | <code>dedup(String&#8230;&#8203; labels)</code> | <code>dedup(Scope scope, String&#8230;&#8203; labels)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>by()</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the scope in which <code>dedup</code> is applied. The <code>global</code> scope will drop duplicate values across the global stream of traversers. The <code>local</code> scope operates at the individual traverser level, and will remove duplicate values from within a collection.</p> </li> <li> <p><code>labels</code> - If <code>dedup()</code> is provided a list of labels, then it will ensure that the de-duplication is not with respect to the current traverser object, but to the path history of the traverser.</p> </li> </ul> </div> <div class="paragraph"> <p>For Example:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="groovy">g.V().as(<span class="string"><span class="delimiter">'</span><span class="content">a</span><span class="delimiter">'</span></span>).out(<span class="string"><span class="delimiter">'</span><span class="content">created</span><span class="delimiter">'</span></span>).as(<span class="string"><span class="delimiter">'</span><span class="content">b</span><span class="delimiter">'</span></span>).in(<span class="string"><span class="delimiter">'</span><span class="content">created</span><span class="delimiter">'</span></span>).as(<span class="string"><span class="delimiter">'</span><span class="content">c</span><span class="delimiter">'</span></span>). dedup(<span class="string"><span class="delimiter">'</span><span class="content">a</span><span class="delimiter">'</span></span>,<span class="string"><span class="delimiter">'</span><span class="content">b</span><span class="delimiter">'</span></span>).select(<span class="string"><span class="delimiter">'</span><span class="content">a</span><span class="delimiter">'</span></span>,<span class="string"><span class="delimiter">'</span><span class="content">b</span><span class="delimiter">'</span></span>,<span class="string"><span class="delimiter">'</span><span class="content">c</span><span class="delimiter">'</span></span>)</code></pre> </div> </div> <div class="paragraph"> <p>will filter out any such <code>a</code> and <code>b</code> pairs which have previously been seen.</p> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>by()</code> - Performs dedup according to the property specified in the by modulation. For example: <code>g.V().dedup().by("name")</code> will filter out vertices with duplicate names.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="ulist"> <ul> <li> <p>There is no guarantee that ordering of results will be preserved across a dedup step.</p> </li> <li> <p>There is no guarantee which element is selected as the survivor when filtering duplicates.</p> </li> </ul> </div> <div class="paragraph"> <p>For example, given a graph with the following three vertices:</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 50%;"> <col style="width: 50%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">name</th> <th class="tableblock halign-left valign-top">age</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Alex</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">38</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Bob</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">45</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Chloe</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">38</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>and the traversal of:</p> </div> <div class="listingblock"> <div class="content"> <pre class="CodeRay highlight"><code data-lang="groovy">g.V().order().by(<span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>, Order.asc). dedup().by(<span class="string"><span class="delimiter">&quot;</span><span class="content">age</span><span class="delimiter">&quot;</span></span>).values(<span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>)</code></pre> </div> </div> <div class="paragraph"> <p>can return any of:</p> </div> <div class="paragraph"> <p><code>["Alex", "Bob"]</code>, <code>["Bob", "Alex"]</code>, <code>["Bob", "Chloe"]</code>, or <code>["Chloe", "Bob"]</code></p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/DedupGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DedupLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#dedup-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="difference-step">difference()</h3> <div class="paragraph"> <p><strong>Description:</strong> Adds the difference of two lists to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>difference(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Set</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an Iterable or an array) or a <code>Traversal</code> that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>Set difference (<code>A-B</code>) is an ordered operation. The incoming traverser is treated as <code>A</code> and the provided argument is treated as <code>B</code>. A set is returned after the difference operation is applied so there won&#8217;t be duplicates. This step only applies to list types which means that non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DifferenceStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#difference-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="disjunct-step">disjunct()</h3> <div class="paragraph"> <p><strong>Description:</strong> Adds the disjunct set to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>disjunct(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Set</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an Iterable or an array) or a <code>Traversal</code> that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>A set is returned after the disjunct operation is applied so there won&#8217;t be duplicates. This step only applies to list types which means that non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/DisjunctStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#disjunct-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="element-step">element()</h3> <div class="paragraph"> <p><strong>Description:</strong> Traverse from <code>Property</code> to its <code>Element</code>.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>element()</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Property</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Element</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> </div> <div class="sect2"> <h3 id="format-step">format()</h3> <div class="paragraph"> <p><strong>Description:</strong> a mid-traversal step which will handle result formatting to string values.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>format(String formatString)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>by()</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>any</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>formatString</code> - Variables can be represented with <code>%{variable_name}</code> notation. Positional arguments can be used as <code>%{_}</code> token. Can be used multiple times. The variable values are used in the order that the first one will be found: Element properties, then Scope values. If value for some variable was not found, then the result is filtered out.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>by()</code> - Used to inject positional argument. For example: <code>g.V().format("%{name} has %{_} connections").by(bothE().count())</code>.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/FormatStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#format-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="length-step">length()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the length of the incoming string or list, if <code>Scope.local</code> is specified, returns the length of each string elements inside incoming list traverser.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>length()</code> | <code>length(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Integer</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LengthGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LengthLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#length-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="intersect-step">intersect()</h3> <div class="paragraph"> <p><strong>Description:</strong> Adds the intersection to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>intersect(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Set</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an Iterable or an array) or a <code>Traversal</code> that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>A set is returned after the intersect operation is applied so there won&#8217;t be duplicates. This step only applies to list types which means that non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#intersect-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="conjoin-step">conjoin()</h3> <div class="paragraph"> <p><strong>Description:</strong> Joins every element in a list together into a String.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>conjoin(String delimiter)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>delimiter</code> - A delimiter to use to join the elements together. Can&#8217;t be null.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>Every element in the list (except <code>null</code>) is converted to a String. Null values are ignored. The delimiter is inserted between neighboring elements to form the final result. This step only applies to list types which means that non-iterable types (including <code>null</code>) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#conjoin-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="lTrim-step">lTrim()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a string with leading whitespace removed.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>lTrim()</code> | <code>lTrim(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/LTrimLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#lTrim-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="merge-step">merge()</h3> <div class="paragraph"> <p><strong>Description:</strong> Adds the union of two sets (or two maps) to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>merge(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code>/<code>Map</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Set</code>/<code>Map</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an <code>Iterable</code> or an array), a <code>Map</code>, or a <code>Traversal</code> that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>For iterable types, a set is returned after the merge operation is applied so there won&#8217;t be duplicates. For maps, if both maps contain the same key then the value yielded from the argument will be the value put into the merged map. This step only applies to list types or maps which means that other non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) or map then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) or map then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#merge-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="merge-e-step">mergeE()</h3> <div class="paragraph"> <p><strong>Description:</strong> Provides upsert-like functionality for edges.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>mergeE()</code> | <code>mergeE(Map searchCreate)</code> | <code>mergeE(Traversal searchCreate)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>option()</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Map</code>/<code>Vertex</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Edge</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>searchCreate</code> - A <code>Map</code> used to match an <code>Edge</code> and if not found will be the default set of data to create the new one.</p> </li> <li> <p><code>onCreate</code> - A <code>Map</code> used to specify additional existence criteria and/or properties not already specified in <code>searchCreate</code>.</p> </li> <li> <p><code>onMatch</code> - A <code>Map</code> used to update the <code>Edge</code> that is found using the <code>searchCreate</code> criteria.</p> </li> <li> <p><code>outV</code> - A <code>Vertex</code> that will be late-bound into the <code>searchCreate</code> and <code>onCreate</code> <code>Maps</code> for the <code>Direction.OUT</code> key, or else another <code>Map</code> used to search for that <code>Vertex</code></p> </li> <li> <p><code>inV</code> - A <code>Vertex</code> that will be late-bound into the <code>searchCreate</code> and <code>onCreate</code> <code>Maps</code> for the <code>Direction.IN</code> key, or else another <code>Map</code> used to search for that <code>Vertex</code></p> </li> </ul> </div> <div class="paragraph"> <p>The <code>searchCreate</code> and <code>onCreate</code> <code>Map</code> instances must consist of any combination of:</p> </div> <div class="ulist"> <ul> <li> <p><code>T</code> - <code>id</code>, <code>label</code></p> </li> <li> <p><code>Direction</code> - <code>IN</code> or <code>to</code>, <code>OUT</code> or <code>from</code></p> </li> <li> <p>Arbitrary <code>String</code> keys (which are assumed to be vertex properties).</p> </li> </ul> </div> <div class="paragraph"> <p>The <code>onMatch</code> <code>Map</code> instance only allows for <code>String</code> keys as the <code>id</code> and <code>label</code> of a <code>Vertex</code> are immutable as are the incident vertices. Values for these valid keys that are <code>null</code> will be treated according to the semantics of the <code>addE()</code> step.</p> </div> <div class="paragraph"> <p>The <code>Map</code> that is used as the argument for <code>searchCreate</code> may be assigned from the incoming <code>Traverser</code> for the no-arg <code>mergeE()</code>. If <code>mergeE(Map)</code> is used, then it will override the incoming <code>Traverser</code>. If <code>mergeE(Traversal)</code> is used, the <code>Traversal</code> argument must resolve to a <code>Map</code> and it would also override the incoming Traverser. The <code>onCreate</code> and <code>onMatch</code> arguments are assigned via modulation as described below.</p> </div> <div class="paragraph"> <p>If <code>onMatch</code> is triggered the <code>Traverser</code> becomes the matched <code>Edge</code>, but the traversal still must return a <code>Map</code> instance to be applied. <code>Null</code> is considered semantically equivalent to an empty <code>Map</code>.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 50%;"> <col style="width: 50%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Event</th> <th class="tableblock halign-left valign-top">Empty <code>Map</code> (or Null)</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Search</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Matches all edges</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Create</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">New edge with defaults</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Update</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">No update to matched edge</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>If <code>T.id</code> is used for <code>searchCreate</code> or <code>onCreate</code>, it may be ignored for edge creation if the <code>Graph</code> does not support user supplied identifiers. <code>onCreate</code> inherits from <code>searchCreate</code> - values for <code>T.id</code>, <code>T.label</code>, and <code>Direction.OUT/IN</code> do not need to be specified twice. Additionally, <code>onCreate</code> cannot override values in <code>searchCreate</code> (i.e. <code>if (exists(x)) return(x) else create(y)</code> is not supported).</p> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>option(Merge, Map)</code> - Sets the <code>onCreate</code> or <code>onMatch</code> arguments directly.</p> </li> <li> <p><code>option(Merge, Traversal)</code> - Sets the <code>onCreate</code> or <code>onMatch</code> arguments dynamically where the <code>Traversal</code> must resolve to a <code>Map</code>.</p> </li> <li> <p><code>option(Merge.outV/inV)</code> can also accept a <code>Traversal</code> that resolves to a <code>Vertex</code>, allowing <code>mergeE</code> to be combined with <code>mergeV</code> via a <code>select</code> operation.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>Map</code> arguments are validated for their keys resulting in exception if they do not meet requirements defined above.</p> </li> <li> <p>Use of <code>T.label</code> should always have a value that is a <code>String</code>.</p> </li> <li> <p>If <code>T.id</code>, <code>T.label</code>, and/or <code>Direction.IN/OUT</code> are specified in <code>searchCreate</code>, they cannot be overriden in <code>onCreate</code>.</p> </li> <li> <p>For late binding of the from and to vertices, <code>Direction.OUT</code> must be set to <code>Merge.outV</code> and <code>Direction.IN</code> must be set to <code>Merge.inV</code>. Other combinations are not allowed and will result in exception.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>mergeE()</code> (i.e. the zero-arg overload) can only be used mid-traversal. It is not a start step.</p> </li> <li> <p>As is common to Gremlin, it is expected that <code>Traversal</code> arguments may utilize <code>sideEffect()</code> steps.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#mergee-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="merge-v-step">mergeV()</h3> <div class="paragraph"> <p><strong>Description:</strong> Provides upsert-like functionality for vertices.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>mergeV()</code> | <code>mergeV(Map searchCreate)</code> | <code>mergeV(Traversal searchCreate)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>option()</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Map</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Vertex</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>searchCreate</code> - A <code>Map</code> used to match a <code>Vertex</code> and if not found will be the default set of data to create the new one.</p> </li> <li> <p><code>onCreate</code> - A <code>Map</code> used to specify additional existence criteria and/or properties not already specified in <code>searchCreate</code>.</p> </li> <li> <p><code>onMatch</code> - A <code>Map</code> used to update the <code>Vertex</code> that is found using the <code>searchCreate</code> criteria.</p> </li> </ul> </div> <div class="paragraph"> <p>The <code>searchCreate</code> and <code>onCreate</code> <code>Map</code> instances must consists of any combination of <code>T.id</code>, <code>T.label</code>, or arbitrary <code>String</code> keys (which are assumed to be vertex properties). The <code>onMatch</code> <code>Map</code> instance only allows for <code>String</code> keys as the <code>id</code> and <code>label</code> of a <code>Vertex</code> are immutable. <code>null</code> Values for these valid keys are not allowed.</p> </div> <div class="paragraph"> <p>The <code>Map</code> that is used as the argument for <code>searchCreate</code> may be assigned from the incoming <code>Traverser</code> for the no-arg <code>mergeV()</code>. If <code>mergeV(Map)</code> is used, then it will override the incoming <code>Traverser</code>. If <code>mergeV(Traversal)</code> is used, the <code>Traversal</code> argument must resolve to a <code>Map</code> and it would also override the incoming Traverser. The <code>onCreate</code> and <code>onMatch</code> arguments are assigned via modulation as described below.</p> </div> <div class="paragraph"> <p>If <code>onMatch</code> is triggered the <code>Traverser</code> becomes the matched <code>Vertex</code>, but the traversal still must return a <code>Map</code> instance to be applied. <code>Null</code> is considered semantically equivalent to an empty <code>Map</code>.</p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 50%;"> <col style="width: 50%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Event</th> <th class="tableblock halign-left valign-top">Empty <code>Map</code> (or Null)</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Search</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Matches all vertices</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Create</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">New vertex with defaults</p></td> </tr> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">Update</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">No update to matched vertex</p></td> </tr> </tbody> </table> <div class="paragraph"> <p>If <code>T.id</code> is used for <code>searchCreate</code> or <code>onCreate</code>, it may be ignored for vertex creation if the <code>Graph</code> does not support user supplied identifiers. <code>onCreate</code> inherits from <code>searchCreate</code> - values for <code>T.id</code>, <code>T.label</code> do not need to be specified twice. Additionally, <code>onCreate</code> cannot override values in <code>searchCreate</code> (i.e. <code>if (exists(x)) return(x) else create(y)</code> is not supported).</p> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>option(Merge, Map)</code> - Sets the <code>onCreate</code> or <code>onMatch</code> arguments directly.</p> </li> <li> <p><code>option(Merge, Traversal)</code> - Sets the <code>onCreate</code> or <code>onMatch</code> arguments dynamically where the <code>Traversal</code> must resolve to a <code>Map</code>.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>Map</code> arguments are validated for their keys resulting in exception if they do not meet requirements defined above.</p> </li> <li> <p>Use of <code>T.label</code> should always have a value that is a <code>String</code>.</p> </li> <li> <p>If <code>T.id</code> and/or <code>T.label</code> are specified in <code>searchCreate</code>, they cannot be overriden in <code>onCreate</code>.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>mergeV()</code> (i.e. the zero-arg overload) can only be used mid-traversal. It is not a start step.</p> </li> <li> <p>As is common to Gremlin, it is expected that <code>Traversal</code> arguments may utilize <code>sideEffect()</code> steps.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#mergev-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="product-step">product()</h3> <div class="paragraph"> <p><strong>Description:</strong> Adds the cartesian product to the Traversal Stream.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>product(Object values)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List(List)</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>values</code> - A list of items (as an <code>Iterable</code> or an array) or a <code>Traversal</code> that will produce a list of items.</p> </li> </ul> </div> <div class="paragraph"> <p><strong>Modulation:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p><strong>Considerations:</strong></p> </div> <div class="paragraph"> <p>A list of lists is returned after the product operation is applied with the inner list being a result pair. This step only applies to list types which means that non-iterable types (including null) will cause exceptions to be thrown.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser isn&#8217;t a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> <li> <p>If the argument doesn&#8217;t resolve to a list (array or Iterable) then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ProductStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#product-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="replace-step">replace()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a string with the specified characters in the original string replaced with the new characters.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>replace(String oldChar, String newChar)</code> | <code>replace(Scope scope, String oldChar, String newChar)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>oldChar</code> - The string character(s) in the original string to be replaced. Nullable, a null input will be a no-op and the original string will be returned</p> </li> <li> <p><code>newChar</code> - The string character(s) to replace with. Nullable, a null input will be a no-op and the original string will be returned</p> </li> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReplaceLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#replace-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="reverse-step">reverse()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the reverse of the incoming traverser</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>reverse()</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Object</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Object</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="paragraph"> <p>None</p> </div> <div class="paragraph"> <p>The behavior of reverse depends on the type of the incoming traverser. If the traverser is a string, then the string is reversed. If the traverser is iterable (Iterable, Iterator, or an array) then a list containing the items in reverse order are returned. All other types (including null) are not processed and are returned unmodified.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong></p> </div> <div class="ulist"> <ul> <li> <p>If the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown.</p> </li> </ul> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ReverseStep.java">source</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#reverse-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="rTrim-step">rTrim()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a string with trailing whitespace removed.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>rTrim()</code> | <code>rTrim(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/RTrimLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#rTrim-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="split-step">split()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a list of strings created by splitting the incoming string traverser around the matches of the given separator.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>split(String separator)</code> | <code>split(Scope scope, String separator)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>separator</code> - The string character(s) used as delimiter to split the input string. Nullable, a null separator will split on whitespaces.</p> </li> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SplitLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#split-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="substring-step">substring()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a substring of the incoming string traverser with a 0-based start index (inclusive) and end index (exclusive).</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>substring(int startIndex, int endIndex)</code> | <code>substring(Scope scope, int startIndex, int endIndex)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>startIndex</code> - The start index, 0 based. If the start index is negative then it will begin at the specified index counted from the end of the string, or 0 if it exceeds the string length.</p> </li> <li> <p><code>endIndex</code> - The end index, 0 based. Optional, if it is not specific then all remaining characters will be returned. End index &leq; start index will return the empty string.</p> </li> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/SubstringLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#substring-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="toLower-step">toLower()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the lowercase representation of incoming string traverser, or if <code>Scope.local</code> is specified, returns the lowercase representation of each string elements inside incoming list traverser.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>toLower()</code> | <code>toLower(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ToLowerGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ToLowerLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#toLower-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="toUpper-step">toUpper()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns the uppercase representation of incoming string traverser, or if <code>Scope.local</code> is specified, returns the uppercase representation of each string elements inside incoming list traverser.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>toUpper()</code> | <code>toUpper(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ToUpperGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ToUpperLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#toUpper-step">reference</a></p> </div> </div> <div class="sect2"> <h3 id="trim-step">trim()</h3> <div class="paragraph"> <p><strong>Description:</strong> Returns a string with leading and trailing whitespace removed.</p> </div> <div class="paragraph"> <p><strong>Syntax:</strong> <code>trim()</code> | <code>trim(Scope scope)</code></p> </div> <table class="tableblock frame-all grid-all stretch"> <colgroup> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> <col style="width: 20%;"> </colgroup> <thead> <tr> <th class="tableblock halign-left valign-top">Start Step</th> <th class="tableblock halign-left valign-top">Mid Step</th> <th class="tableblock halign-left valign-top">Modulated</th> <th class="tableblock halign-left valign-top">Domain</th> <th class="tableblock halign-left valign-top">Range</th> </tr> </thead> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Y</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">N</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>array</code>/<code>Iterable</code></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code>/<code>List</code></p></td> </tr> </tbody> </table> <div class="paragraph"> <p><strong>Arguments:</strong></p> </div> <div class="ulist"> <ul> <li> <p><code>scope</code> - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers. The <code>global</code> scope will operate on individual string traverser. The <code>local</code> scope will operate on list traverser with string elements inside.</p> </li> </ul> </div> <div class="paragraph"> <p>Null values from the incoming traverser are not processed and remain as null when returned.</p> </div> <div class="paragraph"> <p><strong>Exceptions</strong> * For <code>Scope.global</code> or parameterless function calls, if the incoming traverser is a non-String value then an <code>IllegalArgumentException</code> will be thrown. * For <code>Scope.local</code>, if the incoming traverser is not a string or a list of strings then an <code>IllegalArgumentException</code> will be thrown.</p> </div> <div class="paragraph"> <p>See: <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimGlobalStep.java">source</a>, <a href="https://github.com/apache/tinkerpop/tree/3.7.3/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TrimLocalStep.java">source (local)</a>, <a href="https://tinkerpop.apache.org/docs/3.7.3/reference/#trim-step">reference</a></p> </div> </div> </div> </div> <h1 id="policies" class="sect0">Policies</h1> <div class="openblock partintro"> <div class="content"> <span class="image"><img src="../../images/tinkerpop-conference.png" alt="tinkerpop conference"></span> </div> </div> <div class="sect1"> <h2 id="policy-listing">Provider Listing Policy</h2> <div class="sectionbody"> <div class="paragraph"> <p>TinkerPop has two web site sections that help the community find TinkerPop-enabled providers, libraries and tools. There is the <a href="https://tinkerpop.apache.org/providers.html">Providers</a> page and the <a href="https://tinkerpop.apache.org/community.html">Community</a> page. The Providers page lists graph systems that are TinkerPop-enabled. The Community page lists libraries and tools that are designed to work with TinkerPop providers and include things like drivers, object-graph mappers, visualization applications and other similar tools.</p> </div> <div class="paragraph"> <p>To be listed in either page a project should meet the following requirements:</p> </div> <div class="ulist"> <ul> <li> <p>The project must be either a TinkerPop-enabled graph system, a Gremlin language variant/compiler, a Gremlin language driver, or a TinkerPop-enabled middleware tool.</p> </li> <li> <p>The project must have a public URL that can be referenced by Apache TinkerPop.</p> </li> <li> <p>The project must have at least one release.</p> </li> <li> <p>The project must be actively developed/maintained to a current or previous "y" version of Apache TinkerPop (3.y.z).</p> </li> <li> <p>The project must have some documentation and that documentation must make explicit its usage of Apache TinkerPop and its version compatibility requirements.</p> </li> </ul> </div> <div class="paragraph"> <p>Note that the Apache Software Foundation&#8217;s <a href="https://www.apache.org/foundation/marks/linking">linking policy</a> supersede those stipulated by Apache TinkerPop. All things considered, if your project meets the requirements, please email Apache TinkerPop&#8217;s <a href="https://lists.apache.org/list.html?dev@tinkerpop.apache.org">developer mailing list</a> requesting that your project be added to a listing.</p> </div> </div> </div> <div class="sect1"> <h2 id="policy-graphics">Graphic Usage Policy</h2> <div class="sectionbody"> <div class="paragraph"> <p>Apache TinkerPop has a plethora of graphics that the community can use. There are four categories of graphics. These categories and their respective policies are presented below. If you are unsure of the category of a particular graphic, please ask on our <a href="https://lists.apache.org/list.html?dev@tinkerpop.apache.org">developer mailing list</a> before using it. Finally, note that the Apache Software Foundation&#8217;s <a href="https://www.apache.org/foundation/marks/">trademark policies</a> supersede those stipulated by Apache TinkerPop.</p> </div> <div class="sect2"> <h3 id="_character_graphics">Character Graphics</h3> <div class="paragraph"> <p>A character graphic can be used without permission as long as its being used in an Apache TinkerPop related context and it is acknowledged that the graphic is a trademark of the Apache Software Foundation/Apache TinkerPop.</p> </div> <div class="imageblock"> <div class="content"> <img src="../../images/gremlin-and-friends.png" alt="gremlin and friends" width="500"> </div> </div> </div> <div class="sect2"> <h3 id="_character_dress_up_graphics">Character Dress-Up Graphics</h3> <div class="paragraph"> <p>A character graphic can be manipulated ("dressed up") and used without permission as long as it&#8217;s being used in an Apache TinkerPop related context and it is acknowledged that the graphic is a trademark of the Apache Software Foundation/Apache TinkerPop.</p> </div> <table class="tableblock frame-none grid-all stretch"> <colgroup> <col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"> </colgroup> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/gremlin-gremstefani.png" alt="gremlin gremstefani" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/gremlin-gremicide.png" alt="gremlin gremicide" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/gremlin-gremalicious.png" alt="gremlin gremalicious" width="125"></span></p></td> </tr> </tbody> </table> </div> <div class="sect2"> <h3 id="_explanatory_diagrams">Explanatory Diagrams</h3> <div class="paragraph"> <p>Explanatory diagrams can be used without permission as long as they are being used in an Apache TinkerPop related context, it is acknowledged that they are trademarks of the Apache Software Foundation/Apache TinkerPop, and are being used for technical explanatory purposes.</p> </div> <table class="tableblock frame-none grid-all stretch"> <colgroup> <col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"> </colgroup> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/olap-traversal.png" alt="olap traversal" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/cyclicpath-step.png" alt="cyclicpath step" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/flat-map-lambda.png" alt="flat map lambda" width="125"></span></p></td> </tr> </tbody> </table> </div> <div class="sect2"> <h3 id="_character_scene_graphics">Character Scene Graphics</h3> <div class="paragraph"> <p>Character scene graphics require permission before being used. Please ask for permission on the Apache TinkerPop <a href="https://lists.apache.org/list.html?dev@tinkerpop.apache.org">developer mailing list</a>.</p> </div> <table class="tableblock frame-none grid-all stretch"> <colgroup> <col style="width: 33.3333%;"> <col style="width: 33.3333%;"> <col style="width: 33.3334%;"> </colgroup> <tbody> <tr> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/tinkerpop-reading.png" alt="tinkerpop reading" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/gremlintron.png" alt="gremlintron" width="125"></span></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><span class="image"><img src="../../images/tinkerpop3-splash.png" alt="tinkerpop3 splash" width="125"></span></p></td> </tr> </tbody> </table> </div> </div> </div> </div> <div id="footer"> <div id="footer-text"> Last updated 2024-10-24 12:14:39 -0700 </div> </div> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10