From 6457b0bbcb16514933e750453b65bb5aa4697cb4 Mon Sep 17 00:00:00 2001 From: Chen Dai <46505291+dai-chen@users.noreply.github.com> Date: Wed, 8 Apr 2020 15:10:30 -0700 Subject: [PATCH] More docs in reference manual and add architecture doc (#417) * Initial commit for PartiQL and complex query docs * Initial commit for PartiQL and complex query docs * Initial commit for PartiQL and complex query docs * Ignore multi-query for now because of bug * Add test index mappings * Fix partiql doc * Bypass LEFT JOIN for now * Add doc for JOINs * Add doc for JOINs * Add doc for JOINs * Add more cases for PartiQL * Add more cases for PartiQL * Add multi-line support * Add multi-line support * Multi-line all complex queries * Multi-line all complex queries * Add doc for full text search * Add doc for full text search * Add doc for metadata query * Add doc for multi match query * Add doc for delete statement * Remove join explain * Add RDD * Add RDD * Print test data for PartiQL * Print test data for PartiQL * Change titles * Add architecture doc * Add docs for SQL functions * Update index * Update docs * Update docs * Update docs * Address PR comments --- docs/dev/Architecture.md | 39 + docs/dev/img/architecture-journey.png | Bin 0 -> 84086 bytes docs/dev/img/architecture-overview.png | Bin 0 -> 37273 bytes docs/developing.rst | 2 +- docs/user/admin/monitoring.rst | 2 +- docs/user/beyond/fulltext.rst | 517 +++++++++++++ docs/user/beyond/partiql.rst | 295 +++++++ docs/user/dml/delete.rst | 87 +++ docs/user/dql/basics.rst | 75 +- docs/user/dql/complex.rst | 445 +++++++++++ docs/user/dql/functions.rst | 732 ++++++++++++++++++ docs/user/dql/metadata.rst | 116 +++ docs/user/img/rdd/joinPart.png | Bin 0 -> 32167 bytes docs/user/img/rdd/showFilter.png | Bin 0 -> 6355 bytes docs/user/img/rdd/showStatement.png | Bin 0 -> 8542 bytes docs/user/img/rdd/singleDeleteStatement.png | Bin 0 -> 8741 bytes docs/user/img/rdd/tableSource.png | Bin 0 -> 9645 bytes docs/user/index.rst | 14 +- .../sql/doctest/beyond/FullTextIT.java | 144 ++++ .../sql/doctest/beyond/PartiQLIT.java | 153 ++++ .../sql/doctest/core/TestData.java | 26 +- .../sql/doctest/core/builder/DocBuilder.java | 4 + .../core/request/SqlRequestFormat.java | 20 +- .../core/response/SqlResponseFormat.java | 20 + .../sql/doctest/core/test/DocBuilderTest.java | 33 + .../core/test/SqlRequestFormatTest.java | 22 +- .../sql/doctest/dml/DeleteIT.java | 53 ++ .../sql/doctest/dql/BasicQueryIT.java | 63 +- .../sql/doctest/dql/ComplexQueryIT.java | 201 +++++ .../sql/doctest/dql/MetaDataQueryIT.java | 71 ++ .../sql/doctest/dql/SQLFunctionsIT.java | 54 ++ .../resources/doctest/mappings/accounts.json | 44 ++ .../doctest/mappings/employees_nested.json | 44 ++ .../doctest/templates/beyond/fulltext.rst | 16 + .../doctest/templates/beyond/partiql.rst | 16 + .../doctest/templates/dml/delete.rst | 12 + .../doctest/templates/dql/basics.rst | 6 +- .../doctest/templates/dql/complex.rst | 13 + .../doctest/templates/dql/functions.rst | 18 + .../doctest/templates/dql/metadata.rst | 12 + .../doctest/testdata/employees_nested.json | 6 + 41 files changed, 3330 insertions(+), 45 deletions(-) create mode 100644 docs/dev/Architecture.md create mode 100644 docs/dev/img/architecture-journey.png create mode 100644 docs/dev/img/architecture-overview.png create mode 100644 docs/user/beyond/fulltext.rst create mode 100644 docs/user/beyond/partiql.rst create mode 100644 docs/user/dml/delete.rst create mode 100644 docs/user/dql/complex.rst create mode 100644 docs/user/dql/functions.rst create mode 100644 docs/user/dql/metadata.rst create mode 100644 docs/user/img/rdd/joinPart.png create mode 100644 docs/user/img/rdd/showFilter.png create mode 100644 docs/user/img/rdd/showStatement.png create mode 100644 docs/user/img/rdd/singleDeleteStatement.png create mode 100644 docs/user/img/rdd/tableSource.png create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/FullTextIT.java create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/PartiQLIT.java create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dml/DeleteIT.java create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/ComplexQueryIT.java create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/MetaDataQueryIT.java create mode 100644 src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/SQLFunctionsIT.java create mode 100644 src/test/resources/doctest/mappings/accounts.json create mode 100644 src/test/resources/doctest/mappings/employees_nested.json create mode 100644 src/test/resources/doctest/templates/beyond/fulltext.rst create mode 100644 src/test/resources/doctest/templates/beyond/partiql.rst create mode 100644 src/test/resources/doctest/templates/dml/delete.rst create mode 100644 src/test/resources/doctest/templates/dql/complex.rst create mode 100644 src/test/resources/doctest/templates/dql/functions.rst create mode 100644 src/test/resources/doctest/templates/dql/metadata.rst create mode 100644 src/test/resources/doctest/testdata/employees_nested.json diff --git a/docs/dev/Architecture.md b/docs/dev/Architecture.md new file mode 100644 index 0000000000..07c475e49f --- /dev/null +++ b/docs/dev/Architecture.md @@ -0,0 +1,39 @@ +# OpenDistro SQL Engine Architecture + +--- +## 1.Overview + +The OpenDistro SQL (OD-SQL) project is developed based on NLPChina project (https://github.com/NLPchina/elasticsearch-sql) which has been deprecated now ([attributions](https://github.com/opendistro-for-elasticsearch/sql/blob/master/docs/attributions.md)). Over the one year in development, a lot of features have been added to the OD-SQL project on top of the existing older NLPChina project. The purpose of this document is to explain the OD-SQL current architecture going ahead. + +--- +## 2.High Level View + +In the high level, the OD-SQL Engine could be divided into four major sub-module. + +* *Parser*: Currently, there are two Lex&Parser coexists. The Druid Lex&Parser is the original one from NLPChina. The input AST of Core Engine is from the Druid Lex&Parser. The [ANTLR](https://github.com/opendistro-for-elasticsearch/sql/blob/master/src/main/antlr/OpenDistroSqlParser.g4) Lex&Parser is added by us to customized the verification and exception handling. +* *Analyzer*: The analyzer module take the output from ANTLR Lex&Parser then perform syntax and semantic analyze. +* *Core Engine*: The QueryAction take the output from Druid Lex&Parser and translate to the Elasticsearch DSL if possible. This is an NLPChina original module. The QueryPlanner Builder is added by us to support the JOIN and Post-processing logic. The QueryPlanner will take the take the output from Druid Lex&Parser and build the PhysicalPlan +* *Execution*: The execution module execute QueryAction or QueryPlanner and return the response to the client. Different from the Frontend, Analyzer and Core Engine which running on the Transport Thread and can’t do any blocking operation. The Execution module running on the client threadpool and can perform the blocking operation. + +There are also others modules include in the OD-SQL engine. + +* _Documentation_: it is used to auto-generated documentation. +* _Metrics_: it is used to collect OD-SQL related metrics. +* _Resource Manager_: it is used to monitor the memory consumption when performing join operation to avoid the impact to Elasticsearch availability. + +![Architecture Overview](img/architecture-overview.png) + +--- +## 3.Journey of the query in OD-SQL engine. + +The following diagram take a sample query and explain how the query flow within different modules. + +![Architecture Journey](img/architecture-journey.png) + +1. The ANTRL parser based on grammar file (https://github.com/opendistro-for-elasticsearch/sql/blob/master/src/main/antlr/OpenDistroSqlParser.g4) to auto generate the AST. +2. The Syntax and Semantic Analyzer will walk through the AST and verify whether the query is follow the grammar and supported by the OD-SQL. e.g. *SELECT * FROM semantics WHERE LOG(age, city) = 1, *will throw exception with message* Function [LOG] cannot work with [INTEGER, KEYWORD]. *and sample usage message* Usage: LOG(NUMBER T) → DOUBLE. +3. The Druid Lex&Parser takes the input query and generate the druid AST which is different from the AST generated by the ANTRL. This module is the open source library (https://github.com/alibaba/druid) used by NLPChina originally. +4. The QueryPlanner Builder take the AST as input and generate the LogicalPlan from it. Then it optimize the LogicalPlan to PhysicalPlan.(In current implementation, only rule-based model is implemented). The major part of PhysicalPlan generation use NLPChina’s original logic to translate the SQL expression in AST to Elasticsearch DSL. +5. The QueryPlanner executor execute the PhysicalPlan in worker thread. +6. The formatter will reformat the response data to the required format. The default format is JDBC format. + diff --git a/docs/dev/img/architecture-journey.png b/docs/dev/img/architecture-journey.png new file mode 100644 index 0000000000000000000000000000000000000000..71f355f080ec1349615ab134b359ebfe42f53217 GIT binary patch literal 84086 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 VU>^}0)f1lmdc%&sX2}$CNzi<63{sN)V2D_ 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 literal 0 HcmV?d00001 diff --git a/docs/dev/img/architecture-overview.png b/docs/dev/img/architecture-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..cbed41bf42ea93f04e3df2b8c646533c49111b29 GIT binary patch literal 37273 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≪avpFfl%DG0Ir9$_lmrM|Ej4ta4 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%0DR^sTPntKf}L=K7L={ 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 literal 0 HcmV?d00001 diff --git a/docs/developing.rst b/docs/developing.rst index 4f2c435ff9..aadf13330d 100644 --- a/docs/developing.rst +++ b/docs/developing.rst @@ -58,7 +58,7 @@ If there is update in master or you want to keep the forked repository long livi After getting the source code as well as Elasticsearch and Kibana, your workspace layout may look like this:: - $ make opendistro + $ mkdir opendistro $ cd opendistro $ ls -la total 32 diff --git a/docs/user/admin/monitoring.rst b/docs/user/admin/monitoring.rst index b8c3626181..32d588d70b 100644 --- a/docs/user/admin/monitoring.rst +++ b/docs/user/admin/monitoring.rst @@ -52,7 +52,7 @@ Result set:: "failed_request_count_cb" : 0, "failed_request_count_cuserr" : 0, "circuit_breaker" : 0, - "request_total" : 0, + "request_total" : 49, "request_count" : 0, "failed_request_count_syserr" : 0 } diff --git a/docs/user/beyond/fulltext.rst b/docs/user/beyond/fulltext.rst new file mode 100644 index 0000000000..a69e4e92c6 --- /dev/null +++ b/docs/user/beyond/fulltext.rst @@ -0,0 +1,517 @@ + +================ +Full-text Search +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Introduction +============ + +Full-text search is for searching a single stored document which is distinguished from regular search based on original texts in database. It tries to match search criteria by examining all of the words in each document. In Elasticsearch, full-text queries provided enables you to search text fields analyzed during indexing. + +Match Query +=========== + +Description +----------- + +Match query is the standard query for full-text search in Elasticsearch. Both ``MATCHQUERY`` and ``MATCH_QUERY`` are functions for performing match query. + +Example 1 +--------- + +Both functions can accept field name as first argument and a text as second argument. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT account_number, address + FROM accounts + WHERE MATCH_QUERY(address, 'Holmes') + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "match" : { + "address" : { + "query" : "Holmes", + "operator" : "OR", + "prefix_length" : 0, + "max_expansions" : 50, + "fuzzy_transpositions" : true, + "lenient" : false, + "zero_terms_query" : "NONE", + "auto_generate_synonyms_phrase_query" : true, + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "account_number", + "address" + ], + "excludes" : [ ] + } + } + +Result set: + ++--------------+---------------+ +|account_number| address| ++==============+===============+ +| 1|880 Holmes Lane| ++--------------+---------------+ + + +Example 2 +--------- + +Both functions can also accept single argument and be used in the following manner. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT account_number, address + FROM accounts + WHERE address = MATCH_QUERY('Holmes') + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "match" : { + "address" : { + "query" : "Holmes", + "operator" : "OR", + "prefix_length" : 0, + "max_expansions" : 50, + "fuzzy_transpositions" : true, + "lenient" : false, + "zero_terms_query" : "NONE", + "auto_generate_synonyms_phrase_query" : true, + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "account_number", + "address" + ], + "excludes" : [ ] + } + } + +Result set: + ++--------------+---------------+ +|account_number| address| ++==============+===============+ +| 1|880 Holmes Lane| ++--------------+---------------+ + + +Multi-match Query +================= + +Description +----------- + +Besides match query against a single field, you can search for a text with multiple fields. Function ``MULTI_MATCH``, ``MULTIMATCH`` and ``MULTIMATCHQUERY`` are provided for this. + +Example +------- + +Each preceding function accepts ``query`` for a text and ``fields`` for field names or pattern that the text given is searched against. For example, the following query is searching for documents in index accounts with 'Dale' as either firstname or lastname. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT firstname, lastname + FROM accounts + WHERE MULTI_MATCH('query'='Dale', 'fields'='*name') + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "multi_match" : { + "query" : "Dale", + "fields" : [ + "*name^1.0" + ], + "type" : "best_fields", + "operator" : "OR", + "slop" : 0, + "prefix_length" : 0, + "max_expansions" : 50, + "zero_terms_query" : "NONE", + "auto_generate_synonyms_phrase_query" : true, + "fuzzy_transpositions" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "firstname", + "lastname" + ], + "excludes" : [ ] + } + } + +Result set: + ++---------+--------+ +|firstname|lastname| ++=========+========+ +| Dale| Adams| ++---------+--------+ + + +Query String Query +================== + +Description +----------- + +Query string query parses and splits a query string provided based on Lucene query string syntax. The mini language supports logical connectives, wildcard, regex and proximity search. Please refer to official documentation for more details. Note that an error is thrown in the case of any invalid syntax in query string. + +Example +------- + +``QUERY`` function accepts query string and returns true or false respectively for document that matches the query string or not. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT account_number, address + FROM accounts + WHERE QUERY('address:Lane OR address:Street') + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "query_string" : { + "query" : "address:Lane OR address:Street", + "fields" : [ ], + "type" : "best_fields", + "default_operator" : "or", + "max_determinized_states" : 10000, + "enable_position_increments" : true, + "fuzziness" : "AUTO", + "fuzzy_prefix_length" : 0, + "fuzzy_max_expansions" : 50, + "phrase_slop" : 0, + "escape" : false, + "auto_generate_synonyms_phrase_query" : true, + "fuzzy_transpositions" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "account_number", + "address" + ], + "excludes" : [ ] + } + } + +Result set: + ++--------------+------------------+ +|account_number| address| ++==============+==================+ +| 1| 880 Holmes Lane| ++--------------+------------------+ +| 6|671 Bristol Street| ++--------------+------------------+ +| 13|789 Madison Street| ++--------------+------------------+ + + +Match Phrase Query +================== + +Description +----------- + +Match phrase query is similar to match query but it is used for matching exact phrases. ``MATCHPHRASE``, ``MATCH_PHRASE`` and ``MATCHPHRASEQUERY`` are provided for this purpose. + +Example +------- + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT account_number, address + FROM accounts + WHERE MATCH_PHRASE(address, '880 Holmes Lane') + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "match_phrase" : { + "address" : { + "query" : "880 Holmes Lane", + "slop" : 0, + "zero_terms_query" : "NONE", + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "account_number", + "address" + ], + "excludes" : [ ] + } + } + +Result set: + ++--------------+---------------+ +|account_number| address| ++==============+===============+ +| 1|880 Holmes Lane| ++--------------+---------------+ + + +Score Query +=========== + +Description +----------- + +Elasticsearch supports to wrap a filter query so as to return a relevance score along with every matching document. ``SCORE``, ``SCOREQUERY`` and ``SCORE_QUERY`` can be used for this. + +Example +------- + +The first argument is a match query expression and the second argument is for an optional floating point number to boost the score. The default value is 1.0. Apart from this, an implicit variable ``_score`` is available so you can return score for each document or use it for sorting. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT account_number, address, _score + FROM accounts + WHERE SCORE(MATCH_QUERY(address, 'Lane'), 0.5) OR + SCORE(MATCH_QUERY(address, 'Street'), 100) + ORDER BY _score + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "must" : [ + { + "bool" : { + "should" : [ + { + "constant_score" : { + "filter" : { + "match" : { + "address" : { + "query" : "Lane", + "operator" : "OR", + "prefix_length" : 0, + "max_expansions" : 50, + "fuzzy_transpositions" : true, + "lenient" : false, + "zero_terms_query" : "NONE", + "auto_generate_synonyms_phrase_query" : true, + "boost" : 1.0 + } + } + }, + "boost" : 0.5 + } + }, + { + "constant_score" : { + "filter" : { + "match" : { + "address" : { + "query" : "Street", + "operator" : "OR", + "prefix_length" : 0, + "max_expansions" : 50, + "fuzzy_transpositions" : true, + "lenient" : false, + "zero_terms_query" : "NONE", + "auto_generate_synonyms_phrase_query" : true, + "boost" : 1.0 + } + } + }, + "boost" : 100.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "account_number", + "address", + "_score" + ], + "excludes" : [ ] + }, + "sort" : [ + { + "_score" : { + "order" : "asc" + } + } + ] + } + +Result set: + ++--------------+------------------+------+ +|account_number| address|_score| ++==============+==================+======+ +| 1| 880 Holmes Lane| 0.5| ++--------------+------------------+------+ +| 6|671 Bristol Street| 100| ++--------------+------------------+------+ +| 13|789 Madison Street| 100| ++--------------+------------------+------+ + + diff --git a/docs/user/beyond/partiql.rst b/docs/user/beyond/partiql.rst new file mode 100644 index 0000000000..611ae72c54 --- /dev/null +++ b/docs/user/beyond/partiql.rst @@ -0,0 +1,295 @@ + +====================== +PartiQL (JSON) Support +====================== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Introduction +============ + +PartiQL is a SQL-compatible query language that makes it easy and efficient to query semi-structured and nested data regardless of data format. For now our implementation is only partially compatible with PartiQL specification and more support will be provided in future. + +Test Data +========= + +Description +----------- + +The test index ``employees_nested`` used by all examples in this document is very similar to the one used in official PartiQL documentation. + +Example: Employees +------------------ + +Result set:: + + { + "employees" : [ + { + "id" : 3, + "name" : "Bob Smith", + "title" : null, + "projects" : [ + { + "name" : "AWS Redshift Spectrum querying", + "started_year" : 1990 + }, + { + "name" : "AWS Redshift security", + "started_year" : 1999 + }, + { + "name" : "AWS Aurora security", + "started_year" : 2015 + } + ] + }, + { + "id" : 4, + "name" : "Susan Smith", + "title" : "Dev Mgr", + "projects" : [ ] + }, + { + "id" : 6, + "name" : "Jane Smith", + "title" : "Software Eng 2", + "projects" : [ + { + "name" : "AWS Redshift security", + "started_year" : 1998 + }, + { + "name" : "AWS Hello security", + "started_year" : 2015, + "address" : [ + { + "city" : "Dallas", + "state" : "TX" + } + ] + } + ] + } + ] + } + +Querying Nested Collection +========================== + +Description +----------- + +In SQL-92, a database table can only have tuples that consists of scalar values. PartiQL extends SQL-92 to allow you query and unnest nested collection conveniently. In Elasticsearch world, this is very useful for index with object or nested field. + +Example 1: Unnesting a Nested Collection +---------------------------------------- + +In the following example, it finds nested document (project) with field value (name) that satisfies the predicate (contains 'security'). Note that because each parent document can have more than one nested documents, the matched nested document is flattened. In other word, the final result is the Cartesian Product between parent and nested documents. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT e.name AS employeeName, + p.name AS projectName + FROM employees_nested AS e, + e.projects AS p + WHERE p.name LIKE '%security%' + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "nested" : { + "query" : { + "wildcard" : { + "projects.name" : { + "wildcard" : "*security*", + "boost" : 1.0 + } + } + }, + "path" : "projects", + "ignore_unmapped" : false, + "score_mode" : "none", + "boost" : 1.0, + "inner_hits" : { + "ignore_unmapped" : false, + "from" : 0, + "size" : 3, + "version" : false, + "seq_no_primary_term" : false, + "explain" : false, + "track_scores" : false, + "_source" : { + "includes" : [ + "projects.name" + ], + "excludes" : [ ] + } + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "name" + ], + "excludes" : [ ] + } + } + +Result set: + ++------------+---------------------+ +|employeeName| projectName| ++============+=====================+ +| Bob Smith| AWS Aurora security| ++------------+---------------------+ +| Bob Smith|AWS Redshift security| ++------------+---------------------+ +| Jane Smith| AWS Hello security| ++------------+---------------------+ +| Jane Smith|AWS Redshift security| ++------------+---------------------+ + + +Example 2: Unnesting in Existential Subquery +-------------------------------------------- + +Alternatively, a nested collection can be unnested in subquery to check if it satisfies a condition. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT e.name AS employeeName + FROM employees_nested AS e + WHERE EXISTS ( + SELECT * + FROM e.projects AS p + WHERE p.name LIKE '%security%' + ) + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "nested" : { + "query" : { + "bool" : { + "must" : [ + { + "bool" : { + "must" : [ + { + "bool" : { + "must_not" : [ + { + "bool" : { + "must_not" : [ + { + "exists" : { + "field" : "projects", + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + { + "wildcard" : { + "projects.name" : { + "wildcard" : "*security*", + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "path" : "projects", + "ignore_unmapped" : false, + "score_mode" : "none", + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "name" + ], + "excludes" : [ ] + } + } + +Result set: + ++------------+ +|employeeName| ++============+ +| Bob Smith| ++------------+ +| Jane Smith| ++------------+ + + diff --git a/docs/user/dml/delete.rst b/docs/user/dml/delete.rst new file mode 100644 index 0000000000..3d7eb1ee87 --- /dev/null +++ b/docs/user/dml/delete.rst @@ -0,0 +1,87 @@ + +================ +DELETE Statement +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + + +DELETE +====== + +Description +----------- + +``DELETE`` statement deletes documents that satisfy the predicates in ``WHERE`` clause. Note that all documents are deleted in the case of ``WHERE`` clause absent. + +Syntax +------ + +Rule ``singleDeleteStatement``: + +.. image:: /docs/user/img/rdd/singleDeleteStatement.png + +Example +------- + +The ``datarows`` field in this case shows rows impacted, in other words how many documents were just deleted. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + DELETE FROM accounts + WHERE age > 30 + """ + } + +Explain:: + + { + "size" : 1000, + "query" : { + "bool" : { + "must" : [ + { + "range" : { + "age" : { + "from" : 30, + "to" : null, + "include_lower" : false, + "include_upper" : true, + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : false + } + +Result set:: + + { + "schema" : [ + { + "name" : "deleted_rows", + "type" : "long" + } + ], + "total" : 1, + "datarows" : [ + [ + 3 + ] + ], + "size" : 1, + "status" : 200 + } + diff --git a/docs/user/dql/basics.rst b/docs/user/dql/basics.rst index 5ae9c15da2..3b7d0c4746 100644 --- a/docs/user/dql/basics.rst +++ b/docs/user/dql/basics.rst @@ -1,7 +1,7 @@ -=========== -Basic Query -=========== +============= +Basic Queries +============= .. rubric:: Table of contents @@ -313,7 +313,7 @@ WHERE Description ----------- -`WHERE` clause specifies only Elasticsearch documents that meet the criteria should be affected. It consists of predicates that uses ``=``, ``<>``, ``>``, ``>=``, ``<``, ``<=``, ``IN``, ``BETWEEN``, ``LIKE``, ``IS NULL`` or ``IS NOT NULL``. These predicates can be combined by logical operator ``NOT``, ``AND`` or ``OR`` to build more complex expression. +``WHERE`` clause specifies only Elasticsearch documents that meet the criteria should be affected. It consists of predicates that uses ``=``, ``<>``, ``>``, ``>=``, ``<``, ``<=``, ``IN``, ``BETWEEN``, ``LIKE``, ``IS NULL`` or ``IS NOT NULL``. These predicates can be combined by logical operator ``NOT``, ``AND`` or ``OR`` to build more complex expression. For ``LIKE`` and other full text search topics, please refer to Full Text Search documentation. @@ -328,7 +328,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT account_number FROM accounts WHERE account_number = 1" + "query" : """ + SELECT account_number + FROM accounts + WHERE account_number = 1 + """ } Explain:: @@ -388,7 +392,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT account_number, employer FROM accounts WHERE employer IS NULL" + "query" : """ + SELECT account_number, employer + FROM accounts + WHERE employer IS NULL + """ } Explain:: @@ -461,7 +469,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT age FROM accounts GROUP BY age" + "query" : """ + SELECT age + FROM accounts + GROUP BY age + """ } Explain:: @@ -521,7 +533,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT account_number AS num FROM accounts GROUP BY num" + "query" : """ + SELECT account_number AS num + FROM accounts + GROUP BY num + """ } Explain:: @@ -581,7 +597,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT age FROM accounts GROUP BY 1" + "query" : """ + SELECT age + FROM accounts + GROUP BY 1 + """ } Explain:: @@ -641,7 +661,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT ABS(age) AS a FROM accounts GROUP BY ABS(age)" + "query" : """ + SELECT ABS(age) AS a + FROM accounts + GROUP BY ABS(age) + """ } Explain:: @@ -655,9 +679,9 @@ Explain:: ], "excludes" : [ ] }, - "stored_fields" : "a", + "stored_fields" : "abs(age)", "script_fields" : { - "a" : { + "abs(age)" : { "script" : { "source" : "def abs_1 = Math.abs(doc['age'].value);return abs_1;", "lang" : "painless" @@ -666,7 +690,7 @@ Explain:: } }, "aggregations" : { - "a" : { + "abs(age)" : { "terms" : { "script" : { "source" : "def abs_1 = Math.abs(doc['age'].value);return abs_1;", @@ -719,7 +743,12 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT age, MAX(balance) FROM accounts GROUP BY age HAVING MIN(balance) > 10000" + "query" : """ + SELECT age, MAX(balance) + FROM accounts + GROUP BY age + HAVING MIN(balance) > 10000 + """ } Explain:: @@ -856,7 +885,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT employer FROM accounts ORDER BY employer IS NOT NULL" + "query" : """ + SELECT employer + FROM accounts + ORDER BY employer IS NOT NULL + """ } Explain:: @@ -912,7 +945,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT account_number FROM accounts ORDER BY account_number LIMIT 1" + "query" : """ + SELECT account_number + FROM accounts + ORDER BY account_number LIMIT 1 + """ } Explain:: @@ -953,7 +990,11 @@ SQL query:: POST /_opendistro/_sql { - "query" : "SELECT account_number FROM accounts ORDER BY account_number LIMIT 1, 1" + "query" : """ + SELECT account_number + FROM accounts + ORDER BY account_number LIMIT 1, 1 + """ } Explain:: diff --git a/docs/user/dql/complex.rst b/docs/user/dql/complex.rst new file mode 100644 index 0000000000..359779bbe8 --- /dev/null +++ b/docs/user/dql/complex.rst @@ -0,0 +1,445 @@ + +=============== +Complex Queries +=============== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Besides simple SFW queries (SELECT-FROM-WHERE), there is also support for complex queries such as Subquery, ``JOIN``, ``UNION`` and ``MINUS``. For these queries, more than one Elasticsearch index and DSL query is involved. You can check out how they are performed behind the scene by our explain API. + +Subquery +======== + +Description +----------- + +A subquery is a complete ``SELECT`` statement which is used within another statement and enclosed in parenthesis. From the explain output, you can notice that some subquery are actually transformed to an equivalent join query to execute. + +Example 1: Table Subquery +------------------------- + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT a1.firstname, a1.lastname, a1.balance + FROM accounts a1 + WHERE a1.account_number IN ( + SELECT a2.account_number + FROM accounts a2 + WHERE a2.balance > 10000 + ) + """ + } + +Explain:: + + { + "Physical Plan" : { + "Project [ columns=[a1.balance, a1.firstname, a1.lastname] ]" : { + "Top [ count=200 ]" : { + "BlockHashJoin[ conditions=( a1.account_number = a2.account_number ), type=JOIN, blockSize=[FixedBlockSize with size=10000] ]" : { + "Scroll [ accounts as a2, pageSize=10000 ]" : { + "request" : { + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "adjust_pure_negative" : true, + "must" : [ + { + "bool" : { + "adjust_pure_negative" : true, + "must" : [ + { + "bool" : { + "adjust_pure_negative" : true, + "must_not" : [ + { + "bool" : { + "adjust_pure_negative" : true, + "must_not" : [ + { + "exists" : { + "field" : "account_number", + "boost" : 1 + } + } + ], + "boost" : 1 + } + } + ], + "boost" : 1 + } + }, + { + "range" : { + "balance" : { + "include_lower" : false, + "include_upper" : true, + "from" : 10000, + "boost" : 1, + "to" : null + } + } + } + ], + "boost" : 1 + } + } + ], + "boost" : 1 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1 + } + }, + "from" : 0 + } + }, + "Scroll [ accounts as a1, pageSize=10000 ]" : { + "request" : { + "size" : 200, + "from" : 0, + "_source" : { + "excludes" : [ ], + "includes" : [ + "firstname", + "lastname", + "balance", + "account_number" + ] + } + } + }, + "useTermsFilterOptimization" : false + } + } + } + }, + "description" : "Hash Join algorithm builds hash table based on result of first query, and then probes hash table to find matched rows for each row returned by second query", + "Logical Plan" : { + "Project [ columns=[a1.balance, a1.firstname, a1.lastname] ]" : { + "Top [ count=200 ]" : { + "Join [ conditions=( a1.account_number = a2.account_number ) type=JOIN ]" : { + "Group" : [ + { + "Project [ columns=[a1.balance, a1.firstname, a1.lastname, a1.account_number] ]" : { + "TableScan" : { + "tableAlias" : "a1", + "tableName" : "accounts" + } + } + }, + { + "Project [ columns=[a2.account_number] ]" : { + "Filter [ conditions=[AND ( AND account_number ISN null, AND balance GT 10000 ) ] ]" : { + "TableScan" : { + "tableAlias" : "a2", + "tableName" : "accounts" + } + } + } + } + ] + } + } + } + } + } + +Result set: + ++------------+-----------+----------+ +|a1.firstname|a1.lastname|a1.balance| ++============+===========+==========+ +| Amber| Duke| 39225| ++------------+-----------+----------+ +| Nanette| Bates| 32838| ++------------+-----------+----------+ + + +Example 2: Subquery in FROM Clause +---------------------------------- + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT a.f, a.l, a.a + FROM ( + SELECT firstname AS f, lastname AS l, age AS a + FROM accounts + WHERE age > 30 + ) AS a + """ + } + +Explain:: + + { + "from" : 0, + "size" : 200, + "query" : { + "bool" : { + "filter" : [ + { + "bool" : { + "must" : [ + { + "range" : { + "age" : { + "from" : 30, + "to" : null, + "include_lower" : false, + "include_upper" : true, + "boost" : 1.0 + } + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + } + ], + "adjust_pure_negative" : true, + "boost" : 1.0 + } + }, + "_source" : { + "includes" : [ + "firstname", + "lastname", + "age" + ], + "excludes" : [ ] + } + } + +Result set: + ++------+-----+--+ +| f| l| a| ++======+=====+==+ +| Amber| Duke|32| ++------+-----+--+ +| Dale|Adams|33| ++------+-----+--+ +|Hattie| Bond|36| ++------+-----+--+ + + +JOINs +===== + +Description +----------- + +A ``JOIN`` clause combines columns from one or more indices by using values common to each. + +Syntax +------ + +Rule ``tableSource``: + +.. image:: /docs/user/img/rdd/tableSource.png + +Rule ``joinPart``: + +.. image:: /docs/user/img/rdd/joinPart.png + +Example 1: Inner Join +--------------------- + +Inner join is very commonly used that creates a new result set by combining columns of two indices based on the join predicates specified. It iterates both indices and compare each document to find all that satisfy the join predicates. Keyword ``JOIN`` is used and preceded by ``INNER`` keyword optionally. The join predicate(s) is specified by ``ON`` clause. + + Remark that the explain API output for join queries looks complicated. This is because a join query is associated with two Elasticsearch DSL queries underlying and execute in the separate query planner framework. You can interpret it by looking into the logical plan and physical plan. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT + a.account_number, a.firstname, a.lastname, + e.id, e.name + FROM accounts a + JOIN employees_nested e + ON a.account_number = e.id + """ + } + +Explain:: + + { + "Physical Plan" : { + "Project [ columns=[a.account_number, a.firstname, a.lastname, e.name, e.id] ]" : { + "Top [ count=200 ]" : { + "BlockHashJoin[ conditions=( a.account_number = e.id ), type=JOIN, blockSize=[FixedBlockSize with size=10000] ]" : { + "Scroll [ employees_nested as e, pageSize=10000 ]" : { + "request" : { + "size" : 200, + "from" : 0, + "_source" : { + "excludes" : [ ], + "includes" : [ + "id", + "name" + ] + } + } + }, + "Scroll [ accounts as a, pageSize=10000 ]" : { + "request" : { + "size" : 200, + "from" : 0, + "_source" : { + "excludes" : [ ], + "includes" : [ + "account_number", + "firstname", + "lastname" + ] + } + } + }, + "useTermsFilterOptimization" : false + } + } + } + }, + "description" : "Hash Join algorithm builds hash table based on result of first query, and then probes hash table to find matched rows for each row returned by second query", + "Logical Plan" : { + "Project [ columns=[a.account_number, a.firstname, a.lastname, e.name, e.id] ]" : { + "Top [ count=200 ]" : { + "Join [ conditions=( a.account_number = e.id ) type=JOIN ]" : { + "Group" : [ + { + "Project [ columns=[a.account_number, a.firstname, a.lastname] ]" : { + "TableScan" : { + "tableAlias" : "a", + "tableName" : "accounts" + } + } + }, + { + "Project [ columns=[e.name, e.id] ]" : { + "TableScan" : { + "tableAlias" : "e", + "tableName" : "employees_nested" + } + } + } + ] + } + } + } + } + } + +Result set: + ++----------------+-----------+----------+----+----------+ +|a.account_number|a.firstname|a.lastname|e.id| e.name| ++================+===========+==========+====+==========+ +| 6| Hattie| Bond| 6|Jane Smith| ++----------------+-----------+----------+----+----------+ + + +Example 2: Cross Join +--------------------- + +Cross join or Cartesian join combines each document from the first index with each from the second. The result set is the Cartesian Product of documents from both indices. It appears to be similar to inner join without ``ON`` clause to specify join condition. + + Caveat: It is risky to do cross join even on two indices of medium size. This may trigger our circuit breaker to terminate the query to avoid out of memory issue. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT + a.account_number, a.firstname, a.lastname, + e.id, e.name + FROM accounts a + JOIN employees_nested e + """ + } + +Result set: + ++----------------+-----------+----------+----+-----------+ +|a.account_number|a.firstname|a.lastname|e.id| e.name| ++================+===========+==========+====+===========+ +| 1| Amber| Duke| 3| Bob Smith| ++----------------+-----------+----------+----+-----------+ +| 1| Amber| Duke| 4|Susan Smith| ++----------------+-----------+----------+----+-----------+ +| 1| Amber| Duke| 6| Jane Smith| ++----------------+-----------+----------+----+-----------+ +| 6| Hattie| Bond| 3| Bob Smith| ++----------------+-----------+----------+----+-----------+ +| 6| Hattie| Bond| 4|Susan Smith| ++----------------+-----------+----------+----+-----------+ +| 6| Hattie| Bond| 6| Jane Smith| ++----------------+-----------+----------+----+-----------+ +| 13| Nanette| Bates| 3| Bob Smith| ++----------------+-----------+----------+----+-----------+ +| 13| Nanette| Bates| 4|Susan Smith| ++----------------+-----------+----------+----+-----------+ +| 13| Nanette| Bates| 6| Jane Smith| ++----------------+-----------+----------+----+-----------+ +| 18| Dale| Adams| 3| Bob Smith| ++----------------+-----------+----------+----+-----------+ +| 18| Dale| Adams| 4|Susan Smith| ++----------------+-----------+----------+----+-----------+ +| 18| Dale| Adams| 6| Jane Smith| ++----------------+-----------+----------+----+-----------+ + + +Example 3: Outer Join +--------------------- + +Outer join is used to retain documents from one or both indices although it does not satisfy join predicate. For now, only ``LEFT OUTER JOIN`` is supported to retain rows from first index. Note that keyword ``OUTER`` is optional. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : """ + SELECT + a.account_number, a.firstname, a.lastname, + e.id, e.name + FROM accounts a + LEFT JOIN employees_nested e + ON a.account_number = e.id + """ + } + +Result set: + ++----------------+-----------+----------+----+----------+ +|a.account_number|a.firstname|a.lastname|e.id| e.name| ++================+===========+==========+====+==========+ +| 1| Amber| Duke|null| null| ++----------------+-----------+----------+----+----------+ +| 6| Hattie| Bond| 6|Jane Smith| ++----------------+-----------+----------+----+----------+ +| 13| Nanette| Bates|null| null| ++----------------+-----------+----------+----+----------+ +| 18| Dale| Adams|null| null| ++----------------+-----------+----------+----+----------+ + + diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst new file mode 100644 index 0000000000..d048cd10fb --- /dev/null +++ b/docs/user/dql/functions.rst @@ -0,0 +1,732 @@ + +============= +SQL Functions +============= + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + +Introduction +============ + +There is support for a wide variety of SQL functions. We are intend to generate this part of documentation automatically from our type system. However, the type system is missing descriptive information for now. So only formal specifications of all SQL functions supported are listed at the moment. More details will be added in future. + +Most of the specifications can be self explained just as a regular function with data type as argument. The only notation that needs elaboration is generic type ``T`` which binds to an actual type and can be used as return type. For example, ``ABS(NUMBER T) -> T`` means function ``ABS`` accepts an numerical argument of type ``T`` which could be any sub-type of ``NUMBER`` type and returns the actual type of ``T`` as return type. The actual type binds to generic type at runtime dynamically. + +ABS +=== + +Description +----------- + +Specifications: + +1. ABS(NUMBER T) -> T + + +ACOS +==== + +Description +----------- + +Specifications: + +1. ACOS(NUMBER T) -> DOUBLE + + +ADD +=== + +Description +----------- + +Specifications: + +1. ADD(NUMBER T, NUMBER) -> T + + +ASCII +===== + +Description +----------- + +Specifications: + +1. ASCII(STRING T) -> INTEGER + + +ASIN +==== + +Description +----------- + +Specifications: + +1. ASIN(NUMBER T) -> DOUBLE + + +ATAN +==== + +Description +----------- + +Specifications: + +1. ATAN(NUMBER T) -> DOUBLE + + +ATAN2 +===== + +Description +----------- + +Specifications: + +1. ATAN2(NUMBER T, NUMBER) -> DOUBLE + + +CAST +==== + +Description +----------- + +Specification is undefined and type check is skipped for now + +CBRT +==== + +Description +----------- + +Specifications: + +1. CBRT(NUMBER T) -> T + + +CEIL +==== + +Description +----------- + +Specifications: + +1. CEIL(NUMBER T) -> T + + +CONCAT +====== + +Description +----------- + +Specification is undefined and type check is skipped for now + +CONCAT_WS +========= + +Description +----------- + +Specification is undefined and type check is skipped for now + +COS +=== + +Description +----------- + +Specifications: + +1. COS(NUMBER T) -> DOUBLE + + +COSH +==== + +Description +----------- + +Specifications: + +1. COSH(NUMBER T) -> DOUBLE + + +COT +=== + +Description +----------- + +Specifications: + +1. COT(NUMBER T) -> DOUBLE + + +CURDATE +======= + +Description +----------- + +Specifications: + +1. CURDATE() -> DATE + + +DATE +==== + +Description +----------- + +Specifications: + +1. DATE(DATE) -> DATE + + +DATE_FORMAT +=========== + +Description +----------- + +Specifications: + +1. DATE_FORMAT(DATE, STRING) -> STRING +2. DATE_FORMAT(DATE, STRING, STRING) -> STRING + + +DAYOFMONTH +========== + +Description +----------- + +Specifications: + +1. DAYOFMONTH(DATE) -> INTEGER + + +DEGREES +======= + +Description +----------- + +Specifications: + +1. DEGREES(NUMBER T) -> DOUBLE + + +DIVIDE +====== + +Description +----------- + +Specifications: + +1. DIVIDE(NUMBER T, NUMBER) -> T + + +E += + +Description +----------- + +Specifications: + +1. E() -> DOUBLE + + +EXP +=== + +Description +----------- + +Specifications: + +1. EXP(NUMBER T) -> T + + +EXPM1 +===== + +Description +----------- + +Specifications: + +1. EXPM1(NUMBER T) -> T + + +FLOOR +===== + +Description +----------- + +Specifications: + +1. FLOOR(NUMBER T) -> T + + +IF +== + +Description +----------- + +Specifications: + +1. IF(BOOLEAN, ES_TYPE, ES_TYPE) -> ES_TYPE + + +IFNULL +====== + +Description +----------- + +Specifications: + +1. IFNULL(ES_TYPE, ES_TYPE) -> ES_TYPE + + +ISNULL +====== + +Description +----------- + +Specifications: + +1. ISNULL(ES_TYPE) -> INTEGER + + +LEFT +==== + +Description +----------- + +Specifications: + +1. LEFT(STRING T, INTEGER) -> T + + +LENGTH +====== + +Description +----------- + +Specifications: + +1. LENGTH(STRING) -> INTEGER + + +LN +== + +Description +----------- + +Specifications: + +1. LN(NUMBER T) -> DOUBLE + + +LOCATE +====== + +Description +----------- + +Specifications: + +1. LOCATE(STRING, STRING, INTEGER) -> INTEGER +2. LOCATE(STRING, STRING) -> INTEGER + + +LOG +=== + +Description +----------- + +Specifications: + +1. LOG(NUMBER T) -> DOUBLE +2. LOG(NUMBER T, NUMBER) -> DOUBLE + + +LOG2 +==== + +Description +----------- + +Specifications: + +1. LOG2(NUMBER T) -> DOUBLE + + +LOG10 +===== + +Description +----------- + +Specifications: + +1. LOG10(NUMBER T) -> DOUBLE + + +LOWER +===== + +Description +----------- + +Specifications: + +1. LOWER(STRING T) -> T +2. LOWER(STRING T, STRING) -> T + + +LTRIM +===== + +Description +----------- + +Specifications: + +1. LTRIM(STRING T) -> T + + +MAKETIME +======== + +Description +----------- + +Specifications: + +1. MAKETIME(INTEGER, INTEGER, INTEGER) -> DATE + + +MODULUS +======= + +Description +----------- + +Specifications: + +1. MODULUS(NUMBER T, NUMBER) -> T + + +MONTH +===== + +Description +----------- + +Specifications: + +1. MONTH(DATE) -> INTEGER + + +MONTHNAME +========= + +Description +----------- + +Specifications: + +1. MONTHNAME(DATE) -> STRING + + +MULTIPLY +======== + +Description +----------- + +Specifications: + +1. MULTIPLY(NUMBER T, NUMBER) -> NUMBER + + +NOW +=== + +Description +----------- + +Specifications: + +1. NOW() -> DATE + + +PI +== + +Description +----------- + +Specifications: + +1. PI() -> DOUBLE + + +POW +=== + +Description +----------- + +Specifications: + +1. POW(NUMBER T) -> T +2. POW(NUMBER T, NUMBER) -> T + + +POWER +===== + +Description +----------- + +Specifications: + +1. POWER(NUMBER T) -> T +2. POWER(NUMBER T, NUMBER) -> T + + +RADIANS +======= + +Description +----------- + +Specifications: + +1. RADIANS(NUMBER T) -> DOUBLE + + +RAND +==== + +Description +----------- + +Specifications: + +1. RAND() -> NUMBER +2. RAND(NUMBER T) -> T + + +REPLACE +======= + +Description +----------- + +Specifications: + +1. REPLACE(STRING T, STRING, STRING) -> T + + +RIGHT +===== + +Description +----------- + +Specifications: + +1. RIGHT(STRING T, INTEGER) -> T + + +RINT +==== + +Description +----------- + +Specifications: + +1. RINT(NUMBER T) -> T + + +ROUND +===== + +Description +----------- + +Specifications: + +1. ROUND(NUMBER T) -> T + + +RTRIM +===== + +Description +----------- + +Specifications: + +1. RTRIM(STRING T) -> T + + +SIGN +==== + +Description +----------- + +Specifications: + +1. SIGN(NUMBER T) -> T + + +SIGNUM +====== + +Description +----------- + +Specifications: + +1. SIGNUM(NUMBER T) -> T + + +SIN +=== + +Description +----------- + +Specifications: + +1. SIN(NUMBER T) -> DOUBLE + + +SINH +==== + +Description +----------- + +Specifications: + +1. SINH(NUMBER T) -> DOUBLE + + +SQRT +==== + +Description +----------- + +Specifications: + +1. SQRT(NUMBER T) -> T + + +SUBSTRING +========= + +Description +----------- + +Specifications: + +1. SUBSTRING(STRING T, INTEGER, INTEGER) -> T + + +SUBTRACT +======== + +Description +----------- + +Specifications: + +1. SUBTRACT(NUMBER T, NUMBER) -> T + + +TAN +=== + +Description +----------- + +Specifications: + +1. TAN(NUMBER T) -> DOUBLE + + +TIMESTAMP +========= + +Description +----------- + +Specifications: + +1. TIMESTAMP(DATE) -> DATE + + +TRIM +==== + +Description +----------- + +Specifications: + +1. TRIM(STRING T) -> T + + +UPPER +===== + +Description +----------- + +Specifications: + +1. UPPER(STRING T) -> T +2. UPPER(STRING T, STRING) -> T + + +YEAR +==== + +Description +----------- + +Specifications: + +1. YEAR(DATE) -> INTEGER + + diff --git a/docs/user/dql/metadata.rst b/docs/user/dql/metadata.rst new file mode 100644 index 0000000000..bb01bd92af --- /dev/null +++ b/docs/user/dql/metadata.rst @@ -0,0 +1,116 @@ + +================ +Metadata Queries +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + + +Querying Metadata +================= + +Description +----------- + +You can query your indices metadata by ``SHOW`` and ``DESCRIBE`` statement. These commands are very useful for database management tool to enumerate all existing indices and get basic information from the cluster. + +Syntax +------ + +Rule ``showStatement``: + +.. image:: /docs/user/img/rdd/showStatement.png + +Rule ``showFilter``: + +.. image:: /docs/user/img/rdd/showFilter.png + +Example 1: Show All Indices Information +--------------------------------------- + +``SHOW`` statement lists all indices that match the search pattern. By using wildcard '%', information for all indices in the cluster is returned. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : "SHOW TABLES LIKE %" + } + +Result set: + ++---------+-----------+----------------+----------+-------+--------+----------+---------+-------------------------+--------------+ +|TABLE_CAT|TABLE_SCHEM| TABLE_NAME|TABLE_TYPE|REMARKS|TYPE_CAT|TYPE_SCHEM|TYPE_NAME|SELF_REFERENCING_COL_NAME|REF_GENERATION| ++=========+===========+================+==========+=======+========+==========+=========+=========================+==============+ +|integTest| null| accounts|BASE TABLE| null| null| null| null| null| null| ++---------+-----------+----------------+----------+-------+--------+----------+---------+-------------------------+--------------+ +|integTest| null|employees_nested|BASE TABLE| null| null| null| null| null| null| ++---------+-----------+----------------+----------+-------+--------+----------+---------+-------------------------+--------------+ + + +Example 2: Show Specific Index Information +------------------------------------------ + +Here is an example that searches metadata for index name prefixed by 'acc' + +SQL query:: + + POST /_opendistro/_sql + { + "query" : "SHOW TABLES LIKE acc%" + } + +Result set: + ++---------+-----------+----------+----------+-------+--------+----------+---------+-------------------------+--------------+ +|TABLE_CAT|TABLE_SCHEM|TABLE_NAME|TABLE_TYPE|REMARKS|TYPE_CAT|TYPE_SCHEM|TYPE_NAME|SELF_REFERENCING_COL_NAME|REF_GENERATION| ++=========+===========+==========+==========+=======+========+==========+=========+=========================+==============+ +|integTest| null| accounts|BASE TABLE| null| null| null| null| null| null| ++---------+-----------+----------+----------+-------+--------+----------+---------+-------------------------+--------------+ + + +Example 3: Describe Index Fields Information +-------------------------------------------- + +``DESCRIBE`` statement lists all fields for indices that can match the search pattern. + +SQL query:: + + POST /_opendistro/_sql + { + "query" : "DESCRIBE TABLES LIKE accounts" + } + +Result set: + ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|TABLE_CAT|TABLE_SCHEM|TABLE_NAME| COLUMN_NAME|DATA_TYPE|TYPE_NAME|COLUMN_SIZE|BUFFER_LENGTH|DECIMAL_DIGITS|NUM_PREC_RADIX|NULLABLE|REMARKS|COLUMN_DEF|SQL_DATA_TYPE|SQL_DATETIME_SUB|CHAR_OCTET_LENGTH|ORDINAL_POSITION|IS_NULLABLE|SCOPE_CATALOG|SCOPE_SCHEMA|SCOPE_TABLE|SOURCE_DATA_TYPE|IS_AUTOINCREMENT|IS_GENERATEDCOLUMN| ++=========+===========+==========+==============+=========+=========+===========+=============+==============+==============+========+=======+==========+=============+================+=================+================+===========+=============+============+===========+================+================+==================+ +|integTest| null| accounts|account_number| null| long| null| null| null| 10| 2| null| null| null| null| null| 1| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| firstname| null| text| null| null| null| 10| 2| null| null| null| null| null| 2| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| address| null| text| null| null| null| 10| 2| null| null| null| null| null| 3| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| balance| null| long| null| null| null| 10| 2| null| null| null| null| null| 4| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| gender| null| text| null| null| null| 10| 2| null| null| null| null| null| 5| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| city| null| text| null| null| null| 10| 2| null| null| null| null| null| 6| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| employer| null| text| null| null| null| 10| 2| null| null| null| null| null| 7| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| state| null| text| null| null| null| 10| 2| null| null| null| null| null| 8| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| age| null| long| null| null| null| 10| 2| null| null| null| null| null| 9| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| email| null| text| null| null| null| 10| 2| null| null| null| null| null| 10| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ +|integTest| null| accounts| lastname| null| text| null| null| null| 10| 2| null| null| null| null| null| 11| | null| null| null| null| NO| | ++---------+-----------+----------+--------------+---------+---------+-----------+-------------+--------------+--------------+--------+-------+----------+-------------+----------------+-----------------+----------------+-----------+-------------+------------+-----------+----------------+----------------+------------------+ + + diff --git a/docs/user/img/rdd/joinPart.png b/docs/user/img/rdd/joinPart.png new file mode 100644 index 0000000000000000000000000000000000000000..635f4838abc8cbb5231fd9f01c092076c766e566 GIT binary patch literal 32167 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 EKbY57eE%++n%{001b|RF!lA0OBLUyY1~; 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 PAFUcHI?CnGUVr?54){*M literal 0 HcmV?d00001 diff --git a/docs/user/img/rdd/tableSource.png b/docs/user/img/rdd/tableSource.png new file mode 100644 index 0000000000000000000000000000000000000000..f109f44daaec5a78955126b80f18d8f625b59164 GIT binary patch literal 9645 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 literal 0 HcmV?d00001 diff --git a/docs/user/index.rst b/docs/user/index.rst index 04d3c05a8c..028e7b7e76 100644 --- a/docs/user/index.rst +++ b/docs/user/index.rst @@ -19,17 +19,23 @@ Open Distro for Elasticsearch SQL enables you to extract insights out of Elastic * **Data Query Language** - - `Basic Query `_ + - `Basic Queries `_ - - TODO: Subquery, JOIN, MINUS/UNION, SHOW/DESCRIBE, Full Text Search, SQL Functions + - `Complex Queries `_ + + - `Metadata Queries `_ + + - `SQL Functions `_ * **Data Manipulation Language** - - TODO: DELETE + - `DELETE Statement `_ * **Beyond SQL** - - TODO: Elasticsearch features + - `PartiQL (JSON) Support `_ + + - `Full-text Search `_ * **Troubleshooting** diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/FullTextIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/FullTextIT.java new file mode 100644 index 0000000000..190dc2223d --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/FullTextIT.java @@ -0,0 +1,144 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.beyond; + +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; + +@DocTestConfig(template = "beyond/fulltext.rst", testData = {"accounts.json"}) +public class FullTextIT extends DocTest { + + @Section(1) + public void matchQuery() { + section( + title("Match Query"), + description( + "Match query is the standard query for full-text search in Elasticsearch. Both ``MATCHQUERY`` and", + "``MATCH_QUERY`` are functions for performing match query." + ), + example( + description("Both functions can accept field name as first argument and a text as second argument."), + post(multiLine( + "SELECT account_number, address", + "FROM accounts", + "WHERE MATCH_QUERY(address, 'Holmes')" + )) + ), + example( + description("Both functions can also accept single argument and be used in the following manner."), + post(multiLine( + "SELECT account_number, address", + "FROM accounts", + "WHERE address = MATCH_QUERY('Holmes')" + )) + ) + ); + } + + @Section(2) + public void multiMatchQuery() { + section( + title("Multi-match Query"), + description( + "Besides match query against a single field, you can search for a text with multiple fields.", + "Function ``MULTI_MATCH``, ``MULTIMATCH`` and ``MULTIMATCHQUERY`` are provided for this." + ), + example( + description( + "Each preceding function accepts ``query`` for a text and ``fields`` for field names or pattern", + "that the text given is searched against. For example, the following query is searching for", + "documents in index accounts with 'Dale' as either firstname or lastname." + ), + post(multiLine( + "SELECT firstname, lastname", + "FROM accounts", + "WHERE MULTI_MATCH('query'='Dale', 'fields'='*name')" + )) + ) + ); + } + + @Section(3) + public void queryStringQuery() { + section( + title("Query String Query"), + description( + "Query string query parses and splits a query string provided based on Lucene query string syntax.", + "The mini language supports logical connectives, wildcard, regex and proximity search. Please refer", + "to official documentation for more details. Note that an error is thrown in the case of any invalid", + "syntax in query string." + ), + example( + description( + "``QUERY`` function accepts query string and returns true or false respectively for document", + "that matches the query string or not." + ), + post(multiLine( + "SELECT account_number, address", + "FROM accounts", + "WHERE QUERY('address:Lane OR address:Street')" + )) + ) + ); + } + + @Section(4) + public void matchPhraseQuery() { + section( + title("Match Phrase Query"), + description( + "Match phrase query is similar to match query but it is used for matching exact phrases.", + "``MATCHPHRASE``, ``MATCH_PHRASE`` and ``MATCHPHRASEQUERY`` are provided for this purpose." + ), + example( + description(), + post(multiLine( + "SELECT account_number, address", + "FROM accounts", + "WHERE MATCH_PHRASE(address, '880 Holmes Lane')" + )) + ) + ); + } + + @Section(5) + public void scoreQuery() { + section( + title("Score Query"), + description( + "Elasticsearch supports to wrap a filter query so as to return a relevance score along with", + "every matching document. ``SCORE``, ``SCOREQUERY`` and ``SCORE_QUERY`` can be used for this." + ), + example( + description( + "The first argument is a match query expression and the second argument is for an optional", + "floating point number to boost the score. The default value is 1.0. Apart from this, an", + "implicit variable ``_score`` is available so you can return score for each document or", + "use it for sorting." + ), + post(multiLine( + "SELECT account_number, address, _score", + "FROM accounts", + "WHERE SCORE(MATCH_QUERY(address, 'Lane'), 0.5) OR", + " SCORE(MATCH_QUERY(address, 'Street'), 100)", + "ORDER BY _score" + )) + ) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/PartiQLIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/PartiQLIT.java new file mode 100644 index 0000000000..969cbbcfe5 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/beyond/PartiQLIT.java @@ -0,0 +1,153 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.beyond; + +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.builder.Example; +import com.amazon.opendistroforelasticsearch.sql.utils.JsonPrettyFormatter; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.TestData.TEST_DATA_FOLDER_ROOT; +import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils.getResourceFilePath; + +@DocTestConfig(template = "beyond/partiql.rst", testData = {"employees_nested.json"}) +public class PartiQLIT extends DocTest { + + @Section(1) + public void showTestData() { + section( + title("Test Data"), + description( + "The test index ``employees_nested`` used by all examples in this document is very similar to", + "the one used in official PartiQL documentation." + ), + createDummyExampleForTestData("employees_nested.json") + ); + } + + @Section(2) + public void queryNestedCollection() { + section( + title("Querying Nested Collection"), + description( + "In SQL-92, a database table can only have tuples that consists of scalar values.", + "PartiQL extends SQL-92 to allow you query and unnest nested collection conveniently.", + "In Elasticsearch world, this is very useful for index with object or nested field." + ), + example( + title("Unnesting a Nested Collection"), + description( + "In the following example, it finds nested document (project) with field value (name)", + "that satisfies the predicate (contains 'security'). Note that because each parent document", + "can have more than one nested documents, the matched nested document is flattened. In other", + "word, the final result is the Cartesian Product between parent and nested documents." + ), + post(multiLine( + "SELECT e.name AS employeeName,", + " p.name AS projectName", + "FROM employees_nested AS e,", + " e.projects AS p", + "WHERE p.name LIKE '%security%'" + )) + ), + /* + Issue: https://github.com/opendistro-for-elasticsearch/sql/issues/397 + example( + title("Preserving Parent Information with LEFT JOIN"), + description( + "The query in the preceding example is very similar to traditional join queries, except ``ON`` clause missing.", + "This is because it is implicitly in the nesting of nested documents (projects) into parent (employee). Therefore,", + "you can use ``LEFT JOIN`` to preserve the information in parent document associated." + ), + post( + "SELECT e.id AS id, " + + " e.name AS employeeName, " + + " e.title AS title, " + + " p.name AS projectName " + + "FROM employees_nested AS e " + + "LEFT JOIN e.projects AS p" + ) + )*/ + example( + title("Unnesting in Existential Subquery"), + description( + "Alternatively, a nested collection can be unnested in subquery to check if it", + "satisfies a condition." + ), + post(multiLine( + "SELECT e.name AS employeeName", + "FROM employees_nested AS e", + "WHERE EXISTS (", + " SELECT *", + " FROM e.projects AS p", + " WHERE p.name LIKE '%security%'", + ")" + )) + )/*, + Issue: https://github.com/opendistro-for-elasticsearch/sql/issues/398 + example( + title("Aggregating over a Nested Collection"), + description( + "After unnested, a nested collection can be aggregated just like a regular field." + ), + post(multiLine( + "SELECT", + " e.name AS employeeName,", + " COUNT(p) AS cnt", + "FROM employees_nested AS e,", + " e.projects AS p", + "WHERE p.name LIKE '%security%'", + "GROUP BY e.id, e.name", + "HAVING COUNT(p) >= 1" + ) + )) + */ + ); + } + + private Example createDummyExampleForTestData(String fileName) { + Example example = new Example(); + example.setTitle("Employees"); + example.setDescription(""); + example.setResult(parseJsonFromTestData(fileName)); + return example; + } + + /** Concat and pretty format document at odd number line in bulk request file */ + private String parseJsonFromTestData(String fileName) { + Path path = Paths.get(getResourceFilePath(TEST_DATA_FOLDER_ROOT + fileName)); + try { + List lines = Files.readAllLines(path); + String json = IntStream.range(0, lines.size()). + filter(i -> i % 2 == 1). + mapToObj(lines::get). + collect(Collectors.joining(",","{\"employees\":[", "]}")); + return JsonPrettyFormatter.format(json); + } catch (IOException e) { + throw new IllegalStateException("Failed to load test data: " + path, e); + } + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/TestData.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/TestData.java index 9d8fb33fa4..4b2e1788c4 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/TestData.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/TestData.java @@ -15,16 +15,24 @@ package com.amazon.opendistroforelasticsearch.sql.doctest.core; -import com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils; import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils.createIndexByRestClient; +import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils.getResourceFilePath; +import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils.loadDataByRestClient; /** * Test data for document generation */ public class TestData { + public static final String MAPPINGS_FOLDER_ROOT = "src/test/resources/doctest/mappings/"; public static final String TEST_DATA_FOLDER_ROOT = "src/test/resources/doctest/testdata/"; private final String[] testFilePaths; @@ -39,13 +47,15 @@ public TestData(String[] testFilePaths) { */ public void loadToES(DocTest test) { for (String filePath : testFilePaths) { + String indexName = indexName(filePath); try { - TestUtils.loadDataByRestClient(test.restClient(), indexName(filePath), TEST_DATA_FOLDER_ROOT + filePath); + createIndexByRestClient(test.restClient(), indexName, getIndexMapping(filePath)); + loadDataByRestClient(test.restClient(), indexName, TEST_DATA_FOLDER_ROOT + filePath); } catch (Exception e) { throw new IllegalStateException(StringUtils.format( - "Failed to load test filePath from %s", filePath), e); + "Failed to load mapping and test filePath from %s", filePath), e); } - test.ensureGreen(indexName(filePath)); + test.ensureGreen(indexName); } } @@ -60,4 +70,12 @@ private String indexName(String filePath) { ); } + private String getIndexMapping(String filePath) throws IOException { + Path path = Paths.get(getResourceFilePath(MAPPINGS_FOLDER_ROOT + filePath)); + if (Files.notExists(path)) { + return ""; + } + return new String(Files.readAllBytes(path)); + } + } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/builder/DocBuilder.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/builder/DocBuilder.java index ea26a3bcd0..6f7c091e76 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/builder/DocBuilder.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/builder/DocBuilder.java @@ -198,6 +198,10 @@ default Requests put(String name, Object value) { ); } + default String multiLine(String... lines) { + return String.join("\\n", lines); + } + /** Query by a simple SQL is too common and deserve a dedicated overload method */ default Requests post(String sql, UrlParam... params) { return post(new Body("\"query\":\"" + sql + "\""), params); diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/request/SqlRequestFormat.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/request/SqlRequestFormat.java index ffc3c7ac43..c6e32d9f8c 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/request/SqlRequestFormat.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/request/SqlRequestFormat.java @@ -21,13 +21,16 @@ import com.google.common.io.CharStreams; import org.apache.http.Header; import org.elasticsearch.client.Request; +import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; + +import static java.util.stream.Collectors.joining; /** * Different SQL request formats. @@ -51,7 +54,7 @@ public String format(SqlRequest sqlRequest) { if (!headers.isEmpty()) { str.append(headers.stream(). map(header -> StringUtils.format("-H '%s: %s'", header.getName(), header.getValue())). - collect(Collectors.joining(" ", "", " "))); + collect(joining(" ", "", " "))); } str.append(StringUtils.format("-X %s ", request.getMethod())). @@ -103,7 +106,18 @@ protected String body(Request request) { InputStream content = request.getEntity().getContent(); String rawBody = CharStreams.toString(new InputStreamReader(content, Charsets.UTF_8)); if (!rawBody.isEmpty()) { + JSONObject json = new JSONObject(rawBody); + String sql = json.optString("query"); // '\\n' in literal is replaced by '\n' after unquote body = JsonPrettyFormatter.format(rawBody); + + // Format and replace multi-line sql literal + if (!sql.isEmpty() && sql.contains("\n")) { + String multiLineSql = Arrays.stream(sql.split("\\n")). // '\\n' is to escape backslash in regex + collect(joining("\n\t", + "\"\"\"\n\t", + "\n\t\"\"\"")); + body = body.replace("\"" + sql.replace("\n", "\\n") + "\"", multiLineSql); + } } } catch (IOException e) { throw new IllegalStateException("Failed to parse and format body from request", e); @@ -114,6 +128,6 @@ protected String body(Request request) { protected String formatParams(Map params) { return params.entrySet().stream(). map(e -> e.getKey() + "=" + e.getValue()). - collect(Collectors.joining("&", "?", "")); + collect(joining("&", "?", "")); } } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/response/SqlResponseFormat.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/response/SqlResponseFormat.java index 9fb41c0f39..b38f413a48 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/response/SqlResponseFormat.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/response/SqlResponseFormat.java @@ -119,10 +119,19 @@ private List parseDataRows(JSONArray rows, boolean isSorted) { @SuppressWarnings({"rawtypes", "unchecked"}) private static void sort(List lists) { lists.sort((list1, list2) -> { + if (list1 == null || list2 == null) { + return compareNullable(list1, list2); + } + // Assume 2 lists are of same length and all elements are comparable for (int i = 0; i < list1.length; i++) { Comparable obj1 = (Comparable) list1[i]; Comparable obj2 = (Comparable) list2[i]; + + if (obj1 == null || obj2 == null) { + return compareNullable(obj1, obj2); + } + int result = obj1.compareTo(obj2); if (result != 0) { return result; @@ -132,4 +141,15 @@ private static void sort(List lists) { }); } + /** Put NULL first (as smaller element) */ + private static int compareNullable(Object obj1, Object obj2) { + if (obj1 == null && obj2 == null) { + return 0; + } else if (obj1 == null) { + return -1; + } else { // obj2 == null + return 1; + } + } + } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/DocBuilderTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/DocBuilderTest.java index 5ef7fb379f..42b5466854 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/DocBuilderTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/DocBuilderTest.java @@ -96,6 +96,39 @@ public void sectionShouldIncludeTitleAndDescription() { paragraph("This is a test"); } + @Test + public void sectionShouldIncludeMultiLineSql() { + section( + title("Test"), + description("This is a test"), + example( + description("This is an example for the test"), + post(multiLine( + "SELECT firstname", + "FROM accounts", + "WHERE age > 30") + ) + ) + ); + + verifier.section("Test"). + subSection("Description"). + paragraph("This is a test"). + subSection("Example"). + paragraph("This is an example for the test"). + codeBlock( + "SQL query", + "POST /_opendistro/_sql\n" + + "{\n" + + " \"query\" : \"\"\"\n" + + "\tSELECT firstname\n" + + "\tFROM accounts\n" + + "\tWHERE age > 30\n" + + "\t\"\"\"\n" + + "}" + ); + } + @Test public void sectionShouldIncludeExample() { section( diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/SqlRequestFormatTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/SqlRequestFormatTest.java index 681451b706..ddcde16608 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/SqlRequestFormatTest.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/core/test/SqlRequestFormatTest.java @@ -21,8 +21,8 @@ import org.junit.Test; import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.CURL_REQUEST; -import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.KIBANA_REQUEST; import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.IGNORE_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.KIBANA_REQUEST; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -63,4 +63,24 @@ public void testKibanaFormat() { assertThat(KIBANA_REQUEST.format(sqlRequest), is(expected)); } + @Test + public void multiLineSqlInKibanaRequestShouldBeWellFormatted() { + SqlRequest multiLineSqlRequest = new SqlRequest( + "POST", + "/_opendistro/_sql", + "{\"query\":\"SELECT *\\nFROM accounts\\nWHERE age > 30\"}" + ); + + String expected = + "POST /_opendistro/_sql\n" + + "{\n" + + " \"query\" : \"\"\"\n" + + "\tSELECT *\n" + + "\tFROM accounts\n" + + "\tWHERE age > 30\n" + + "\t\"\"\"\n" + + "}"; + assertThat(KIBANA_REQUEST.format(multiLineSqlRequest), is(expected)); + } + } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dml/DeleteIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dml/DeleteIT.java new file mode 100644 index 0000000000..8f7ef547b3 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dml/DeleteIT.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.dml; + +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; + +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.IGNORE_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.KIBANA_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.response.SqlResponseFormat.PRETTY_JSON_RESPONSE; + +@DocTestConfig(template = "dml/delete.rst", testData = {"accounts.json"}) +public class DeleteIT extends DocTest { + + @Section(1) + public void delete() { + section( + title("DELETE"), + description( + "``DELETE`` statement deletes documents that satisfy the predicates in ``WHERE`` clause.", + "Note that all documents are deleted in the case of ``WHERE`` clause absent." + ), + images("rdd/singleDeleteStatement.png"), + example( + description( + "The ``datarows`` field in this case shows rows impacted, in other words how many", + "documents were just deleted." + ), + post(multiLine( + "DELETE FROM accounts", + "WHERE age > 30" + )), + queryFormat(KIBANA_REQUEST, PRETTY_JSON_RESPONSE), + explainFormat(IGNORE_REQUEST, PRETTY_JSON_RESPONSE) + ) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/BasicQueryIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/BasicQueryIT.java index 6d5183ed14..94cd41918e 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/BasicQueryIT.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/BasicQueryIT.java @@ -115,7 +115,7 @@ public void where() { section( title("WHERE"), description( - "`WHERE` clause specifies only Elasticsearch documents that meet the criteria should be affected.", + "``WHERE`` clause specifies only Elasticsearch documents that meet the criteria should be affected.", "It consists of predicates that uses ``=``, ``<>``, ``>``, ``>=``, ``<``, ``<=``, ``IN``,", "``BETWEEN``, ``LIKE``, ``IS NULL`` or ``IS NOT NULL``. These predicates can be combined by", "logical operator ``NOT``, ``AND`` or ``OR`` to build more complex expression.\n\n" + @@ -130,7 +130,11 @@ public void where() { "number, string or date.", "``IN`` and ``BETWEEN`` is convenient for comparison with multiple values or a range." ), - post("SELECT account_number FROM accounts WHERE account_number = 1") + post(multiLine( + "SELECT account_number", + "FROM accounts", + "WHERE account_number = 1" + )) ), example( title("Missing Fields"), @@ -140,7 +144,11 @@ public void where() { "fields or existing fields only.\n\n" + "Note that for now we don't differentiate missing field and field set to ``NULL`` explicitly." ), - post("SELECT account_number, employer FROM accounts WHERE employer IS NULL") + post(multiLine( + "SELECT account_number, employer", + "FROM accounts", + "WHERE employer IS NULL" + )) ) ); } @@ -158,12 +166,20 @@ public void groupBy() { example( title("Grouping by Fields"), description(), - post("SELECT age FROM accounts GROUP BY age") + post(multiLine( + "SELECT age", + "FROM accounts", + "GROUP BY age" + )) ), example( title("Grouping by Field Alias"), description("Field alias is accessible in ``GROUP BY`` clause."), - post("SELECT account_number AS num FROM accounts GROUP BY num") + post(multiLine( + "SELECT account_number AS num", + "FROM accounts", + "GROUP BY num" + )) ), example( title("Grouping by Ordinal"), @@ -172,7 +188,11 @@ public void groupBy() { "recommended because your ``GROUP BY`` clause depends on fields in ``SELECT`` clause", "and require to change accordingly." ), - post("SELECT age FROM accounts GROUP BY 1") + post(multiLine( + "SELECT age", + "FROM accounts", + "GROUP BY 1" + )) ), example( title("Grouping by Scalar Function"), @@ -180,7 +200,11 @@ public void groupBy() { "Scalar function can be used in ``GROUP BY`` clause and it's required to be present in", "``SELECT`` clause too." ), - post("SELECT ABS(age) AS a FROM accounts GROUP BY ABS(age)") + post(multiLine( + "SELECT ABS(age) AS a", + "FROM accounts", + "GROUP BY ABS(age)" + )) ) ); } @@ -195,7 +219,12 @@ public void having() { ), example( description(), - post("SELECT age, MAX(balance) FROM accounts GROUP BY age HAVING MIN(balance) > 10000") + post(multiLine( + "SELECT age, MAX(balance)", + "FROM accounts", + "GROUP BY age", + "HAVING MIN(balance) > 10000" + )) ) ); } @@ -221,7 +250,11 @@ public void orderBy() { "The default behavior of Elasticsearch is to return nulls or missing last.", "You can make them present before non-nulls by using ``IS NOT NULL``." ), - post("SELECT employer FROM accounts ORDER BY employer IS NOT NULL") + post(multiLine( + "SELECT employer", + "FROM accounts", + "ORDER BY employer IS NOT NULL" + )) ) ); } @@ -239,7 +272,11 @@ public void limit() { description( "Given a positive number, ``LIMIT`` uses it as page size to fetch result of that size at most." ), - post("SELECT account_number FROM accounts ORDER BY account_number LIMIT 1") + post(multiLine( + "SELECT account_number", + "FROM accounts", + "ORDER BY account_number LIMIT 1" + )) ), example( title("Fetching at Offset"), @@ -248,7 +285,11 @@ public void limit() { "This can be used as simple pagination solution though it's inefficient on large index.", "Generally ``ORDER BY`` is required in this case to ensure the same order between pages." ), - post("SELECT account_number FROM accounts ORDER BY account_number LIMIT 1, 1") + post(multiLine( + "SELECT account_number", + "FROM accounts", + "ORDER BY account_number LIMIT 1, 1" + )) ) ); } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/ComplexQueryIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/ComplexQueryIT.java new file mode 100644 index 0000000000..aa28b2ca21 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/ComplexQueryIT.java @@ -0,0 +1,201 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.dql; + +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.builder.Example; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.builder.Requests; +import org.junit.Ignore; + +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.IGNORE_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.KIBANA_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.response.SqlResponseFormat.IGNORE_RESPONSE; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.response.SqlResponseFormat.TABLE_RESPONSE; + +@DocTestConfig(template = "dql/complex.rst", testData = {"accounts.json", "employees_nested.json"}) +public class ComplexQueryIT extends DocTest { + + @Section(1) + public void subquery() { + section( + title("Subquery"), + description( + "A subquery is a complete ``SELECT`` statement which is used within another statement", + "and enclosed in parenthesis. From the explain output, you can notice that some subquery", + "are actually transformed to an equivalent join query to execute." + ), + /* + example( + title("Scalar Value Subquery"), + description( + "" + ), + post( + "SELECT firstname, lastname, balance " + + "FROM accounts " + + "WHERE balance >= ( " + + " SELECT AVG(balance) FROM accounts " + + ") " + ) + ),*/ + example( + title("Table Subquery"), + description(""), + post(multiLine( + "SELECT a1.firstname, a1.lastname, a1.balance", + "FROM accounts a1", + "WHERE a1.account_number IN (", + " SELECT a2.account_number", + " FROM accounts a2", + " WHERE a2.balance > 10000", + ")" + )) + ), + example( + title("Subquery in FROM Clause"), + description(""), + post(multiLine( + "SELECT a.f, a.l, a.a", + "FROM (", + " SELECT firstname AS f, lastname AS l, age AS a", + " FROM accounts", + " WHERE age > 30", + ") AS a" + )) + ) + ); + } + + @Section(2) + public void joins() { + section( + title("JOINs"), + description( + "A ``JOIN`` clause combines columns from one or more indices by using values common to each." + ), + images("rdd/tableSource.png", "rdd/joinPart.png"), + example( + title("Inner Join"), + description( + "Inner join is very commonly used that creates a new result set by combining columns", + "of two indices based on the join predicates specified. It iterates both indices and", + "compare each document to find all that satisfy the join predicates. Keyword ``JOIN``", + "is used and preceded by ``INNER`` keyword optionally. The join predicate(s) is specified", + "by ``ON`` clause.\n\n", + "Remark that the explain API output for join queries looks complicated. This is because", + "a join query is associated with two Elasticsearch DSL queries underlying and execute in", + "the separate query planner framework. You can interpret it by looking into the logical", + "plan and physical plan." + ), + post(multiLine( + "SELECT", + " a.account_number, a.firstname, a.lastname,", + " e.id, e.name", + "FROM accounts a", + "JOIN employees_nested e", + " ON a.account_number = e.id" + )) + ), + joinExampleWithoutExplain( + title("Cross Join"), + description( + "Cross join or Cartesian join combines each document from the first index with each from", + "the second. The result set is the Cartesian Product of documents from both indices.", + "It appears to be similar to inner join without ``ON`` clause to specify join condition.\n\n", + "Caveat: It is risky to do cross join even on two indices of medium size. This may trigger", + "our circuit breaker to terminate the query to avoid out of memory issue." + ), + post(multiLine( + "SELECT", + " a.account_number, a.firstname, a.lastname,", + " e.id, e.name", + "FROM accounts a", + "JOIN employees_nested e" + )) + ), + joinExampleWithoutExplain( + title("Outer Join"), + description( + "Outer join is used to retain documents from one or both indices although it does not satisfy", + "join predicate. For now, only ``LEFT OUTER JOIN`` is supported to retain rows from first index.", + "Note that keyword ``OUTER`` is optional." + ), + post(multiLine( + "SELECT", + " a.account_number, a.firstname, a.lastname,", + " e.id, e.name", + "FROM accounts a", + "LEFT JOIN employees_nested e", + " ON a.account_number = e.id" + )) + ) + ); + } + + @Ignore("Multi-query doesn't work for default format: https://github.com/opendistro-for-elasticsearch/sql/issues/388") + @Section(3) + public void setOperations() { + section( + title("Set Operations"), + description( + "Set operations allow results of multiple queries to be combined into a single result set.", + "The results to be combined are required to be of same type. In other word, they require to", + "have same column. Otherwise, a semantic analysis exception is raised." + ), + example( + title("UNION Operator"), + description( + "A ``UNION`` clause combines the results of two queries into a single result set. Duplicate rows", + "are removed unless ``UNION ALL`` clause is being used. A common use case of ``UNION`` is to combine", + "result set from data partitioned in indices daily or monthly." + ), + post(multiLine( + "SELECT balance, firstname, lastname", + "FROM accounts WHERE balance < 10000", + "UNION", + "SELECT balance, firstname, lastname", + "FROM accounts WHERE balance > 30000" + )) + ), + example( + title("MINUS Operator"), + description( + "A ``MINUS`` clause takes two queries too but returns resulting rows of first query that", + "do not appear in the other query. Duplicate rows are removed automatically as well." + ), + post(multiLine( + "SELECT balance, age", + "FROM accounts", + "WHERE balance < 10000", + "MINUS", + "SELECT balance, age", + "FROM accounts", + "WHERE age < 35" + )) + ) + ); + } + + private Example joinExampleWithoutExplain(String title, String description, Requests requests) { + return example(title, description, requests, + queryFormat(KIBANA_REQUEST, TABLE_RESPONSE), + explainFormat(IGNORE_REQUEST, IGNORE_RESPONSE) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/MetaDataQueryIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/MetaDataQueryIT.java new file mode 100644 index 0000000000..9850e2397f --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/MetaDataQueryIT.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.dql; + +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.builder.Example; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.builder.Requests; + +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.IGNORE_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.request.SqlRequestFormat.KIBANA_REQUEST; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.response.SqlResponseFormat.IGNORE_RESPONSE; +import static com.amazon.opendistroforelasticsearch.sql.doctest.core.response.SqlResponseFormat.TABLE_RESPONSE; + +@DocTestConfig(template = "dql/metadata.rst", testData = {"accounts.json", "employees_nested.json"}) +public class MetaDataQueryIT extends DocTest { + + @Section(1) + public void queryMetaData() { + section( + title("Querying Metadata"), + description( + "You can query your indices metadata by ``SHOW`` and ``DESCRIBE`` statement. These commands are", + "very useful for database management tool to enumerate all existing indices and get basic information", + "from the cluster." + ), + images("rdd/showStatement.png", "rdd/showFilter.png"), + metadataQueryExample( + title("Show All Indices Information"), + description( + "``SHOW`` statement lists all indices that match the search pattern. By using wildcard '%',", + "information for all indices in the cluster is returned." + ), + post("SHOW TABLES LIKE %") + ), + metadataQueryExample( + title("Show Specific Index Information"), + description("Here is an example that searches metadata for index name prefixed by 'acc'"), + post("SHOW TABLES LIKE acc%") + ), + metadataQueryExample( + title("Describe Index Fields Information"), + description("``DESCRIBE`` statement lists all fields for indices that can match the search pattern."), + post("DESCRIBE TABLES LIKE accounts") + ) + ); + } + + /** Explain doesn't work for SHOW/DESCRIBE so skip it */ + private Example metadataQueryExample(String title, String description, Requests requests) { + return example(title, description, requests, + queryFormat(KIBANA_REQUEST, TABLE_RESPONSE), + explainFormat(IGNORE_REQUEST, IGNORE_RESPONSE) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/SQLFunctionsIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/SQLFunctionsIT.java new file mode 100644 index 0000000000..29a1d6e7de --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/doctest/dql/SQLFunctionsIT.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.doctest.dql; + +import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.function.ScalarFunction; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.DocTest; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.DocTestConfig; +import com.amazon.opendistroforelasticsearch.sql.doctest.core.annotation.Section; +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; + +import static com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.TypeExpression.TypeExpressionSpec; + +@DocTestConfig(template = "dql/functions.rst") +public class SQLFunctionsIT extends DocTest { + + /** List only specifications of all SQL functions supported for now */ + @Section + public void listFunctions() { + for (ScalarFunction func : ScalarFunction.values()) { // Java Enum.values() return enums in order they are defined + section( + title(func.getName()), + description(listFunctionSpecs(func)) + ); + } + } + + private String listFunctionSpecs(ScalarFunction func) { + TypeExpressionSpec[] specs = func.specifications(); + if (specs.length == 0) { + return "Specification is undefined and type check is skipped for now"; + } + + StringBuilder specStr = new StringBuilder("Specifications: \n\n"); + for (int i = 0; i < specs.length; i++) { + specStr.append( + StringUtils.format("%d. %s%s\n", (i + 1), func.getName(), specs[i]) + ); + } + return specStr.toString(); + } +} diff --git a/src/test/resources/doctest/mappings/accounts.json b/src/test/resources/doctest/mappings/accounts.json new file mode 100644 index 0000000000..de9930778d --- /dev/null +++ b/src/test/resources/doctest/mappings/accounts.json @@ -0,0 +1,44 @@ +{ + "mappings": { + "properties": { + "gender": { + "type": "text", + "fielddata": true + }, + "address": { + "type": "text", + "fielddata": true + }, + "firstname": { + "type": "text", + "fielddata": true, + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "lastname": { + "type": "text", + "fielddata": true, + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "state": { + "type": "text", + "fielddata": true, + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/doctest/mappings/employees_nested.json b/src/test/resources/doctest/mappings/employees_nested.json new file mode 100644 index 0000000000..1805628b39 --- /dev/null +++ b/src/test/resources/doctest/mappings/employees_nested.json @@ -0,0 +1,44 @@ +{ + "mappings": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "projects": { + "type": "nested", + "properties": { + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + }, + "fielddata": true + }, + "started_year": { + "type": "long" + } + } + }, + "title": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/doctest/templates/beyond/fulltext.rst b/src/test/resources/doctest/templates/beyond/fulltext.rst new file mode 100644 index 0000000000..c7fc3e10f9 --- /dev/null +++ b/src/test/resources/doctest/templates/beyond/fulltext.rst @@ -0,0 +1,16 @@ + +================ +Full-text Search +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Introduction +============ + +Full-text search is for searching a single stored document which is distinguished from regular search based on original texts in database. It tries to match search criteria by examining all of the words in each document. In Elasticsearch, full-text queries provided enables you to search text fields analyzed during indexing. + diff --git a/src/test/resources/doctest/templates/beyond/partiql.rst b/src/test/resources/doctest/templates/beyond/partiql.rst new file mode 100644 index 0000000000..ec71e9e296 --- /dev/null +++ b/src/test/resources/doctest/templates/beyond/partiql.rst @@ -0,0 +1,16 @@ + +====================== +PartiQL (JSON) Support +====================== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Introduction +============ + +PartiQL is a SQL-compatible query language that makes it easy and efficient to query semi-structured and nested data regardless of data format. For now our implementation is only partially compatible with PartiQL specification and more support will be provided in future. + diff --git a/src/test/resources/doctest/templates/dml/delete.rst b/src/test/resources/doctest/templates/dml/delete.rst new file mode 100644 index 0000000000..11188fa943 --- /dev/null +++ b/src/test/resources/doctest/templates/dml/delete.rst @@ -0,0 +1,12 @@ + +================ +DELETE Statement +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + + diff --git a/src/test/resources/doctest/templates/dql/basics.rst b/src/test/resources/doctest/templates/dql/basics.rst index 4837573cf1..34cd5d3d09 100644 --- a/src/test/resources/doctest/templates/dql/basics.rst +++ b/src/test/resources/doctest/templates/dql/basics.rst @@ -1,7 +1,7 @@ -=========== -Basic Query -=========== +============= +Basic Queries +============= .. rubric:: Table of contents diff --git a/src/test/resources/doctest/templates/dql/complex.rst b/src/test/resources/doctest/templates/dql/complex.rst new file mode 100644 index 0000000000..f184e0ae58 --- /dev/null +++ b/src/test/resources/doctest/templates/dql/complex.rst @@ -0,0 +1,13 @@ + +=============== +Complex Queries +=============== + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 2 + +Besides simple SFW queries (SELECT-FROM-WHERE), there is also support for complex queries such as Subquery, ``JOIN``, ``UNION`` and ``MINUS``. For these queries, more than one Elasticsearch index and DSL query is involved. You can check out how they are performed behind the scene by our explain API. + diff --git a/src/test/resources/doctest/templates/dql/functions.rst b/src/test/resources/doctest/templates/dql/functions.rst new file mode 100644 index 0000000000..5f4a922b98 --- /dev/null +++ b/src/test/resources/doctest/templates/dql/functions.rst @@ -0,0 +1,18 @@ + +============= +SQL Functions +============= + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + +Introduction +============ + +There is support for a wide variety of SQL functions. We are intend to generate this part of documentation automatically from our type system. However, the type system is missing descriptive information for now. So only formal specifications of all SQL functions supported are listed at the moment. More details will be added in future. + +Most of the specifications can be self explained just as a regular function with data type as argument. The only notation that needs elaboration is generic type ``T`` which binds to an actual type and can be used as return type. For example, ``ABS(NUMBER T) -> T`` means function ``ABS`` accepts an numerical argument of type ``T`` which could be any sub-type of ``NUMBER`` type and returns the actual type of ``T`` as return type. The actual type binds to generic type at runtime dynamically. + diff --git a/src/test/resources/doctest/templates/dql/metadata.rst b/src/test/resources/doctest/templates/dql/metadata.rst new file mode 100644 index 0000000000..ab6cc3c4da --- /dev/null +++ b/src/test/resources/doctest/templates/dql/metadata.rst @@ -0,0 +1,12 @@ + +================ +Metadata Queries +================ + +.. rubric:: Table of contents + +.. contents:: + :local: + :depth: 1 + + diff --git a/src/test/resources/doctest/testdata/employees_nested.json b/src/test/resources/doctest/testdata/employees_nested.json new file mode 100644 index 0000000000..a0142c49f1 --- /dev/null +++ b/src/test/resources/doctest/testdata/employees_nested.json @@ -0,0 +1,6 @@ +{"index":{"_id":"1"}} +{"id":3,"name":"Bob Smith","title":null,"projects":[{"name":"AWS Redshift Spectrum querying","started_year":1990},{"name":"AWS Redshift security","started_year":1999},{"name":"AWS Aurora security","started_year":2015}]} +{"index":{"_id":"2"}} +{"id":4,"name":"Susan Smith","title":"Dev Mgr","projects":[]} +{"index":{"_id":"3"}} +{"id":6,"name":"Jane Smith","title":"Software Eng 2","projects":[{"name":"AWS Redshift security","started_year":1998},{"name":"AWS Hello security","started_year":2015,"address":[{"city":"Dallas","state":"TX"}]}]}