Repository: incubator-zeppelin Updated Branches: refs/heads/master 67f7f3e40 -> b9583c6e0
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/resources/scalate/DescribeType_live_data_address.html ---------------------------------------------------------------------- diff --git a/cassandra/src/test/resources/scalate/DescribeType_live_data_address.html b/cassandra/src/test/resources/scalate/DescribeType_live_data_address.html new file mode 100644 index 0000000..5c4546f --- /dev/null +++ b/cassandra/src/test/resources/scalate/DescribeType_live_data_address.html @@ -0,0 +1,137 @@ +<br/> +<br/> +<nav class="navbar navbar-default"> + <ul class="nav navbar-nav"> + + <li> + <a><strong>DESCRIBE TYPE live_data.address;</strong></a> + </li> + </ul> + <ul class="nav navbar-nav navbar-right"> + <li class="dropdown"> + <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + <strong>Legend</strong> + <span class="caret"></span> + </a> + <ul class="dropdown-menu"> + <li> + <a role="button"> + <i class="glyphicon glyphicon-dashboard text-muted" /> Cluster + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-folder-open text-danger" /> Keyspace + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-copyright-mark text-warning" /> UDT + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-th-list text-primary" /> Table + </a> + </li> + <li class="bg-info"> + <a role="button"> + <i class="glyphicon glyphicon-fullscreen" /> Partition Key + </a> + </li> + <li class="bg-warning"> + <a role="button"> + <i class="glyphicon glyphicon-pushpin" /> Static Column + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort" /> Clustering Column + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort-by-attributes" /> Clustering Order ASC + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort-by-attributes-alt" /> Clustering Order DESC + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-info-sign" /> Indexed Column + </a> + </li> + </ul> + </li> + <li> + <a href="#"></a> + </li> + </ul> +</nav> +<hr/> +<div class="row"> + <div class="col-md-3"></div> + <div class="col-md-6 col-offset-md-3"> + <div class="panel panel-default table-responsive table-bordered"> + <table class="table"> + + <caption><h4 class="text-warning"><i class="glyphicon glyphicon-copyright-mark"/> address</h4></caption> + + <thead> + <tr> + <th class="col-md-6">Column Name</th> + <th class="col-md-6">Data Type</th> + </tr> + </thead> + <tbody> + + <tr> + <td class="col-md-6">number</td> + <td class="col-md-6">int</td> + </tr> + + <tr> + <td class="col-md-6">street</td> + <td class="col-md-6">text</td> + </tr> + + <tr> + <td class="col-md-6">zip</td> + <td class="col-md-6">int</td> + </tr> + + <tr> + <td class="col-md-6">city</td> + <td class="col-md-6">text</td> + </tr> + + <tr> + <td class="col-md-6">country</td> + <td class="col-md-6">text</td> + </tr> + + <tbody> + </table> + <div class="panel-footer"> + <a data-toggle="collapse" data-target="#984da320-350d-11e5-a7a9-8f0ea8ae1a37_asCQL"> + <strong>As CQL statement</strong> + <span class="caret"></span> + </a> + <br/><br/> + <div class="collapse" id="984da320-350d-11e5-a7a9-8f0ea8ae1a37_asCQL"> + <pre class="well">CREATE TYPE live_data.address ( + number int, + street text, + zip int, + city text, + country text +);</pre> + </div> + </div> + </div> + </div> + <div class="col-md-3"></div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/resources/scalate/DescribeType_live_data_address_within_current_keyspace.html ---------------------------------------------------------------------- diff --git a/cassandra/src/test/resources/scalate/DescribeType_live_data_address_within_current_keyspace.html b/cassandra/src/test/resources/scalate/DescribeType_live_data_address_within_current_keyspace.html new file mode 100644 index 0000000..aa657f9 --- /dev/null +++ b/cassandra/src/test/resources/scalate/DescribeType_live_data_address_within_current_keyspace.html @@ -0,0 +1,137 @@ +<br/> +<br/> +<nav class="navbar navbar-default"> + <ul class="nav navbar-nav"> + + <li> + <a><strong>DESCRIBE TYPE address;</strong></a> + </li> + </ul> + <ul class="nav navbar-nav navbar-right"> + <li class="dropdown"> + <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + <strong>Legend</strong> + <span class="caret"></span> + </a> + <ul class="dropdown-menu"> + <li> + <a role="button"> + <i class="glyphicon glyphicon-dashboard text-muted" /> Cluster + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-folder-open text-danger" /> Keyspace + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-copyright-mark text-warning" /> UDT + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-th-list text-primary" /> Table + </a> + </li> + <li class="bg-info"> + <a role="button"> + <i class="glyphicon glyphicon-fullscreen" /> Partition Key + </a> + </li> + <li class="bg-warning"> + <a role="button"> + <i class="glyphicon glyphicon-pushpin" /> Static Column + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort" /> Clustering Column + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort-by-attributes" /> Clustering Order ASC + </a> + </li> + <li class="bg-success"> + <a role="button"> + <i class="glyphicon glyphicon-sort-by-attributes-alt" /> Clustering Order DESC + </a> + </li> + <li> + <a role="button"> + <i class="glyphicon glyphicon-info-sign" /> Indexed Column + </a> + </li> + </ul> + </li> + <li> + <a href="#"></a> + </li> + </ul> +</nav> +<hr/> +<div class="row"> + <div class="col-md-3"></div> + <div class="col-md-6 col-offset-md-3"> + <div class="panel panel-default table-responsive table-bordered"> + <table class="table"> + + <caption><h4 class="text-warning"><i class="glyphicon glyphicon-copyright-mark"/> address</h4></caption> + + <thead> + <tr> + <th class="col-md-6">Column Name</th> + <th class="col-md-6">Data Type</th> + </tr> + </thead> + <tbody> + + <tr> + <td class="col-md-6">number</td> + <td class="col-md-6">int</td> + </tr> + + <tr> + <td class="col-md-6">street</td> + <td class="col-md-6">text</td> + </tr> + + <tr> + <td class="col-md-6">zip</td> + <td class="col-md-6">int</td> + </tr> + + <tr> + <td class="col-md-6">city</td> + <td class="col-md-6">text</td> + </tr> + + <tr> + <td class="col-md-6">country</td> + <td class="col-md-6">text</td> + </tr> + + <tbody> + </table> + <div class="panel-footer"> + <a data-toggle="collapse" data-target="#984da320-350d-11e5-a7a9-8f0ea8ae1a37_asCQL"> + <strong>As CQL statement</strong> + <span class="caret"></span> + </a> + <br/><br/> + <div class="collapse" id="984da320-350d-11e5-a7a9-8f0ea8ae1a37_asCQL"> + <pre class="well">CREATE TYPE live_data.address ( + number int, + street text, + zip int, + city text, + country text +);</pre> + </div> + </div> + </div> + </div> + <div class="col-md-3"></div> +</div> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/resources/scalate/Help.html ---------------------------------------------------------------------- diff --git a/cassandra/src/test/resources/scalate/Help.html b/cassandra/src/test/resources/scalate/Help.html new file mode 100644 index 0000000..de71d64 --- /dev/null +++ b/cassandra/src/test/resources/scalate/Help.html @@ -0,0 +1,870 @@ +<br/> +<br/> +<nav class="navbar navbar-default"> + <ul class="nav navbar-nav"> + <li role="presentation" class="dropdown"> + <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + <span class="text-info"><i class="glyphicon glyphicon-book"/> <strong>Please select ...</strong></span> + <span class="text-info caret"></span> + <ul class="dropdown-menu"> + <li class="dropdown-header"><span class="text-info">Topics</span></li> + <li> + <a role="button" data-toggle="collapse" data-target="#d977b63a-9900-4ecd-a438-70eb490d6a48"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Basic Commands</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#eb22d00b-9be8-478a-b4bf-740f98e1e6ec"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Schema Discovery</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#881a5e0b-5e52-4474-ba80-787dfe0a770d"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Query Parameters</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#2af5a125-8754-40fb-a044-bda10395504f"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Prepared Statements</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#4dfe08e1-7ee0-4222-8be0-3d9d43aab38e"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Dynamic Forms</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#6485679a-ab5b-406b-8281-37f586459754"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Interpreter Configuration</span> + </a> + </li> + <li> + <a role="button" data-toggle="collapse" data-target="#2d060cba-7f9a-40de-8cc3-d82586d4321e"> + <span class="text-info"><i class="glyphicon glyphicon-bookmark"/> Misc</span> + </a> + </li> + </ul> + </a> + </li> + + <li> + <a><span class="text-info"><strong>CASSANDRA INTERPRETER DOCUMENTATION</strong></span></a> + </li> + </ul> + <ul class="nav navbar-nav navbar-right"> + <li class="dropdown"> + <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + <span class="text-info"><strong>About ...</strong></span> + <span class="caret"></span> + </a> + <ul class="dropdown-menu"> + <li> + <a role="button"> + <span class="text-info">Version <strong>1.0</strong></span> + </a> + </li> + <li> + <a role="button"> + <span class="text-info">Java Driver Version <strong>2.1.7.1</strong></span> + </a> + </li> + <li> + <a role="button"> + <span class="text-info">Author <strong>@doanduyhai</strong></span> + </a> + </li> + </ul> + </li> + <li> + <a href="#"></a> + </li> +</nav> +<br/><br/> +<div class="container"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#d977b63a-9900-4ecd-a438-70eb490d6a48" aria-expanded="false"> + <span class="text-info"><strong>Basic Commands</strong></span> + </a> + </h4> + </div> + <div id="d977b63a-9900-4ecd-a438-70eb490d6a48" class="panel-collapse collapse in" role="tabpanel"> + <div class="panel-body"> + + <div class="panel panel-default"> + <div class="panel-body"> + <h3>I CQL Statements</h3> + <p>This interpreter is compatible with any CQL statement supported by Cassandra. Ex: + <br/><br/> + <div class="row"> + <div class="col-md-6 col-md-offset-3"> + <pre> + + INSERT INTO users(login,name) VALUES('jdoe','John DOE'); + SELECT * FROM users WHERE login='jdoe'; + </pre> + </div> + </div> + <br/> + Each statement should be separated by a <strong>semi-colon</strong> (;). + <br/> + <strong>Multi-line</strong> statements as well as multiple statements on the <strong>same line</strong> + are also supported as long as they are separated by a semi-colon. Ex: + <br/> + <br/> + <div class="row"> + <div class="col-md-8 col-md-offset-2"> + <pre> + + USE spark_demo; + + SELECT * FROM albums_by_country LIMIT 1; SELECT * FROM countries LIMIT 1; + + SELECT * + FROM artists + WHERE login='jlennon'; + </pre> + </div> + </div> + <br/> + <strong>Batch</strong> statements are supported and can span multiple lines, as well as + <strong>DDL</strong>(CREATE/ALTER/DROP) statements: + <br/> + <br/> + <div class="row"> + <div class="col-md-8 col-md-offset-2"> + <pre> + + BEGIN BATCH + INSERT INTO users(login,name) VALUES('jdoe','John DOE'); + INSERT INTO users_preferences(login,account_type) VALUES('jdoe','BASIC'); + APPLY BATCH; + + CREATE TABLE IF NOT EXISTS test( + key int PRIMARY KEY, + value text + ); + </pre> + </div> + </div> + <br/> + CQL statements are <strong>case-insensitive</strong> (except for column names and values). + This means that the following statements are equivalent and valid: + <br/> + <br/> + <div class="row"> + <div class="col-md-8 col-md-offset-2"> + <pre> + + INSERT INTO users(login,name) VALUES('jdoe','John DOE'); + Insert into users(login,name) vAlues('hsue','Helen SUE'); + </pre> + </div> + </div> + <br/> + The complete list of all CQL statements and versions can be found below: + <br/><br/> + <div class="row"> + <div class="col-md-6 col-md-offset-3"> + <table class="table table-bordered"> + <thead> + <tr><th>Cassandra version</th><th>Documentation</th></tr> + </thead> + <tbody> + <tr> + <td><strong>2.2</strong></td> + <td> + <a href="http://docs.datastax.com/en/cql/3.3/cql/cqlIntro.html" target="_blank"> + http://docs.datastax.com/en/cql/3.3/cql/cqlIntro.html + </a> + </td> + </tr> + <tr> + <td><strong>2.1 & 2.0</strong></td> + <td> + <a href="http://docs.datastax.com/en/cql/3.1/cql/cql_intro_c.html" target="_blank"> + http://docs.datastax.com/en/cql/3.1/cql/cql_intro_c.html + </a> + </td> + </tr> + <tr> + <td><strong>1.2</strong></td> + <td> + <a href="http://docs.datastax.com/en/cql/3.0/cql/aboutCQL.html" target="_blank"> + http://docs.datastax.com/en/cql/3.0/cql/aboutCQL.html + </a> + </td> + </tr> + </tbody> + </table> + </div> + </div> + + + </p> + <h3>II Comments</h3> + <p> + It is possible to add comments between statements. Single line comments start with the + <strong>hash</strong> sign (#). Multi-line comments are enclosed between + <strong>/**</strong> and <strong>**/</strong>. Ex: + + <br/> + <br/> + <div class="row"> + <div class="col-md-8 col-md-offset-2"> + <pre> + + #First comment + INSERT INTO users(login,name) VALUES('jdoe','John DOE'); + + /** + Multi line + comments + **/ + Insert into users(login,name) vAlues('hsue','Helen SUE'); + </pre> + </div> + </div> + <br/> + + </p> + <h3>III Syntax Validation</h3> + <p> + The interpreters is shipped with a <em>built-in syntax validator</em>. This validator only + checks for <strong>basic syntax errors</strong>. All CQL-related syntax validation is delegated + directly to <strong>Cassandra</strong> + <br/><br/> + Most of the time, syntax errors are due to missing semi-colons between statements or typo errors. + + </p> + + </div> + </div> + + + + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#eb22d00b-9be8-478a-b4bf-740f98e1e6ec" aria-expanded="false"> + <span class="text-info"><strong>Schema Discovery</strong></span> + </a> + </h4> + </div> + <div id="eb22d00b-9be8-478a-b4bf-740f98e1e6ec" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + + <div class="panel panel-default"> + <div class="panel-body"> + <h3>I Commands For Discovery</h3> + <p> + To make schema discovery easier and more interactive, the following commands are supported: + <br/><br/> + <table class="table table-bordered"> + <thead> + <tr><th>Command</th><th>Description</th></tr> + </thead> + <tbody> + <tr> + <td><strong>DESCRIBE CLUSTER;</strong></td> + <td>Show the current cluster name and its partitioner</td> + </tr> + <tr> + <td><strong>DESCRIBE KEYSPACES;</strong></td> + <td>List all existing keyspaces in the cluster and their configuration + (replication factor, durable write ...)</td> + </tr> + <tr> + <td><strong>DESCRIBE TABLES;</strong></td> + <td>List all existing keyspaces in the cluster and for each, all the tables name</td> + </tr> + <tr> + <td><strong>DESCRIBE KEYSPACE <keyspace name>;</strong></td> + <td>Describe the given keyspace configuration and all its table details (name, columns, ...)</td> + </tr> + <tr> + <td><strong>DESCRIBE TABLE <em>(<keyspace name>).</em><table name>;</strong></td> + <td> + Describe the given table. If the keyspace is not provided, the current + <strong>logged in</strong> keyspace is used. If there is no logged in keyspace, + the default <em>system</em> keyspace is used. If no table is found, an error message is raised + </td> + </tr> + <tr> + <td><strong>DESCRIBE TYPE <em>(<keyspace name>).</em><type name>;</strong></td> + <td> + Describe the given type(UDT). If the keyspace is not provided, the current + <strong>logged in</strong> keyspace is used. If there is no logged in keyspace, + the default <em>system</em> keyspace is used. If no type is found, an error message is raised + </td> + </tr> + </tbody> + </table> + <br/> + <div class="alert alert-danger" role="alert"> + Please note that each <strong>DESCRIBE</strong> command should be ended by <strong>a semi-colon</strong>. + </div> + </p> + <h3>II Schema Display</h3> + <p> + The schema objects (cluster, keyspace, table & type) are displayed in a tabular format. + There is a <strong>drop-down</strong> menu on the top left corner to expand objects details. + On the top right menu is shown the Icon legend. + + </p> + </div> + </div> + + </div> + </div> + </div> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#881a5e0b-5e52-4474-ba80-787dfe0a770d" aria-expanded="false"> + <span class="text-info"><strong>Query Parameters</strong></span> + </a> + </h4> + </div> + <div id="881a5e0b-5e52-4474-ba80-787dfe0a770d" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + + <div class="panel panel-default"> + <div class="panel-body"> + <p> + Sometimes you want to be able to pass runtime query parameters to your statements. + Those parameters are <strong>not</strong> part of the CQL specs and are specific to the interpreter. + Below is the list of all parameters: + + <br/><br/> + <table class="table table-bordered"> + <caption> + <h4>Query Parameters</h4> + </caption> + <thead> + <tr> + <th>Parameter</th> + <th>Syntax</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>Consistency Level</td> + <td><strong>@consistency=<em>value</em></strong></td> + <td>Apply the given consistency level to all queries in the paragraph</td> + </tr> + <tr> + <td>Serial Consistency Level</td> + <td><strong>@serialConsistency=<em>value</em></strong></td> + <td>Apply the given serial consistency level to all queries in the paragraph</td> + </tr> + <tr> + <td>Timestamp</td> + <td><strong>@timestamp=<em>long value</em></strong></td> + <td>Apply the given timestamp to all queries in the paragraph.<br/> + Please note that timestamp value passed directly in CQL statement will override this value + </td> + </tr> + <tr> + <td>Retry Policy</td> + <td><strong>@retryPolicy=<em>value</em></strong></td> + <td>Apply the given retry policy to all queries in the paragraph</td> + </tr> + <tr> + <td>Fetch Size</td> + <td><strong>@fetchSize=<em>int value</em></strong></td> + <td>Apply the given fetch size to all queries in the paragraph</td> + </tr> + </tbody> + </table> + <br/> + Some parameters only accept restricted values: + + <br/><br/> + <table class="table table-bordered"> + <caption> + <h4>Allowed Values</h4> + </caption> + <thead> + <tr> + <th>Parameter</th> + <th>Possible Values</th> + </tr> + </thead> + <tbody> + <tr> + <td>Consistency Level</td> + <td><strong>ALL, ANY, ONE, TWO, THREE, QUORUM, LOCAL_ONE, LOCAL_QUORUM, EACH_QUORUM</strong></td> + </tr> + <tr> + <td>Serial Consistency Level</td> + <td><strong>SERIAL, LOCAL_SERIAL</strong></td> + </tr> + <tr> + <td>Timestamp</td> + <td>Any long value</td> + </tr> + <tr> + <td>Retry Policy</td> + <td> + <strong> + DEFAULT, DOWNGRADING_CONSISTENCY, FALLTHROUGH, LOGGING_DEFAULT, + LOGGING_DOWNGRADING, LOGGING_FALLTHROUGH + </strong> + </td> + </tr> + <tr> + <td>Fetch Size</td> + <td>Any integer value</td> + </tr> + </tbody> + </table> + <br/> + + <div class="alert alert-danger" role="alert"> + Please note that you <strong>should not add semi-colon (;)</strong> at the end of each parameter statement + </div> + + Some example: + <br/><br/> + <div class="row"> + <div class="col-md-8 col-md-offset-2"> + <pre> + + CREATE TABLE IF NOT EXISTS spark_demo.ts( + key int PRIMARY KEY, + value text + ); + TRUNCATE spark_demo.ts; + + # Timestamp in the past + @timestamp=10 + + # Force timestamp directly in the first insert + INSERT INTO spark_demo.ts(key,value) VALUES(1,'first insert') USING TIMESTAMP 100; + + # Select some data to make the clock turn + SELECT * FROM spark_demo.albums LIMIT 100; + + # Now insert using the timestamp parameter set at the beginning(10) + INSERT INTO spark_demo.ts(key,value) VALUES(1,'second insert'); + + # Check for the result. You should see 'first insert' + SELECT value FROM spark_demo.ts WHERE key=1; + </pre> + </div> + </div> + <br/> + + Some remarks about query parameters: + <br/><br/> + <div class="alert alert-info" role="alert"> + <ul> + <li><strong>many</strong> query parameters can be set in the same paragraph</li> + <li>if the <strong>same</strong> query parameter is set many time with different values, + the interpreter only take into account the first value + </li> + <li>each query parameter applies to <strong>all</strong> CQL statement in the same paragraph, + unless you override the option using plain CQL text (like forcing timestamp with the USING clause) + </li> + <li>the order of each query parameter with regard to CQL statement does not matter</li> + </ul> + </div> + </p> + </div> + </div> + + + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#2af5a125-8754-40fb-a044-bda10395504f" aria-expanded="false"> + <span class="text-info"><strong>Prepared Statements</strong></span> + </a> + </h4> + </div> + <div id="2af5a125-8754-40fb-a044-bda10395504f" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + <div class="panel panel-default"> + <div class="panel-body"> + <h3>I Syntax</h3> + <br/> + <p> + For performance reason, it is better to <strong>prepare statements</strong> before-hand and reuse + them later by providing bound values. This interpreter provides 3 commands to handle prepared and + bound statements: + <br/><br/> + <ol> + <li><strong>@prepare</strong></li> + <li><strong>@bind</strong></li> + <li><strong>@remove_prepared</strong></li> + </ol> + <br/> + Example: + <br/> + <div class="row"> + <div class="col-md-10 col-md-offset-1"> + <pre> + + @prepare[statement_name]=... + + @bind[statement_name]=âtextâ, 1223, â2015-07-30 12:00:01â, null, true, [âlist_item1â, âlist_item2â] + + @bind[statement_name_with_no_bound_value] + + @remove_prepare[statement_name] + + </pre> + </div> + </div> + <br/> + + <h3>II @prepare</h3> + <br/> + <p> + You can use the syntax "<strong>@prepare[statement_name]=SELECT ...</strong>" to create a prepared statement. + The <em>statement_name</em> is mandatory because the interpreter prepares the given statement with the + Java driver and saves the generated prepared statement in an internal map, using the provided + <em>statement_name</em> as search key. + <br/><br/> + <div class="alert alert-info"> + Please note that this internal prepared statement map is shared with <strong>all notebooks</strong> + and <strong>all paragraphs</strong> because there is only one instance of the interpreter for Cassandra + </div> + <br/> + <div class="alert alert-warning"> + If the interpreter encounters many @prepare for the <strong>same statement_name</strong> (key), + only the <strong>first</strong> statement will be taken into account. + </div> + <br/> + Example: + <br/> + <div class="row"> + <div class="col-md-10 col-md-offset-1"> + <pre> + + @prepare[select]=SELECT * FROM spark_demo.albums LIMIT ? + + @prepare[select]=SELECT * FROM spark_demo.artists LIMIT ? + </pre> + </div> + </div> + <br/> + + For the above example, the prepared statement is <strong>"SELECT * FROM spark_demo.albums LIMIT ?"</strong>. + <em>"SELECT * FROM spark_demo.artists LIMIT ?"</em> is ignored because an entry already exists in the + prepared statements map with the key <strong>select</strong>. + <br/><br/> + In the context of Zeppelin, a notebook can be scheduled to be executed at regular interval, + thus it is necessary to avoid re-preparing many time the same statement (considered an anti-pattern). + </p> + <h3>III @bind</h3> + <br/> + <p> + Once the statement is prepared (possibly in a separated notebook/paragraph). You can bind values to it: + <br/><br/> + <div class="row"> + <div class="col-md-10 col-md-offset-1"> + <pre> + + @bind[select_first]=10 + </pre> + </div> + </div> + <br/> + Bound values are not mandatory for the <strong>@bind</strong> statement. + However if you provide bound values, they need to comply to some syntax: + + <ul> + <li>String values should be enclosed between simple quotes ( â )</li> + <li>Date values should be enclosed between simple quotes ( â ) and respect the formats: + <ol> + <li>yyyy-MM-dd HH:MM:ss</li> + <li>yyyy-MM-dd HH:MM:ss.SSS</li> + </ol> + </li> + <li><strong>null</strong> is parsed as-is</li> + <li><strong>boolean</strong> (true|false) are parsed as-is </li> + <li>collection values must follow the + <a href="http://docs.datastax.com/en/cql/3.1/cql/cql_using/use_collections_c.html" target="_blank">standard CQL syntax</a>: + <ul> + <li>list: [âlist_item1â, âlist_item2â, ...]</li> + <li>set: {âset_item1â, âset_item2â, â¦}</li> + <li>map: {âkey1â: âval1â, âkey2â: âval2â, â¦}</li> + </ul> + </li> + <li> + tuple values should be enclosed between parenthesis + (see <a href="http://docs.datastax.com/en/cql/3.1/cql/cql_reference/tupleType.html" target="_blank">tuple CQL syntax</a>): + (âtextâ, 123, true) + </li> + <li> + udt values should be enclosed between brackets + (see <a href="http://docs.datastax.com/en/cql/3.1/cql/cql_using/cqlUseUDT.html" target="_blank">udt CQL syntax</a>): + {stree_name: âBeverly Hillsâ, number: 104, zip_code: 90020, state: âCaliforniaâ, â¦} + </li> + </ul> + <br/> + <div class="alert alert-info"> + It is possible to use the <strong>@bind</strong> statement inside a batch: <br/> + <pre> + BEGIN BATCH + @bind[insert_user]='jdoe','John DOE' + UPDATE users SET age = 27 WHERE login='hsue'; + APPLY BATCH; + </pre> + </div> + <br/> + </p> + <h3>IV @remove_prepare</h3> + <br/> + <p> + To avoid for a prepared statement to stay forever in the prepared statement map, you can use the <strong>@remove_prepare[statement_name]</strong> syntax + to remove it. Removing a non-existing prepared statement yields no error. + </p> + </div> + </div> + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#4dfe08e1-7ee0-4222-8be0-3d9d43aab38e" aria-expanded="false"> + <span class="text-info"><strong>Dynamic Forms</strong></span> + </a> + </h4> + </div> + <div id="4dfe08e1-7ee0-4222-8be0-3d9d43aab38e" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + + <div class="panel panel-default"> + <div class="panel-body"> + <p> + Instead of hard-coding your CQL queries, it is possible to use the mustache syntax (<strong>{{ }}</strong>) + to inject simple value or multiple choices forms. + <br/><br/> + + The syntax for simple parameter is: <strong>{{input_Label=default value}}</strong>. + The default value is mandatory because the first time the paragraph is executed, + we launch the CQL query before rendering the form so at least one value should be provided. + <br/><br/> + The syntax for multiple choices parameter is: <strong>{{input_Label=value1 | value2 | ⦠| valueN }}</strong>. + By default the first choice is used for CQL query the first time the paragraph is executed. + <br/><br/> + Example: + <br/> + <div class="row"> + <div class="col-md-10 col-md-offset-1"> + <pre> + + #Secondary index on performer style + SELECT name, country, performer + FROM spark_demo.performers + WHERE name='{{performer=Sheryl Crow|Doof|Fanfarlo|Los Paranoia}}' + AND styles CONTAINS '{{style=Rock}}'; + + </pre> + </div> + </div> + <br/> + + In the above example, the first CQL query will be executed for <em>performer='Sheryl Crow'</em> + AND <em>style='Rock'</em>. For subsequent queries, you can change the value directly using the form. + Please note that we enclosed the {{ }} block between simple quotes (') because Cassandra expects a String here. + We could have also use the <strong>{{style='Rock'}}</strong> syntax but this time, the value + displayed on the form is <em>'Rock'</em> and not <em>Rock</em>. + + <br/><br/> + <div class="alert alert-info"> + It is also possible to use dynamic forms for <strong>prepared statements</strong>: <br/> + <strong>@bind[select]=='{{performer=Sheryl Crow|Doof|Fanfarlo|Los Paranoia}}', '{{style=Rock}}'</strong> + </div> + </pre> + </p> + </div> + </div> + + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#6485679a-ab5b-406b-8281-37f586459754" aria-expanded="false"> + <span class="text-info"><strong>Interpreter Configuration</strong></span> + </a> + </h4> + </div> + <div id="6485679a-ab5b-406b-8281-37f586459754" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + The <strong>Cassandra</strong> interpreter comes with some some configuration values for the Java driver: + + <table class="table table-bordered"> + <caption> + <h4>Interpreter Configuration</h4> + </caption> + <thead> + <tr> + <th>Parameter</th> + <th>Default Value</th> + </tr> + </thead> + <tbody> + <tr> + <td>cassandra.cluster</td> + <td><strong>Test Cluster</strong></td> + </tr> + <tr> + <td>cassandra.compression.protocol</td> + <td><strong>NONE</strong>, possible values: LZ4, SNAPPY</td> + </tr> + <tr> + <td>cassandra.credentials.password</td> + <td><strong>none</strong></td> + </tr> + <tr> + <td>cassandra.credentials.username</td> + <td><strong>none</strong></td> + </tr> + <tr> + <td>cassandra.hosts</td> + <td><strong>localhost</strong></td> + </tr> + <tr> + <td>cassandra.interpreter.parallelism</td> + <td><strong>10</strong></td> + </tr> + <tr> + <td>cassandra.keyspace</td> + <td><strong>system</strong></td> + </tr> + <tr> + <td>cassandra.load.balancing.policy</td> + <td><strong>DEFAULT</strong>, or a FQCN of a custom class</td> + </tr> + <tr> + <td>cassandra.max.schema.agreement.wait.second</td> + <td><strong>10</strong></td> + </tr> + <tr> + <td>cassandra.native.port</td> + <td><strong>9042</strong></td> + </tr> + <tr> + <td>cassandra.pooling.core.connection.per.host.local</td> + <td><strong>Protocol V2 and below: 2, V3 and above: 1</strong></td> + </tr> + <tr> + <td>cassandra.pooling.core.connection.per.host.remote</td> + <td><strong>Protocol V2 and below: 1, V3 and above: 1</strong></td> + </tr> + <tr> + <td>cassandra.pooling.heartbeat.interval.seconds</td> + <td><strong>30</strong></td> + </tr> + <tr> + <td>cassandra.pooling.idle.timeout.seconds</td> + <td><strong>Test Cluster</strong></td> + </tr> + <tr> + <td>cassandra.pooling.max.connection.per.host.local</td> + <td><strong>Protocol V2 and below: 8, V3 and above: 1</strong></td> + </tr> + <tr> + <td>cassandra.pooling.max.connection.per.host.remote</td> + <td><strong>Protocol V2 and below: 2, V3 and above: 1</strong></td> + </tr> + <tr> + <td>cassandra.pooling.max.request.per.connection.local</td> + <td><strong>Protocol V2 and below: 128, V3 and above: 1024</strong></td> + </tr> + <tr> + <td>cassandra.pooling.max.request.per.connection.remote</td> + <td><strong>Protocol V2 and below: 128, V3 and above: 256</strong></td> + </tr> + <tr> + <td>cassandra.pooling.new.connection.threshold.local</td> + <td><strong>Protocol V2 and below: 100, V3 and above: 800</strong></td> + </tr> + <tr> + <td>cassandra.pooling.new.connection.threshold.remote</td> + <td><strong>Protocol V2 and below: 100, V3 and above: 200</strong></td> + </tr> + <tr> + <td>cassandra.pooling.pool.timeout.millisecs</td> + <td><strong>5000</strong></td> + </tr> + <tr> + <td>cassandra.protocol.version</td> + <td><strong>3</strong></td> + </tr> + <tr> + <td>cassandra.query.default.consistency</td> + <td><strong>ONE</strong></td> + </tr> + <tr> + <td>cassandra.query.default.fetchSize</td> + <td><strong>5000</strong></td> + </tr> + <tr> + <td>cassandra.query.default.serial.consistency</td> + <td><strong>SERIAL</strong></td> + </tr> + <tr> + <td>cassandra.reconnection.policy</td> + <td><strong>DEFAULT</strong>, or a FQCN of a custom class</td> + </tr> + <tr> + <td>cassandra.retry.policy</td> + <td><strong>DEFAULT</strong>, or a FQCN of a custom class</td> + </tr> + <tr> + <td>cassandra.socket.connection.timeout.millisecs</td> + <td><strong>500</strong></td> + </tr> + <tr> + <td>cassandra.socket.read.timeout.millisecs</td> + <td><strong>12000</strong></td> + </tr> + <tr> + <td>cassandra.socket.tcp.no_delay</td> + <td><strong>true</strong></td> + </tr> + <tr> + <td>cassandra.speculative.execution.policy</td> + <td><strong>DEFAULT</strong>, or a FQCN of a custom class</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a role="button" data-toggle="collapse" data-target="#2d060cba-7f9a-40de-8cc3-d82586d4321e" aria-expanded="false"> + <span class="text-info"><strong>Miscellaneous</strong></span> + </a> + </h4> + </div> + <div id="2d060cba-7f9a-40de-8cc3-d82586d4321e" class="panel-collapse collapse" role="tabpanel"> + <div class="panel-body"> + <h3>Execution parallelism</h3> + It is possible to execute many paragraphs in parallel. However, at the back-end side, weâre still using <strong>synchronous</strong> queries. Asynchronous execution is only possible when it is possible to return a Future value in the <strong>InterpreterResult</strong>. It may be an interesting proposal for the Zeppelin project. + </div> + </div> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/resources/scalate/NoResult.html ---------------------------------------------------------------------- diff --git a/cassandra/src/test/resources/scalate/NoResult.html b/cassandra/src/test/resources/scalate/NoResult.html new file mode 100644 index 0000000..e7c2d46 --- /dev/null +++ b/cassandra/src/test/resources/scalate/NoResult.html @@ -0,0 +1,6 @@ +<div class="container"> + <div class="row text-center"> + <h4>No Result</h4> + </div> + <br> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/resources/scalate/NoResultWithExecutionInfo.html ---------------------------------------------------------------------- diff --git a/cassandra/src/test/resources/scalate/NoResultWithExecutionInfo.html b/cassandra/src/test/resources/scalate/NoResultWithExecutionInfo.html new file mode 100644 index 0000000..c8975c8 --- /dev/null +++ b/cassandra/src/test/resources/scalate/NoResultWithExecutionInfo.html @@ -0,0 +1,42 @@ +<div class="container"> + <div class="row text-center"> + <h4>No Result</h4> + </div> + <br/> + <div class="row"> + <div class="col-md-3"></div> + <div class="col-md-6 col-offset-md-3 table-responsive table-bordered"> + <table class="table"> + <caption><h5>Last query execution info</h5></caption> + <thead> + <tr> + <th>Info</th> + <th>Value</th> + </tr> + </thead> + <tbody> + <tr> + <td>Statement</td> + <td>CREATE TABLE IF NOT EXISTS no_select(id int PRIMARY KEY);</td> + </tr> + <tr> + <td>Achieved Consistency</td> + <td>N/A</td> + </tr> + <tr> + <td>Tried Hosts</td> + <td>TRIED_HOSTS</td> + </tr> + <tr> + <td>Queried Hosts</td> + <td>QUERIED_HOSTS</td> + </tr> + <tr> + <td>Schema In Agreement</td> + <td>true</td> + </tr> + </tbody> + </table> + </div> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/scala/org/apache/zeppelin/cassandra/BoundValuesParserTest.scala ---------------------------------------------------------------------- diff --git a/cassandra/src/test/scala/org/apache/zeppelin/cassandra/BoundValuesParserTest.scala b/cassandra/src/test/scala/org/apache/zeppelin/cassandra/BoundValuesParserTest.scala new file mode 100644 index 0000000..de14c88 --- /dev/null +++ b/cassandra/src/test/scala/org/apache/zeppelin/cassandra/BoundValuesParserTest.scala @@ -0,0 +1,193 @@ +/* + * 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. + */ +package org.apache.zeppelin.cassandra + +import org.scalatest.{Matchers, BeforeAndAfterEach, FlatSpec} + +class BoundValuesParserTest extends FlatSpec +with BeforeAndAfterEach +with Matchers { + + val parser = new BoundValuesParser + + "BoundValuesParser" should "parse quoted string" in { + //Given + val input = """'a'""" + + //When + val parsed1 = parser.parse(parser.quotedString,input) + val parsed2 = parser.parse(parser.value,input) + + //Then + parsed1.get should be("'a'") + parsed2.get should be("'a'") + } + + "BoundValuesParser" should "parse integer" in { + //Given + val input = """00123""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("123") + } + + "BoundValuesParser" should "parse decimal number" in { + //Given + val input1 = """00123.35000""" + val input2 = """+123.""" + val input3 = """.35000""" + val input4 = """-.35000""" + + //When + val parsed1 = parser.parse(parser.value,input1) + val parsed2 = parser.parse(parser.value,input2) + val parsed3 = parser.parse(parser.value,input3) + val parsed4 = parser.parse(parser.value,input4) + + //Then + parsed1.get should be("123.35") + parsed2.get should be("123.0") + parsed3.get should be("0.35") + parsed4.get should be("-0.35") + } + + "BoundValuesParser" should "parse list" in { + //Given + val input = """['a','b','c']""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("['a','b','c']") + } + + "BoundValuesParser" should "parse set" in { + //Given + val input = """{'a',2,3.4}""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("{'a',2,3.4}") + } + + "BoundValuesParser" should "parse map" in { + //Given + val input = """{'key1': 'val', 'key2': 2, 'key3': 3.4}""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("{'key1': 'val', 'key2': 2, 'key3': 3.4}") + } + + "BoundValuesParser" should "parse tuple" in { + //Given + val input = """('a',2,3.4)""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("('a',2,3.4)") + } + + "BoundValuesParser" should "parse udt" in { + //Given + val input = """{col1: 'val1', col2: 2, col3: 3.4}""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("{col1: 'val1', col2: 2, col3: 3.4}") + } + + "BoundValuesParser" should "parse date" in { + //Given + val input = """'2015-07-10 14:56:34'""" + + //When + val parsed = parser.parse(parser.value,input) + + //Then + parsed.get should be("2015-07-10 14:56:34") + } + + "BoundValuesParser" should "parse nested types" in { + + //Given + val input = "'jdoe','John','DOE'," + + "{street_number: 3, street_name: 'Beverly Hills Bld', zip_code: 90209," + + " country: 'USA', extra_info: ['Right on the hills','Next to the post box']," + + " phone_numbers: {'home': 2016778524, 'office': 2015790847} }," + + "('USA', 90209, 'Beverly Hills')" + + //When + val parsed = parser.parse(parser.values,input) + + //Then + parsed.get should be(List( + "'jdoe'", + "'John'", + "'DOE'", + "{street_number: 3, street_name: 'Beverly Hills Bld', zip_code: 90209, country: 'USA', extra_info: ['Right on the hills','Next to the post box'], phone_numbers: {'home': 2016778524, 'office': 2015790847}}", + "('USA',90209,'Beverly Hills')" + )) + } + + "BoundValuesParser" should "not parse mustaches for zeppelin variables" in { + //Given + val input = "'jdoe',{{firstname='Jdoe'}}" + + //When + val parsed = parser.parse(parser.values,input) + + //Then + parsed.get should be(List("'jdoe'","{{firstname='Jdoe'}}")) + + } + + "BoundValuesParser" should "parse zeppelin variable" in { + //Given + val input = """'{{login=jdoe}}'""" + + //When + val parsed = parser.parse(parser.values,input) + + //Then + parsed.get should be(List("'{{login=jdoe}}'")) + } + + "BoundValuesParser" should "parse null value" in { + //Given + val input = """'login',null,'LASTNAME'""" + + //When + val parsed = parser.parse(parser.values,input) + + //Then + parsed.get should be(List("'login'","null","'LASTNAME'")) + } + +} http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/cassandra/src/test/scala/org/apache/zeppelin/cassandra/ParagraphParserTest.scala ---------------------------------------------------------------------- diff --git a/cassandra/src/test/scala/org/apache/zeppelin/cassandra/ParagraphParserTest.scala b/cassandra/src/test/scala/org/apache/zeppelin/cassandra/ParagraphParserTest.scala new file mode 100644 index 0000000..b1a8866 --- /dev/null +++ b/cassandra/src/test/scala/org/apache/zeppelin/cassandra/ParagraphParserTest.scala @@ -0,0 +1,585 @@ +/* + * 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. + */ +package org.apache.zeppelin.cassandra + +import com.datastax.driver.core._ +import org.apache.zeppelin.interpreter.InterpreterException +import org.scalatest.mock.MockitoSugar +import org.scalatest.{BeforeAndAfterEach, FlatSpec, Matchers} +import org.apache.zeppelin.cassandra.ParagraphParser._ +import org.apache.zeppelin.cassandra.TextBlockHierarchy._ + +class ParagraphParserTest extends FlatSpec + with BeforeAndAfterEach + with Matchers + with MockitoSugar { + + val session: Session = mock[Session] + val preparedStatements:collection.mutable.Map[String,PreparedStatement] = collection.mutable.Map() + val parser: ParagraphParser = new ParagraphParser() + + + "Parser" should "parse mixed statements" in { + val query: String = """ + SELECT * FROM albums LIMIT 10; + + begin UnLoGgEd BATCH + INSERT INTO users(id) VALUES(10); + @bind[test]='a',12.34 + apply Batch; + + SELECT * FROM users LIMIT 10; + + BEGIN BATCH + Insert INTO users(id) VALUES(11); + INSERT INTO users(id) VALUES(12); + APPLY BATCH; + + @bind[toto]='a',12.34 + + desc table zeppelin.users; + describe keyspace zeppelin; + """.stripMargin + + val parsed = parser.parse(parser.queries,query) + + parsed.get should be(List( + SimpleStm("SELECT * FROM albums LIMIT 10;"), + BatchStm(BatchStatement.Type.UNLOGGED, + List( + SimpleStm("INSERT INTO users(id) VALUES(10);"), + BoundStm("test","'a',12.34") + ) + ), + SimpleStm("SELECT * FROM users LIMIT 10;"), + BatchStm(BatchStatement.Type.LOGGED, + List( + SimpleStm("Insert INTO users(id) VALUES(11);"), + SimpleStm("INSERT INTO users(id) VALUES(12);") + ) + ), + BoundStm("toto","'a',12.34"), + DescribeTableCmd(Option("zeppelin"),"users"), + DescribeKeyspaceCmd("zeppelin") + )) + } + + "Parser" should "parse single-line comment" in { + val query :CharSequence="""#This is a comment""".stripMargin + + val parsed = parser.parseAll[Comment](parser.singleLineComment, query) + parsed.get should be(Comment("This is a comment")) + } + + + "Parser" should "parse multi-line comment" in { + val query:String = + """/*This is a comment + |line1 + |line2 + |line3 + |*/ + """.stripMargin + + val parsed = parser.parseAll(parser.multiLineComment, query) + parsed.get should be(Comment("This is a comment\nline1\nline2\nline3\n")) + } + + "Parser" should "parse consistency level" in { + val query:String =""" @consistency=ONE""".stripMargin + val parsed = parser.parseAll(parser.consistency, query) + parsed.get should be(Consistency(ConsistencyLevel.ONE)) + } + + "Parser" should "fails parsing unknown consistency level" in { + val query:String =""" @consistency=TEST""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.consistency, query) + } + ex.getMessage should be(s"Invalid syntax for @consistency. It should comply to the pattern ${CONSISTENCY_LEVEL_PATTERN.toString}") + } + + "Parser" should "parse serial consistency level" in { + val query:String =""" @serialConsistency=LOCAL_SERIAL""".stripMargin + val parsed = parser.parseAll(parser.serialConsistency, query) + parsed.get should be(SerialConsistency(ConsistencyLevel.LOCAL_SERIAL)) + } + + "Parser" should "fails parsing unknown serial consistency level" in { + val query:String =""" @serialConsistency=TEST""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.serialConsistency, query) + } + ex.getMessage should be(s"Invalid syntax for @serialConsistency. It should comply to the pattern ${SERIAL_CONSISTENCY_LEVEL_PATTERN.toString}") + } + + "Parser" should "parse timestamp" in { + val query:String =""" @timestamp=111""".stripMargin + val parsed = parser.parseAll(parser.timestamp, query) + parsed.get should be(Timestamp(111L)) + } + + "Parser" should "fails parsing invalid timestamp" in { + val query:String =""" @timestamp=TEST""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.timestamp, query) + } + ex.getMessage should be(s"Invalid syntax for @timestamp. It should comply to the pattern ${TIMESTAMP_PATTERN.toString}") + } + + "Parser" should "parse retry policy" in { + val query:String ="@retryPolicy="+CassandraInterpreter.DOWNGRADING_CONSISTENCY_RETRY + val parsed = parser.parseAll(parser.retryPolicy, query) + parsed.get should be(DowngradingRetryPolicy) + } + + "Parser" should "fails parsing invalid retry policy" in { + val query:String =""" @retryPolicy=TEST""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.retryPolicy, query) + } + ex.getMessage should be(s"Invalid syntax for @retryPolicy. It should comply to the pattern ${RETRY_POLICIES_PATTERN.toString}") + } + + "Parser" should "parse fetch size" in { + val query:String ="@fetchSize=100" + val parsed = parser.parseAll(parser.fetchSize, query) + parsed.get should be(FetchSize(100)) + } + + "Parser" should "fails parsing invalid fetch size" in { + val query:String =""" @fetchSize=TEST""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.fetchSize, query) + } + ex.getMessage should be(s"Invalid syntax for @fetchSize. It should comply to the pattern ${FETCHSIZE_PATTERN.toString}") + } + + "Parser" should "parse simple statement" in { + //Given + val query:String =""" sElecT * FROM users LIMIT ? ;""".stripMargin + + //When + val parsed = parser.parseAll(parser.genericStatement, query) + + //Then + parsed.get should be(SimpleStm("sElecT * FROM users LIMIT ? ;")) + } + + "Parser" should "parse prepare" in { + //Given + val query:String =""" @prepare[select_users]=SELECT * FROM users LIMIT ? """.stripMargin + + //When + val parsed = parser.parseAll(parser.prepare, query) + + //Then + parsed.get should be(PrepareStm("select_users","SELECT * FROM users LIMIT ?")) + } + + "Parser" should "fails parsing invalid prepared statement" in { + val query:String =""" @prepare=SELECT * FROM users LIMIT ?""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.prepare, query) + } + ex.getMessage should be(s"Invalid syntax for @prepare. It should comply to the pattern: @prepare[prepared_statement_name]=CQL Statement (without semi-colon)") + } + + "Parser" should "parse remove prepare" in { + //Given + val query:String =""" @remove_prepare[select_users ]""".stripMargin + + //When + val parsed = parser.parseAll(parser.removePrepare, query) + + //Then + parsed.get should be(RemovePrepareStm("select_users")) + } + + "Parser" should "fails parsing invalid remove prepared statement" in { + val query:String =""" @remove_prepare[select_users]=SELECT * FROM users LIMIT ?""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.removePrepare, query) + } + ex.getMessage should be(s"Invalid syntax for @remove_prepare. It should comply to the pattern: @remove_prepare[prepared_statement_name]") + } + + "Parser" should "parse bind" in { + //Given + val query:String =""" @bind[select_users ]=10,'toto'""".stripMargin + + //When + val parsed = parser.parseAll(parser.bind, query) + + //Then + parsed.get should be(BoundStm("select_users","10,'toto'")) + } + + "Parser" should "fails parsing invalid bind statement" in { + val query:String =""" @bind[select_users]=""".stripMargin + val ex = intercept[InterpreterException] { + parser.parseAll(parser.bind, query) + } + ex.getMessage should be("""Invalid syntax for @bind. It should comply to the pattern: @bind[prepared_statement_name]=10,'jdoe','John DOE',12345,'2015-07-32 12:04:23.234' OR @bind[prepared_statement_name] with no bound value. No semi-colon""") + } + + "Parser" should "parse batch" in { + //Given + val query:String =""" + bEgin Batch + Insert INTO users(id) VALUES(10); + @bind[select_users ]=10,'toto' + update users SET name ='John DOE' WHERE id=10; + dElEtE users WHERE id=11; + APPLY BATCH;""".stripMargin + + //When + val parsed = parser.parseAll(parser.batch, query) + + //Then + parsed.get should be( + BatchStm( + BatchStatement.Type.LOGGED, + List[QueryStatement]( + SimpleStm("Insert INTO users(id) VALUES(10);"), + BoundStm("select_users", "10,'toto'"), + SimpleStm("update users SET name ='John DOE' WHERE id=10;"), + SimpleStm("dElEtE users WHERE id=11;") + ) + ) + ) + } + + "Parser" should "fails parsing invalid batch type" in { + val query:String ="""BEGIN UNKNOWN BATCH""".stripMargin + + val ex = intercept[InterpreterException] { + parser.extractBatchType(query) + } + ex.getMessage should be(s"""Invalid syntax for BEGIN BATCH. It should comply to the pattern: ${BATCH_PATTERN.toString}""") + } + + "Parser" should "parse query parameter with statement" in { + + val query:String = "@serialConsistency=SERIAL\n" + + "SELECT * FROM zeppelin.artists LIMIT 1;" + + val parsed = parser.parseAll(parser.queries, query) + + parsed.get should be (List( + SerialConsistency(ConsistencyLevel.SERIAL), + SimpleStm("SELECT * FROM zeppelin.artists LIMIT 1;") + )) + } + + "Parser" should "parse multi-line single statement" in { + + val query:String = "CREATE TABLE IF NOT EXISTS zeppelin.albums(\n" + + " title text PRIMARY KEY,\n" + + " artist text,\n" + + " year int\n" + + ");\n"; + + val parsed = parser.parseAll(parser.queries, query) + + parsed.get should be (List( + SimpleStm("CREATE TABLE IF NOT EXISTS zeppelin.albums(\n title text PRIMARY KEY,\n artist text,\n year int\n);") + )) + } + + "Parser" should "parse multi-line statements" in { + val query:String = "CREATE TABLE IF NOT EXISTS zeppelin.albums(\n" + + " title text PRIMARY KEY,\n" + + " artist text,\n" + + " year int\n" + + ");\n" + + "@consistency=THREE\n" + + "@serialConsistency=SERIAL\n" + + "BEGIN BATCH\n"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Impossible Dream EP','Carter the Unstoppable Sex Machine',1992);"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Way You Are','Tears for Fears',1983);"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('Primitive','Soulfly',2003);\n"+ + "APPLY BATCH;\n"+ + "@timestamp=10\n" + + "@retryPolicy=DOWNGRADING_CONSISTENCY\n" + + "SELECT * FROM zeppelin.albums;"; + + val parsed = parser.parseAll(parser.queries, query) + + parsed.get should be (List( + SimpleStm("CREATE TABLE IF NOT EXISTS zeppelin.albums(\n title text PRIMARY KEY,\n artist text,\n year int\n);"), + Consistency(ConsistencyLevel.THREE), + SerialConsistency(ConsistencyLevel.SERIAL), + BatchStm(BatchStatement.Type.LOGGED, + List( + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Impossible Dream EP','Carter the Unstoppable Sex Machine',1992);"), + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Way You Are','Tears for Fears',1983);"), + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('Primitive','Soulfly',2003);") + ) + ), + Timestamp(10L), + DowngradingRetryPolicy, + SimpleStm("SELECT * FROM zeppelin.albums;") + )) + } + + "Parser" should "parse mixed single-line and multi-line statements" in { + + val query:String = "CREATE TABLE IF NOT EXISTS zeppelin.albums(\n" + + " title text PRIMARY KEY,\n" + + " artist text,\n" + + " year int\n" + + ");\n" + + "BEGIN BATCH"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Impossible Dream EP','Carter the Unstoppable Sex Machine',1992);"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Way You Are','Tears for Fears',1983);"+ + " INSERT INTO zeppelin.albums(title,artist,year) VALUES('Primitive','Soulfly',2003);\n"+ + "APPLY BATCH;"+ + "SELECT * FROM zeppelin.albums;"; + + val parsed = parser.parseAll(parser.queries, query) + + parsed.get should be (List( + SimpleStm("CREATE TABLE IF NOT EXISTS zeppelin.albums(\n title text PRIMARY KEY,\n artist text,\n year int\n);"), + BatchStm(BatchStatement.Type.LOGGED, + List( + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Impossible Dream EP','Carter the Unstoppable Sex Machine',1992);"), + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('The Way You Are','Tears for Fears',1983);"), + SimpleStm("INSERT INTO zeppelin.albums(title,artist,year) VALUES('Primitive','Soulfly',2003);") + ) + ), + SimpleStm("SELECT * FROM zeppelin.albums;") + )) + } + + "Parser" should "parse a block queries with comments" in { + val query = + """ + /* + This example show how to force a + timestamp on the query + */ + #Timestamp in the past + @timestamp=10 + CREATE TABLE IF NOT EXISTS spark_demo.ts(key int PRIMARY KEY, value text); + + TRUNCATE spark_demo.ts; + + #Force timestamp directly in the first INSERT + INSERT INTO spark_demo.ts(key,value) VALUES(1,'val1') USING TIMESTAMP 100; + + #Select some data to loose some time + SELECT * FROM spark_demo.albums LIMIT 100; + + #Use @timestamp value set at the beginning(10) + INSERT INTO spark_demo.ts(key,value) VALUES(1,'val2'); + + #Check the result + SELECT * FROM spark_demo.ts WHERE key=1; + + """.stripMargin + + val parsed = parser.parseAll(parser.queries, query) + + parsed.get should be (List( + Comment("\n This example show how to force a\n timestamp on the query\n "), + Comment("Timestamp in the past"), + Timestamp(10L), + SimpleStm("CREATE TABLE IF NOT EXISTS spark_demo.ts(key int PRIMARY KEY, value text);"), + SimpleStm("TRUNCATE spark_demo.ts;"), + Comment("Force timestamp directly in the first INSERT"), + SimpleStm("INSERT INTO spark_demo.ts(key,value) VALUES(1,'val1') USING TIMESTAMP 100;"), + Comment("Select some data to loose some time"), + SimpleStm("SELECT * FROM spark_demo.albums LIMIT 100;"), + Comment("Use @timestamp value set at the beginning(10)"), + SimpleStm("INSERT INTO spark_demo.ts(key,value) VALUES(1,'val2');"), + Comment("Check the result"), + SimpleStm("SELECT * FROM spark_demo.ts WHERE key=1;") + ) + ) + } + + "Parser" should "remove prepared statement" in { + val queries = + """ + #Removing an unknown statement should has no side effect + @remove_prepare[unknown_statement] + @remove_prepare[select_artist_by_name] + + #This should fail because the 'select_artist_by_name' has been removed + @bind[select_artist_by_name]='The Beatles' + """.stripMargin + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List( + Comment("Removing an unknown statement should has no side effect"), + RemovePrepareStm("unknown_statement"), + RemovePrepareStm("select_artist_by_name"), + Comment("This should fail because the 'select_artist_by_name' has been removed"), + BoundStm("select_artist_by_name","'The Beatles'") + )) + } + + "Parser" should "parse only parameter" in { + val queries = + "@fetchSize=1000" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(FetchSize(1000))) + } + + + "Parser" should "parse describe cluster" in { + val queries ="Describe ClUsTeR;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get(0) shouldBe a [DescribeClusterCmd] + } + + "Parser" should "fail parsing describe cluster" in { + val queries ="Describe ClUsTeR" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE CLUSTER. It should comply to the pattern: ${DESCRIBE_CLUSTER_PATTERN.toString}") + } + + "Parser" should "parse describe keyspaces" in { + val queries ="Describe KeYsPaCeS;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get(0) shouldBe a [DescribeKeyspacesCmd] + } + + "Parser" should "fail parsing describe keyspaces" in { + val queries ="Describe KeYsPaCeS" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE KEYSPACES. It should comply to the pattern: ${DESCRIBE_KEYSPACES_PATTERN.toString}") + } + + "Parser" should "parse describe tables" in { + val queries ="Describe TaBlEs;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get(0) shouldBe a [DescribeTablesCmd] + } + + "Parser" should "fail parsing describe tables" in { + val queries ="Describe TaBlEs" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE TABLES. It should comply to the pattern: ${DESCRIBE_TABLES_PATTERN.toString}") + } + + "Parser" should "parse describe keyspace" in { + val queries ="Describe KeYsPaCe toto;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(DescribeKeyspaceCmd("toto"))) + } + + "Parser" should "fail parsing describe keyspace" in { + val queries ="Describe KeYsPaCe toto" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE KEYSPACE. It should comply to the pattern: ${DESCRIBE_KEYSPACE_PATTERN.toString}") + } + + "Parser" should "parse describe table" in { + val queries ="Describe TaBlE toto;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(DescribeTableCmd(None,"toto"))) + } + + "Parser" should "parse describe table with keyspace" in { + val queries ="Describe TaBlE ks.toto;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(DescribeTableCmd(Some("ks"),"toto"))) + } + + "Parser" should "fail parsing describe table" in { + val queries ="Describe TaBlE toto" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE TABLE. It should comply to the patterns: " + + s"${DESCRIBE_TABLE_WITH_KEYSPACE_PATTERN.toString} or ${DESCRIBE_TABLE_PATTERN.toString}") + } + + "Parser" should "parse describe type" in { + val queries ="Describe Type toto;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(DescribeUDTCmd(None,"toto"))) + } + + "Parser" should "parse describe type with keyspace" in { + val queries ="Describe Type ks.toto;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get should be(List(DescribeUDTCmd(Some("ks"),"toto"))) + } + + "Parser" should "fail parsing describe type" in { + val queries ="Describe Type toto" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for DESCRIBE TYPE. It should comply to the patterns: " + + s"${DESCRIBE_TYPE_WITH_KEYSPACE_PATTERN.toString} or ${DESCRIBE_TYPE_PATTERN.toString}") + } + + "Parser" should "parse help" in { + val queries ="hElp;" + + val parsed = parser.parseAll(parser.queries, queries) + + parsed.get(0) shouldBe a [HelpCmd] + } + + "Parser" should "fail parsing help" in { + val queries ="HELP" + + val ex = intercept[InterpreterException] { + parser.parseAll(parser.queries, queries) + } + ex.getMessage should be(s"Invalid syntax for HELP. It should comply to the patterns: " + + s"${HELP_PATTERN.toString}") + } +} http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/conf/zeppelin-site.xml.template ---------------------------------------------------------------------- diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template index c2294cb..8d0a7f1 100644 --- a/conf/zeppelin-site.xml.template +++ b/conf/zeppelin-site.xml.template @@ -87,7 +87,7 @@ <property> <name>zeppelin.interpreters</name> - <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter</value> + <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter</value> <description>Comma separated interpreter configurations. First interpreter become a default</description> </property> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index a484405..9e5f54e 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,7 @@ <module>flink</module> <module>ignite</module> <module>lens</module> + <module>cassandra</module> <module>zeppelin-web</module> <module>zeppelin-server</module> <module>zeppelin-distribution</module> http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/b9583c6e/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java ---------------------------------------------------------------------- diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java index d5c8155..086c15e 100644 --- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java +++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java @@ -415,7 +415,8 @@ public class ZeppelinConfiguration extends XMLConfiguration { + "org.apache.zeppelin.flink.FlinkInterpreter," + "org.apache.zeppelin.ignite.IgniteInterpreter," + "org.apache.zeppelin.ignite.IgniteSqlInterpreter," - + "org.apache.zeppelin.lens.LensInterpreter"), + + "org.apache.zeppelin.lens.LensInterpreter," + + "org.apache.zeppelin.cassandra.CassandraInterpreter"), ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"), ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT("zeppelin.interpreter.connect.timeout", 30000), ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),
