diff --git a/build.gradle b/build.gradle index eba53b88b6..6f9fa1eba8 100644 --- a/build.gradle +++ b/build.gradle @@ -112,6 +112,7 @@ compileJava { // TODO: Similarly, need to fix compiling errors in test source code compileTestJava.options.warnings = false compileTestJava { + options.compilerArgs.addAll(["-processor", 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor']) doFirst { options.compilerArgs.remove('-Werror') options.compilerArgs.remove('-Xdoclint:all') @@ -132,6 +133,33 @@ integTestRunner { systemProperty 'tests.security.manager', 'false' // allows integration test classes to access test resource from project root path systemProperty('project.root', project.rootDir.absolutePath) + + // Run different task based on test type. "exclude" is required for each task. + def testType = System.getProperty("testType") + if (testType == 'doctest') { // Doctest to generate documentation + include 'com/amazon/opendistroforelasticsearch/sql/doctest/**/*IT.class' + exclude 'com/amazon/opendistroforelasticsearch/sql/correctness/**' + exclude 'com/amazon/opendistroforelasticsearch/sql/esintgtest/**' + + } else if (testType == 'comparison') { // Comparision testing + include 'com/amazon/opendistroforelasticsearch/sql/correctness/CorrectnessIT.class' + exclude 'com/amazon/opendistroforelasticsearch/sql/doctest/**' + exclude 'com/amazon/opendistroforelasticsearch/sql/esintgtest/**' + + // Enable logging output to console + testLogging.showStandardStreams true + + // Pass down system properties to IT class + systemProperty "esHost", System.getProperty("esHost") + systemProperty "dbUrl", System.getProperty("dbUrl") + systemProperty "otherDbUrls", System.getProperty("otherDbUrls") + systemProperty "queries", System.getProperty("queries") + + } else { // Run all other integration tests and doctest by default + include 'com/amazon/opendistroforelasticsearch/sql/esintgtest/**/*IT.class' + include 'com/amazon/opendistroforelasticsearch/sql/doctest/**/*IT.class' + exclude 'com/amazon/opendistroforelasticsearch/sql/correctness/**' + } } integTestCluster { @@ -214,6 +242,12 @@ dependencies { // testCompile group: 'com.alibaba', name: 'fastjson', version:'1.2.56' // testCompile group: 'org.mockito', name: 'mockito-core', version:'2.23.4' testCompile group: "org.elasticsearch.client", name: 'transport', version: "${es_version}" + + // JDBC drivers for comparison test. Somehow Apache Derby throws security permission exception. + testCompile group: 'com.amazon.opendistroforelasticsearch.client', name: 'opendistro-sql-jdbc', version: '1.3.0.0' + testCompile group: 'com.h2database', name: 'h2', version: '1.4.200' + testCompile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' + //testCompile group: 'org.apache.derby', name: 'derby', version: '10.15.1.3' } apply plugin: 'nebula.ospackage' diff --git a/docs/dev/Testing.md b/docs/dev/Testing.md new file mode 100644 index 0000000000..96ae0d3930 --- /dev/null +++ b/docs/dev/Testing.md @@ -0,0 +1,289 @@ +# How OpenDistro SQL Is Tested + +## 1.Introduction + +### 1.1 Problem Statement + +Currently there are quite a few unit tests and integration tests in the codebase. However, there are still problems to rely on these test cases only to ensure the correctness of our plugin: + + 1. **Test coverage**: Although the amount of test cases seems large, we’ve found that more than one important case was missed, for example use table alias in FROM clause, GROUP BY field alias etc. + 2. **Test correctness**: Some test case make assertion on the result set too simple to capture the issue if any. For example, some assertion is as loose as checking if result set is not empty. Unfortunately with strict assertion, we’re still not sure if the assertion is correct or not. For example, there were issue [#124](https://github.com/opendistro-for-elasticsearch/sql/issues/124) and [#226](https://github.com/opendistro-for-elasticsearch/sql/issues/226) regarding LEFT JOIN with WHERE clause and SUBSTRING. The correct semantic was actually different from what we thought. We did have MySQL reference docs at hands but it’s possible to miss some cases or misunderstand the correct use. So some mechanism is required to replace the loose assertion and be able to enforce the verification of our understanding. + 3. **Test Bench**: We want to run this new test on a regular basis to improving our implementation continuously. + +### 1.2 Our Goals + +In this work, we want to address the problems above for our testing to get more confidence of the correctness of our plugin. To achieve this goal, we need to improve both the quantity and quality of our tests and also keep it running on a regular basis. Although the search space of SQL is infinite, we can cover typical use cases and corner cases and most importantly make sure it does demonstrate the correctness of our implementation if it can pass. + +> Note that performance is also another important area that we want to test and understand how efficient or where is the bottleneck of typical queries. Since Elasticsearch provides ESRally benchmark tool which is totally different from our homemade test harness, we will be focuses on the correctness and won't cover performance testing here. + +## 2.Design + +### 2.1 Approaches + +First we can improve the test coverage by improving the diversity of our test case set, including diversity of test schema, test data and test cases. Secondly although it's difficult to provide rigorous mathematical proof for the result set of each query, we can make our best to ensure relative correctness by comparing with a few other database implementations. Of course the comparison testing doesn't guarantee 100% correctness and heavily depends on the correctness of reference implementation. But it should be much more reliable than hand written assertions after reading specification documentation previously. + +### 2.2 Components + +At this stage we don’t want to spend too much efforts on setting up a complicated infrastructure for testing. So we can take full advantage of capabilities that GitHub provides: + + 1. **Test Data & Cases**: Use test case set with Kibana flights and ecommerce sample index. + 2. **Trigger**: Set up another GitHub Action workflow. + 3. **Test Runner**: Use embedded Elasticsearch and other IMDBs. + 4. **Reporting**: Use standard JUnit report or simple custom json format. + 5. **Visualization**: Enable GitHub Pages for viewing or feed into Elasticsearch. + +![Test Framework Components](img/test-framework-components.png) + +## 3.Implementation + +### 3.1 Test Data + +For schema, we can just use Elasticsearch mapping as the format of schema and convert it to `INSERT` statement. For data we use CSV format simply. + +``` +{ + "_doc": { + "properties": { + "AvgTicketPrice": { + "type": "float" + }, + "Cancelled": { + "type": "boolean" + }, + "Carrier": { + "type": "keyword" + }, + ... + ... + "dayOfWeek": { + "type": "integer" + } + } + } +} + +FlightNum,Origin,FlightDelay,DistanceMiles,...,DestCityName +9HY9SWR,Frankfurt am Main Airport,false,10247.856675613455,...,Sydney +X98CCZO,Cape Town International Airport,false,5482.606664853586,...,Venice +...... +``` + +### 3.2 Test Cases + +For now we don't implement a Fuzzer to generate test queries because test queries prepared manually is sufficient to help identify issues. So we just put all queries in a text file. In future, a fuzzer can generate queries and save to file to integrate with Test Runner smoothly. + +``` +SELECT 1 AS `empty` FROM `kibana_sample_data_flights` +SELECT substring(OriginWeather, 1, 2) AS OriginWeather FROM kibana_sample_data_flights +SELECT SUM(FlightDelayMin) AS sum_FlightDelayMin_ok FROM kibana_sample_data_flights +SELECT SUM(FlightDelay) AS sum_FlightDelay_ok FROM kibana_sample_data_flights +SELECT SUM(DistanceMiles) AS sum_DistanceMiles_ok FROM kibana_sample_data_flights +``` + +### 3.3 Test Runner + +To simplify the test and be confident about the test result, Test Runner runs query by JDBC driver of OpenDistro SQL and other databases. In this case we don’t need to parse the data format returned from our plugin. And obviously another benefit is the correctness of JDBC driver is also covered. + +![How We Do Comparison Test](img/how-we-do-comparison-test.png) + +### 3.4 Trigger + +GitHub Action can be set up to trigger Gradle task to generate test report. + +### 3.5 Reporting + +Elasticsearch integration test is still using JUnit 4 which has many problems, such as dynamic test cases. So we can define our own report format for flexibility: + +``` +{ + "summary": { + "total": 3, + "success": 1, + "failure": 2 + }, + "tests": [ + { + "id": 1, + "sql": "...", + "result": "Success" + }, + { + "id": 2, + "sql": "...", + "result": "Failed", + "resultSets": [ + { + "database": "Elasticsearch", + "resultSet": { + "schema": [{"name":"","type":""},...], + "dataRows": [[...],...,[...]] + } + }, + { + "database": "Other database", + "resultSet": { + "schema": [...], + "dataRows": [[...],...,[...]] + } + } + ] + }, + { + "id": 3, + "sql": "...", + "result": "Failed", + "reason": "..." + }, + ] +} +``` + +The workflow of generating test result is: + +![The Workflow of Comparison Test](img/the-workflow-of-comparison-test.png) + +### 3.6 Visualization + +TODO + +--- + +## Appendix + +### I.Sample Usage + +Use default test set and reference databases by `testType` argument given only. Because `integTestRunner` triggers quite a few integration tests which take long time and runs with `gradlew build` every time, `testType` is added to run doctest for documentation and comparison test separately. + +Note that for now test data set argument is not supported because it often requires code changes to map more ES data type to JDBC type as well as convert data. + +``` +$ ./gradlew integTestRunner -DtestType=comparison + + [2020-01-06T11:37:57,437][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Starting comparison test + ================================= + Tested Database : (Use internal Elasticsearch in workspace) + Other Databases : + SQLite = jdbc:sqlite::memory: + H2 = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + Test data set(s) : + Test data set : + Table name: kibana_sample_data_flights + Schema: { + "mappings": { + "properties": { + "AvgTicketPrice": { + "type": "float" + }, + "Cancelled": { + "type": "boolean" + }, + "Carrier": { + "type": "keyword" + }, + ... + } + } + } + + Data rows (first 5 in 21): + [FlightNum, Origin, FlightDelay, DistanceMiles, FlightTimeMin, OriginWeather, dayOfWeek, AvgTicketPrice, Carrier, FlightDelayMin, OriginRegion, FlightDelayType, DestAirportID, Dest, FlightTimeHour, Cancelled, DistanceKilometers, OriginCityName, DestWeather, OriginCountry, DestCountry, DestRegion, OriginAirportID, DestCityName, timestamp] + [RGXY9H5, Chubu Centrair International Airport, false, 1619.970725161303, 124.1471507959044, Heavy Fog, 0, 626.1297405910661, Kibana Airlines, 0, SE-BD, No Delay, CAN, Guangzhou Baiyun International Airport, 2.06911917993174, true, 2607.0901667139924, Tokoname, Clear, JP, CN, SE-BD, NGO, Guangzhou, 2019-12-23T11:19:32] + [WOPNZEP, Munich Airport, true, 198.57903689856937, 34.9738738474057, Sunny, 0, 681.9911763989377, Kibana Airlines, 15, DE-BY, Carrier Delay, VE05, Venice Marco Polo Airport, 0.5828978974567617, false, 319.58198155849124, Munich, Cloudy, DE, IT, IT-34, MUC, Venice, 2019-12-23T12:32:26] + [G9J5O2V, Frankfurt am Main Airport, false, 4857.154739888458, 651.402736475921, Clear, 0, 868.0507463122127, Kibana Airlines, 0, DE-HE, No Delay, XIY, Xi'an Xianyang International Airport, 10.856712274598683, false, 7816.832837711051, Frankfurt am Main, Thunder & Lightning, DE, CN, SE-BD, FRA, Xi'an, 2019-12-23T03:48:33] + [HM80A5V, Itami Airport, false, 5862.6666599206, 555.0027890084269, Heavy Fog, 0, 765.0413127727119, Logstash Airways, 0, SE-BD, No Delay, TV01, Treviso-Sant'Angelo Airport, 9.250046483473783, true, 9435.047413143258, Osaka, Clear, JP, IT, IT-34, ITM, Treviso, 2019-12-23T19:50:48] + + Test data set : + Table name: kibana_sample_data_ecommerce + Schema: { + "mappings": { + "properties": { + "category": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "currency": { + "type": "keyword" + }, + "customer_birth_date": { + "type": "date" + }, + ... + } + } + } + Data rows (first 5 in 21): + [customer_first_name, customer_phone, type, manufacturer, customer_full_name, order_date, customer_last_name, day_of_week_i, total_quantity, currency, taxless_total_price, total_unique_products, category, customer_id, sku, order_id, user, customer_gender, email, day_of_week, taxful_total_price] + [Irwin, , order, [Elitelligence, Microlutions], Irwin Mcdonald, 2019-12-19T23:21:07+00:00, Mcdonald, 3, 2, EUR, 26.98, 2, [Men's Clothing], 14, [ZO0564605646, ZO0117401174], 551689, irwin, MALE, irwin@mcdonald-family.zzz, Thursday, 26.98] + [Wilhemina St., , order, [Spherecords Maternity, Oceanavigations], Wilhemina St. Washington, 2019-12-19T08:03:50+00:00, Washington, 3, 2, EUR, 72.98, 2, [Women's Clothing], 17, [ZO0705107051, ZO0270302703], 550817, wilhemina, FEMALE, wilhemina st.@washington-family.zzz, Thursday, 72.98] + [Kamal, , order, [Elitelligence, Oceanavigations], Kamal Foster, 2019-12-19T08:47:02+00:00, Foster, 3, 2, EUR, 45.98, 2, [Men's Clothing], 39, [ZO0532905329, ZO0277802778], 550864, kamal, MALE, kamal@foster-family.zzz, Thursday, 45.98] + [Diane, , order, [Tigress Enterprises, Low Tide Media], Diane Turner, 2019-12-22T13:45:07+00:00, Turner, 6, 2, EUR, 107.94, 2, [Women's Clothing, Women's Shoes], 22, [ZO0059900599, ZO0381103811], 555222, diane, FEMALE, diane@turner-family.zzz, Sunday, 107.94] + + Test query set : SQL queries (first 5 in 215): + SELECT SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) AS `OriginWeather` FROM `kibana_sample_data_flights` GROUP BY 1 + SELECT SUM(`kibana_sample_data_flights`.`FlightDelayMin`) AS `sum_Offset_ok` FROM `kibana_sample_data_flights` GROUP BY 1 + SELECT SUM(`kibana_sample_data_flights`.`FlightDelay`) AS `sum_FlightDelay_ok` FROM `kibana_sample_data_flights` GROUP BY 1 + SELECT SUM(`kibana_sample_data_flights`.`DistanceMiles`) AS `sum_DistanceMiles_ok` FROM `kibana_sample_data_flights` GROUP BY 1 + SELECT YEAR(`kibana_sample_data_flights`.`timestamp`) AS `yr_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 + + ================================= + + [2020-01-06T11:37:57,996][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Loading test data set... + [2020-01-06T11:38:06,308][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Verifying test queries... + [2020-01-06T11:38:21,180][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Saving test report to disk... + [2020-01-06T11:38:21,202][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Report file location is /Users/xxx/Workspace/sql/reports/report_2020-01-06-19.json + [2020-01-06T11:38:21,204][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Cleaning up test data... + [2020-01-06T11:38:21,849][INFO ][c.a.o.s.c.CorrectnessIT ] [performComparisonTest] Completed comparison test. +``` + +Specify different test case set by `queries` argument: + +``` +$ ./gradlew integTestRunner -DtestType=comparison -Dqueries=sanity_integration_tests.txt + + ... + Test query set : SQL queries (first 5 in 7): + SELECT AvgTicketPrice, Cancelled, Carrier, FlightDelayMin, timestamp FROM kibana_sample_data_flights + SELECT AvgTicketPrice AS avg, Cancelled AS cancel, Carrier AS carrier, FlightDelayMin AS delay, timestamp AS ts FROM kibana_sample_data_flights + SELECT Carrier, AVG(FlightDelayMin) FROM kibana_sample_data_flights GROUP BY Carrier + SELECT Carrier, AVG(FlightDelayMin) FROM kibana_sample_data_flights GROUP BY Carrier HAVING AVG(FlightDelayMin) > 5 + SELECT YEAR(timestamp) FROM kibana_sample_data_flights + ... +``` + +Specify external Elasticsearch cluster by `esHost` argument, otherwise an internal Elasticsearch in workspace is in use by default. + +``` +$ ./gradlew integTestRunner -DtestType=comparison -DesHost=localhost:9200 + + ================================= + Tested Database : localhost:9200 + Other Databases : + SQLite = jdbc:sqlite::memory: + H2 = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + ... +``` + +Specify different databases for comparison. `dbUrl` is for database to be tested. `otherDbUrls` is for other databases whose result set be referenced and compared. + +``` +$ ./gradlew integTestRunner -DtestType=comparison -Dqueries=sanity_integration_tests.txt -DdbUrl=jdbc:sqlite::memory: + + ================================= + Tested Database : jdbc:sqlite::memory: + Other Databases : + SQLite = jdbc:sqlite::memory: + H2 = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + ... + +$ ./gradlew integTestRunner -DtestType=comparison -Dqueries=sanity_integration_tests.txt -DdbUrl=jdbc:sqlite::memory: -DotherDbUrls=Unknown=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 + + ================================= + Tested Database : jdbc:sqlite::memory: + Other Databases : + Unknown = jdbc:h2:mem:test + ... +``` diff --git a/docs/dev/img/how-we-do-comparison-test.png b/docs/dev/img/how-we-do-comparison-test.png new file mode 100644 index 0000000000..b8dab8b635 Binary files /dev/null and b/docs/dev/img/how-we-do-comparison-test.png differ diff --git a/docs/dev/img/test-framework-components.png b/docs/dev/img/test-framework-components.png new file mode 100644 index 0000000000..35d25aa7cc Binary files /dev/null and b/docs/dev/img/test-framework-components.png differ diff --git a/docs/dev/img/the-workflow-of-comparison-test.png b/docs/dev/img/the-workflow-of-comparison-test.png new file mode 100644 index 0000000000..df3e839139 Binary files /dev/null and b/docs/dev/img/the-workflow-of-comparison-test.png differ diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/CorrectnessIT.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/CorrectnessIT.java new file mode 100644 index 0000000000..d52282c817 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/CorrectnessIT.java @@ -0,0 +1,138 @@ +/* + * 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.correctness; + +import com.amazon.opendistroforelasticsearch.sql.correctness.report.TestReport; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.ComparisonTest; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.DBConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.ESConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.JDBCConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestDataSet; +import com.amazon.opendistroforelasticsearch.sql.esintgtest.SQLIntegTestCase; +import com.google.common.collect.Maps; +import org.apache.http.HttpHost; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.client.RestClient; +import org.json.JSONObject; +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils.getResourceFilePath; + +/** + * Correctness integration test by performing comparison test with other databases. + */ +public class CorrectnessIT extends SQLIntegTestCase { + + private static final Logger LOG = LogManager.getLogger(); + + @Test + public void performComparisonTest() { + TestConfig config = new TestConfig(getCmdLineArgs()); + LOG.info("Starting comparison test {}", config); + + try (ComparisonTest test = new ComparisonTest(getThisDBConnection(config), + getOtherDBConnections(config))) { + LOG.info("Loading test data set..."); + test.connect(); + for (TestDataSet dataSet : config.getTestDataSets()) { + test.loadData(dataSet); + } + + LOG.info("Verifying test queries..."); + TestReport report = test.verify(config.getTestQuerySet()); + + LOG.info("Saving test report to disk..."); + store(report); + + LOG.info("Cleaning up test data..."); + for (TestDataSet dataSet : config.getTestDataSets()) { + test.cleanUp(dataSet); + } + } + LOG.info("Completed comparison test."); + } + + private Map getCmdLineArgs() { + return Maps.fromProperties(System.getProperties()); + } + + private DBConnection getThisDBConnection(TestConfig config) { + String dbUrl = config.getDbConnectionUrl(); + if (dbUrl.isEmpty()) { + return getESConnection(config); + } + return new JDBCConnection("DB Tested", dbUrl); + } + + /** Use Elasticsearch cluster given on CLI arg or internal embedded in SQLIntegTestCase */ + private DBConnection getESConnection(TestConfig config) { + RestClient client; + String esHost = config.getESHostUrl(); + if (esHost.isEmpty()) { + client = getRestClient(); + esHost = client.getNodes().get(0).getHost().toString(); + } else { + client = RestClient.builder(HttpHost.create(esHost)).build(); + } + return new ESConnection("jdbc:elasticsearch://" + esHost, client); + } + + /** Create database connection with database name and connect URL */ + private DBConnection[] getOtherDBConnections(TestConfig config) { + return config.getOtherDbConnectionNameAndUrls(). + entrySet().stream(). + map(e -> new JDBCConnection(e.getKey(), e.getValue())). + toArray(DBConnection[]::new); + } + + private void store(TestReport report) { + try { + // Create reports folder if not exists + String folderPath = "reports/"; + Path path = Paths.get(getResourceFilePath(folderPath)); + if (Files.notExists(path)) { + Files.createDirectory(path); + } + + // Write to report file + String relFilePath = folderPath + reportFileName(); + String absFilePath = getResourceFilePath(relFilePath); + byte[] content = new JSONObject(report).toString(2).getBytes(); + + LOG.info("Report file location is {}", absFilePath); + Files.write(Paths.get(absFilePath), content); + } catch (Exception e) { + throw new IllegalStateException("Failed to store report file", e); + } + } + + private String reportFileName() { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH"); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + String dateTime = df.format(new Date()); + return "report_" + dateTime + ".json"; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/TestConfig.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/TestConfig.java new file mode 100644 index 0000000000..3d55a00b6d --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/TestConfig.java @@ -0,0 +1,154 @@ +/* + * 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.correctness; + +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestDataSet; +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestQuerySet; +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; + +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.stream.Collectors.joining; + +/** + * Test configuration parse the following information from command line arguments: + * 1) Test schema and data + * 2) Test queries + * 3) Elasticsearch connection URL + * 4) Other database connection URLs + */ +public class TestConfig { + + private static final String DEFAULT_TEST_QUERIES = "tableau_integration_tests.txt"; + private static final String DEFAULT_OTHER_DB_URLS = "H2=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1," + "SQLite=jdbc:sqlite::memory:"; + + private final TestDataSet[] testDataSets; + + private final TestQuerySet testQuerySet; + + private final String esHostUrl; + + /** Test against some database rather than Elasticsearch via our JDBC driver */ + private final String dbConnectionUrl; + + private final Map otherDbConnectionNameAndUrls = new HashMap<>(); + + public TestConfig(Map cliArgs) { + testDataSets = buildDefaultTestDataSet(); // TODO: parse test data set argument + testQuerySet = buildTestQuerySet(cliArgs); + esHostUrl = cliArgs.getOrDefault("esHost", ""); + dbConnectionUrl = cliArgs.getOrDefault("dbUrl", ""); + + parseOtherDbConnectionInfo(cliArgs); + } + + public TestDataSet[] getTestDataSets() { + return testDataSets; + } + + public TestQuerySet getTestQuerySet() { + return testQuerySet; + } + + public String getESHostUrl() { + return esHostUrl; + } + + public String getDbConnectionUrl() { + return dbConnectionUrl; + } + + public Map getOtherDbConnectionNameAndUrls() { + return otherDbConnectionNameAndUrls; + } + + private TestDataSet[] buildDefaultTestDataSet() { + return new TestDataSet[]{ + new TestDataSet("kibana_sample_data_flights", + readFile("kibana_sample_data_flights.json"), + readFile("kibana_sample_data_flights.csv")), + new TestDataSet("kibana_sample_data_ecommerce", + readFile("kibana_sample_data_ecommerce.json"), + readFile("kibana_sample_data_ecommerce.csv")), + }; + } + + private TestQuerySet buildTestQuerySet(Map cliArgs) { + String queryFilePath = cliArgs.getOrDefault("queries", ""); // Gradle set it empty always + if (queryFilePath.isEmpty()) { + queryFilePath = DEFAULT_TEST_QUERIES; + } + return new TestQuerySet(readFile(queryFilePath)); + } + + private void parseOtherDbConnectionInfo(Map cliArgs) { + String otherDbUrls = cliArgs.getOrDefault("otherDbUrls", ""); + if (otherDbUrls.isEmpty()) { + otherDbUrls = DEFAULT_OTHER_DB_URLS; + } + + for (String dbNameAndUrl : otherDbUrls.split(",")) { + int firstEq = dbNameAndUrl.indexOf('='); + String dbName = dbNameAndUrl.substring(0, firstEq); + String dbUrl = dbNameAndUrl.substring(firstEq + 1); + otherDbConnectionNameAndUrls.put(dbName, dbUrl); + } + } + + private static String readFile(String relativePath) { + try { + URL url = Resources.getResource("correctness/" + relativePath); + return Resources.toString(url, Charsets.UTF_8); + } catch (Exception e) { + throw new IllegalStateException("Failed to read test file [" + relativePath + "]"); + } + } + + @Override + public String toString() { + return "\n=================================\n" + + "Tested Database : " + esHostUrlToString() + '\n' + + "Other Databases :\n" + otherDbConnectionInfoToString() + '\n' + + "Test data set(s) :\n" + testDataSetsToString() + '\n' + + "Test query set : " + testQuerySet + '\n' + + "=================================\n"; + } + + private String testDataSetsToString() { + return Arrays.stream(testDataSets). + map(TestDataSet::toString). + collect(joining("\n")); + } + + private String esHostUrlToString() { + if (!dbConnectionUrl.isEmpty()) { + return dbConnectionUrl; + } + return esHostUrl.isEmpty() ? "(Use internal Elasticsearch in workspace)" : esHostUrl; + } + + private String otherDbConnectionInfoToString() { + return otherDbConnectionNameAndUrls.entrySet().stream(). + map(e -> StringUtils.format(" %s = %s", e.getKey(), e.getValue())). + collect(joining("\n")); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/ErrorTestCase.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/ErrorTestCase.java new file mode 100644 index 0000000000..2aaf593548 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/ErrorTestCase.java @@ -0,0 +1,40 @@ +/* + * 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.correctness.report; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import static com.amazon.opendistroforelasticsearch.sql.correctness.report.TestCaseReport.TestResult.FAILURE; + +/** + * Report for test case that ends with an error. + */ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Getter +public class ErrorTestCase extends TestCaseReport { + + /** Root cause of the error */ + private final String reason; + + public ErrorTestCase(int id, String sql, String reason) { + super(id, sql, FAILURE); + this.reason = reason; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/FailedTestCase.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/FailedTestCase.java new file mode 100644 index 0000000000..93a6dbaec2 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/FailedTestCase.java @@ -0,0 +1,45 @@ +/* + * 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.correctness.report; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.Comparator; +import java.util.List; + +import static com.amazon.opendistroforelasticsearch.sql.correctness.report.TestCaseReport.TestResult.FAILURE; + +/** + * Report for test case that fails due to inconsistent result set. + */ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Getter +public class FailedTestCase extends TestCaseReport { + + /** Inconsistent result sets for reporting */ + private final List resultSets; + + public FailedTestCase(int id, String sql, List resultSets) { + super(id, sql, FAILURE); + this.resultSets = resultSets; + this.resultSets.sort(Comparator.comparing(DBResult::getDatabaseName)); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/SuccessTestCase.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/SuccessTestCase.java new file mode 100644 index 0000000000..7653c22eb0 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/SuccessTestCase.java @@ -0,0 +1,35 @@ +/* + * 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.correctness.report; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import static com.amazon.opendistroforelasticsearch.sql.correctness.report.TestCaseReport.TestResult.SUCCESS; + +/** + * Report for successful test case result. + */ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Getter +public class SuccessTestCase extends TestCaseReport { + + public SuccessTestCase(int id, String sql) { + super(id, sql, SUCCESS); + } +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestCaseReport.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestCaseReport.java new file mode 100644 index 0000000000..42406adaa3 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestCaseReport.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.correctness.report; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import static com.amazon.opendistroforelasticsearch.sql.correctness.report.TestCaseReport.TestResult.SUCCESS; + +/** + * Base class for different test result. + */ +@EqualsAndHashCode +@ToString +public abstract class TestCaseReport { + + public enum TestResult { + SUCCESS, FAILURE; + } + + @Getter + private final int id; + + @Getter + private final String sql; + + private final TestResult result; + + public TestCaseReport(int id, String sql, TestResult result) { + this.id = id; + this.sql = sql; + this.result = result; + } + + public String getResult() { + return result == SUCCESS ? "Success" : "Failed"; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestReport.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestReport.java new file mode 100644 index 0000000000..8ef1c4264f --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestReport.java @@ -0,0 +1,50 @@ +/* + * 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.correctness.report; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test report class to generate JSON report. + */ +@EqualsAndHashCode +@ToString +@Getter +public class TestReport { + + private final TestSummary summary = new TestSummary(); + + private final List tests = new ArrayList<>(); + + /** + * Add a test case report to the whole report. + * @param testCase report for a single test case + */ + public void addTestCase(TestCaseReport testCase) { + tests.add(testCase); + if ("Success".equals(testCase.getResult())) { + summary.addSuccess(); + } else { + summary.addFailure(); + } + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestSummary.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestSummary.java new file mode 100644 index 0000000000..4da3c832e7 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/report/TestSummary.java @@ -0,0 +1,46 @@ +/* + * 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.correctness.report; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +/** + * Test summary section. + */ +@EqualsAndHashCode +@ToString +@Getter +public class TestSummary { + + private int total; + + private int success; + + private int failure; + + public void addSuccess() { + success++; + total++; + } + + public void addFailure() { + failure++; + total++; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/ComparisonTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/ComparisonTest.java new file mode 100644 index 0000000000..49e583d57d --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/ComparisonTest.java @@ -0,0 +1,168 @@ +/* + * 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.correctness.runner; + +import com.amazon.opendistroforelasticsearch.sql.correctness.report.ErrorTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.FailedTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.SuccessTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.TestCaseReport; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.TestReport; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.DBConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestDataSet; +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestQuerySet; +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; +import com.google.common.collect.Iterators; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import static com.google.common.collect.ObjectArrays.concat; + +/** + * Comparison test runner for query result correctness. + */ +public class ComparisonTest implements AutoCloseable { + + /** Next id for test case */ + private int testCaseId = 1; + + /** Connection for database being tested */ + private final DBConnection thisConnection; + + /** Database connections for reference databases */ + private final DBConnection[] otherDbConnections; + + public ComparisonTest(DBConnection thisConnection, DBConnection[] otherDbConnections) { + this.thisConnection = thisConnection; + this.otherDbConnections = otherDbConnections; + + // Guarantee ordering of other database in comparison test + Arrays.sort(this.otherDbConnections, Comparator.comparing(DBConnection::getDatabaseName)); + } + + /** + * Open database connection. + */ + public void connect() { + for (DBConnection conn : concat(thisConnection, otherDbConnections)) { + conn.connect(); + } + } + + /** + * Create table and load test data. + * @param dataSet test data set + */ + public void loadData(TestDataSet dataSet) { + for (DBConnection conn : concat(thisConnection, otherDbConnections)) { + conn.create(dataSet.getTableName(), dataSet.getSchema()); + insertTestDataInBatch(conn, dataSet.getTableName(), dataSet.getDataRows()); + } + } + + /** + * Verify queries one by one by comparing between databases. + * @param querySet SQL queries + * @return Test result report + */ + public TestReport verify(TestQuerySet querySet) { + TestReport report = new TestReport(); + for (String sql : querySet) { + try { + DBResult esResult = thisConnection.select(sql); + report.addTestCase(compareWithOtherDb(sql, esResult)); + } catch (Exception e) { + report.addTestCase(new ErrorTestCase(nextId(), sql, + StringUtils.format("%s: %s", e.getClass().getSimpleName(), extractRootCause(e)))); + } + } + return report; + } + + /** + * Clean up test table. + * @param dataSet test data set + */ + public void cleanUp(TestDataSet dataSet) { + for (DBConnection conn : concat(thisConnection, otherDbConnections)) { + conn.drop(dataSet.getTableName()); + } + } + + @Override + public void close() { + for (DBConnection conn : concat(thisConnection, otherDbConnections)) { + try { + conn.close(); + } catch (Exception e) { + // Ignore + } + } + } + + /** Execute the query and compare with current result */ + private TestCaseReport compareWithOtherDb(String sql, DBResult esResult) { + StringBuilder reasons = new StringBuilder(); + for (int i = 0; i < otherDbConnections.length; i++) { + try { + DBResult otherDbResult = otherDbConnections[i].select(sql); + if (esResult.equals(otherDbResult)) { + return new SuccessTestCase(nextId(), sql); + } + + // Cannot find any database result match + if (i == otherDbConnections.length - 1) { + return new FailedTestCase(nextId(), sql, Arrays.asList(esResult, otherDbResult)); + } + } catch (Exception e) { + // Ignore and move on to next database + reasons.append(extractRootCause(e)).append(";"); + } + } + + // Cannot find any database support this query + return new ErrorTestCase(nextId(), sql, "No other databases support this query: " + reasons); + } + + private int nextId() { + return testCaseId++; + } + + private void insertTestDataInBatch(DBConnection conn, String tableName, List testData) { + Iterator iterator = testData.iterator(); + String[] fieldNames = iterator.next(); // first row is header of column names + Iterators.partition(iterator, 100). + forEachRemaining(batch -> conn.insert(tableName, fieldNames, batch)); + } + + private String extractRootCause(Throwable e) { + while (e.getCause() != null) { + e = e.getCause(); + } + + if (e.getLocalizedMessage() != null) { + return e.getLocalizedMessage(); + } + if (e.getMessage() != null) { + return e.getMessage(); + } + return e.toString(); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/DBConnection.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/DBConnection.java new file mode 100644 index 0000000000..df2174d471 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/DBConnection.java @@ -0,0 +1,70 @@ +/* + * 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.correctness.runner.connection; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; + +import java.util.List; + +/** + * Abstraction for different databases. + */ +public interface DBConnection { + + /** + * @return database name + */ + String getDatabaseName(); + + /** + * Connect to database by opening a connection. + */ + void connect(); + + /** + * Create table with the schema. + * @param tableName table name + * @param schema schema json in ES mapping format + */ + void create(String tableName, String schema); + + /** + * Insert batch of data to database. + * @param tableName table name + * @param columnNames column names + * @param batch batch of rows + */ + void insert(String tableName, String[] columnNames, List batch); + + /** + * Fetch data from database. + * @param query SQL query + * @return result set + */ + DBResult select(String query); + + /** + * Drop table. + * @param tableName table name + */ + void drop(String tableName); + + /** + * Close the database connection. + */ + void close(); + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/ESConnection.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/ESConnection.java new file mode 100644 index 0000000000..17fe6afd8b --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/ESConnection.java @@ -0,0 +1,113 @@ +/* + * 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.correctness.runner.connection; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.List; + +/** + * Elasticsearch database connection for insertion. This class wraps JDBCConnection to delegate query method. + */ +public class ESConnection implements DBConnection { + + /** Connection via our Elasticsearch JDBC driver */ + private final DBConnection connection; + + /** Native Elasticsearch REST client for operation unsupported by driver such as CREATE/INSERT */ + private final RestClient client; + + public ESConnection(String connectionUrl, RestClient client) { + this.connection = new JDBCConnection("Elasticsearch", connectionUrl); + this.client = client; + } + + @Override + public String getDatabaseName() { + return "Elasticsearch"; + } + + @Override + public void connect() { + connection.connect(); + } + + @Override + public void create(String tableName, String schema) { + Request request = new Request("PUT", "/" + tableName); + request.setJsonEntity(schema); + performRequest(request); + } + + @Override + public void drop(String tableName) { + performRequest(new Request("DELETE", "/" + tableName)); + } + + @Override + public void insert(String tableName, String[] columnNames, List batch) { + Request request = new Request("POST", "/" + tableName + "/_bulk"); + request.setJsonEntity(buildBulkBody(columnNames, batch)); + performRequest(request); + } + + @Override + public DBResult select(String query) { + return connection.select(query); + } + + @Override + public void close() { + connection.close(); + try { + client.close(); + } catch (IOException e) { + // Ignore + } + } + + private void performRequest(Request request) { + try { + Response response = client.performRequest(request); + int status = response.getStatusLine().getStatusCode(); + if (status != 200) { + throw new IllegalStateException("Failed to perform request. Error code: " + status); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to perform request", e); + } + } + + private String buildBulkBody(String[] columnNames, List batch) { + StringBuilder body = new StringBuilder(); + for (String[] fieldValues : batch) { + JSONObject json = new JSONObject(); + for (int i = 0; i < columnNames.length; i++) { + json.put(columnNames[i], fieldValues[i]); + } + + body.append("{\"index\":{}}\n"). + append(json).append("\n"); + } + return body.toString(); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/JDBCConnection.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/JDBCConnection.java new file mode 100644 index 0000000000..96c14798c6 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/connection/JDBCConnection.java @@ -0,0 +1,175 @@ +/* + * 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.correctness.runner.connection; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.Row; +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; +import org.json.JSONObject; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; + +import static java.util.stream.Collectors.joining; + +/** + * Database connection by JDBC driver. + */ +public class JDBCConnection implements DBConnection { + + private static final String SINGLE_QUOTE = "'"; + private static final String DOUBLE_QUOTE = "''"; + + /** Database name for display */ + private final String databaseName; + + /** Database connection URL */ + private final String connectionUrl; + + /** Current live connection */ + private Connection connection; + + public JDBCConnection(String databaseName, String connectionUrl) { + this.databaseName = databaseName; + this.connectionUrl = connectionUrl; + } + + @Override + public void connect() { + try { + connection = DriverManager.getConnection(connectionUrl); + } catch (Exception e) { + throw new IllegalStateException("Failed to open connection", e); + } + } + + @Override + public String getDatabaseName() { + return databaseName; + } + + @Override + public void create(String tableName, String schema) { + try (Statement stmt = connection.createStatement()) { + String types = parseColumnNameAndTypesInSchemaJson(schema); + stmt.executeUpdate(StringUtils.format("CREATE TABLE %s(%s)", tableName, types)); + } catch (SQLException e) { + throw new IllegalStateException("Failed to create table [" + tableName + "]", e); + } + } + + @Override + public void drop(String tableName) { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("DROP TABLE " + tableName); + } catch (SQLException e) { + throw new IllegalStateException("Failed to drop table [" + tableName + "]", e); + } + } + + @Override + public void insert(String tableName, String[] columnNames, List batch) { + try (Statement stmt = connection.createStatement()) { + String names = String.join(",", columnNames); + for (String[] fieldValues : batch) { + stmt.addBatch(StringUtils.format( + "INSERT INTO %s(%s) VALUES (%s)", tableName, names, getValueList(fieldValues))); + } + stmt.executeBatch(); + } catch (SQLException e) { + throw new IllegalStateException("Failed to execute update", e); + } + } + + @Override + public DBResult select(String query) { + try (Statement stmt = connection.createStatement()) { + ResultSet resultSet = stmt.executeQuery(query); + DBResult result = new DBResult(databaseName); + populateMetaData(resultSet, result); + populateData(resultSet, result); + return result; + } catch (SQLException e) { + throw new IllegalStateException("Failed to execute query [" + query + "]", e); + } + } + + @Override + public void close() { + try { + connection.close(); + } catch (SQLException e) { + // Ignore + } + } + + /** Parse out type in schema json and convert to field name and type pairs for CREATE TABLE statement. */ + private String parseColumnNameAndTypesInSchemaJson(String schema) { + JSONObject json = (JSONObject) new JSONObject(schema).query("/mappings/properties"); + return json.keySet().stream(). + map(colName -> colName + " " + mapToJDBCType(json.getJSONObject(colName).getString("type"))). + collect(joining(",")); + } + + private String getValueList(String[] fieldValues) { + return Arrays.stream(fieldValues). + map(val -> val.replace(SINGLE_QUOTE, DOUBLE_QUOTE)). + map(val -> SINGLE_QUOTE + val + SINGLE_QUOTE). + collect(joining(",")); + } + + private void populateMetaData(ResultSet resultSet, DBResult result) throws SQLException { + ResultSetMetaData metaData = resultSet.getMetaData(); + for (int i = 1; i <= metaData.getColumnCount(); i++) { + result.addColumn(metaData.getColumnName(i), metaData.getColumnTypeName(i)); + } + } + + private void populateData(ResultSet resultSet, DBResult result) throws SQLException { + while (resultSet.next()) { + Row row = new Row(); + for (int i = 1; i <= result.columnSize(); i++) { + row.add(resultSet.getObject(i)); + } + result.addRow(row); + } + } + + private String mapToJDBCType(String esType) { + switch (esType.toUpperCase()) { + case "KEYWORD": + case "TEXT": + return "VARCHAR"; + case "DATE": + return "TIMESTAMP"; + case "HALF_FLOAT": + return "FLOAT"; + default: + return esType; + } + } + + /** Setter for unit test mock */ + public void setConnection(Connection connection) { + this.connection = connection; + } +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/DBResult.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/DBResult.java new file mode 100644 index 0000000000..a40f79e6a1 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/DBResult.java @@ -0,0 +1,88 @@ +/* + * 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.correctness.runner.resultset; + +import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.json.JSONPropertyName; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Query result for equality comparison. Based on different type of query, such as query with/without ORDER BY and + * query with SELECT columns or just *, order of column and row may matter or not. So the internal data structure of this + * class is passed in from outside either list or set, hash map or linked hash map etc. + */ +@EqualsAndHashCode(exclude = "databaseName") +@ToString +public class DBResult { + + /** Database name for display */ + private final String databaseName; + + /** Column name and types from result set meta data */ + private final Map colNameAndTypes; + + /** Data rows from result set */ + private final Collection dataRows; + + /** + * By default treat both columns and data rows in order. This makes sense for typical query + * with specific column names in SELECT but without ORDER BY. + */ + public DBResult(String databaseName) { + this(databaseName, new LinkedHashMap<>(), new HashSet<>()); + } + + public DBResult(String databaseName, Map colNameAndTypes, Collection rows) { + this.databaseName = databaseName; + this.colNameAndTypes = colNameAndTypes; + this.dataRows = rows; + } + + public int columnSize() { + return colNameAndTypes.size(); + } + + public void addColumn(String name, String type) { + colNameAndTypes.put(StringUtils.toUpper(name), StringUtils.toUpper(type)); + } + + public void addRow(Row row) { + dataRows.add(row); + } + + @JSONPropertyName("database") + public String getDatabaseName() { + return databaseName; + } + + @JSONPropertyName("schema") + public Map getColumnNameAndTypes() { + return colNameAndTypes; + } + + /** Flatten for simplifying json generated */ + public Collection> getDataRows() { + return dataRows.stream().map(Row::getValues).collect(Collectors.toSet()); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/Row.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/Row.java new file mode 100644 index 0000000000..a1cf33d728 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/runner/resultset/Row.java @@ -0,0 +1,60 @@ +/* + * 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.correctness.runner.resultset; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Row in result set. + */ +@EqualsAndHashCode +@ToString +@Getter +public class Row { + + private final Collection values; + + public Row() { + this(new ArrayList<>()); // values in order by default + } + + public Row(Collection values) { + this.values = values; + } + + public void add(Object value) { + values.add(roundFloatNum(value)); + } + + private Object roundFloatNum(Object value) { + if (value instanceof Float) { + BigDecimal decimal = BigDecimal.valueOf((Float) value).setScale(2, RoundingMode.CEILING); + value = decimal.floatValue(); + } else if (value instanceof Double) { + BigDecimal decimal = BigDecimal.valueOf((Double) value).setScale(2, RoundingMode.CEILING); + value = decimal.doubleValue(); + } + return value; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ComparisonTestTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ComparisonTestTest.java new file mode 100644 index 0000000000..ee0b202e65 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ComparisonTestTest.java @@ -0,0 +1,182 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.report.ErrorTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.FailedTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.SuccessTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.TestReport; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.ComparisonTest; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.DBConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.Row; +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestQuerySet; +import com.google.common.collect.ImmutableMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link ComparisonTest} + */ +@RunWith(MockitoJUnitRunner.class) +public class ComparisonTestTest { + + @Mock + private DBConnection esConnection; + + @Mock + private DBConnection otherDbConnection; + + private ComparisonTest correctnessTest; + + @Before + public void setUp() { + when(esConnection.getDatabaseName()).thenReturn("ES"); + when(otherDbConnection.getDatabaseName()).thenReturn("Other"); + correctnessTest = new ComparisonTest( + esConnection, new DBConnection[]{otherDbConnection} + ); + } + + @Test + public void testSuccess() { + when(esConnection.select(anyString())).thenReturn( + new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))) + ); + when(otherDbConnection.select(anyString())).thenReturn( + new DBResult("Other DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))) + ); + + TestReport expected = new TestReport(); + expected.addTestCase(new SuccessTestCase(1, "SELECT * FROM accounts")); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testFailureDueToInconsistency() { + DBResult esResult = new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))); + DBResult otherDbResult = new DBResult("Other DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("JOHN")))); + when(esConnection.select(anyString())).thenReturn(esResult); + when(otherDbConnection.select(anyString())).thenReturn(otherDbResult); + + TestReport expected = new TestReport(); + expected.addTestCase(new FailedTestCase(1, "SELECT * FROM accounts", asList(esResult, otherDbResult))); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testSuccessFinally() { + DBConnection anotherDbConnection = mock(DBConnection.class); + when(anotherDbConnection.getDatabaseName()).thenReturn("Another"); + correctnessTest = new ComparisonTest( + esConnection, new DBConnection[]{otherDbConnection, anotherDbConnection} + ); + + DBResult esResult = new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))); + DBResult otherDbResult = new DBResult("Other DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("JOHN")))); + DBResult anotherDbResult = new DBResult("Another DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))); + when(esConnection.select(anyString())).thenReturn(esResult); + when(otherDbConnection.select(anyString())).thenReturn(otherDbResult); + when(anotherDbConnection.select(anyString())).thenReturn(anotherDbResult); + + TestReport expected = new TestReport(); + expected.addTestCase(new SuccessTestCase(1, "SELECT * FROM accounts")); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testFailureDueToEventualInconsistency() { + DBConnection anotherDbConnection = mock(DBConnection.class); + when(anotherDbConnection.getDatabaseName()).thenReturn("ZZZ DB"); // Make sure this will be called after Other DB + correctnessTest = new ComparisonTest( + esConnection, new DBConnection[]{otherDbConnection, anotherDbConnection} + ); + + DBResult esResult = new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))); + DBResult otherDbResult = new DBResult("Other DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("JOHN")))); + DBResult anotherDbResult = new DBResult("ZZZ DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("Hank")))); + when(esConnection.select(anyString())).thenReturn(esResult); + when(otherDbConnection.select(anyString())).thenReturn(otherDbResult); + when(anotherDbConnection.select(anyString())).thenReturn(anotherDbResult); + + TestReport expected = new TestReport(); + expected.addTestCase(new FailedTestCase(1, "SELECT * FROM accounts", asList(esResult, anotherDbResult))); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testErrorDueToESException() { + when(esConnection.select(anyString())).thenThrow(new RuntimeException("All shards failure")); + + TestReport expected = new TestReport(); + expected.addTestCase(new ErrorTestCase(1, "SELECT * FROM accounts", "RuntimeException: All shards failure")); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testErrorDueToNoOtherDBSupportThisQuery() { + when(esConnection.select(anyString())).thenReturn( + new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))) + ); + when(otherDbConnection.select(anyString())).thenThrow(new RuntimeException("Unsupported feature")); + + TestReport expected = new TestReport(); + expected.addTestCase(new ErrorTestCase(1, "SELECT * FROM accounts", "No other databases support this query: Unsupported feature;")); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + @Test + public void testSuccessWhenOneDBSupportThisQuery() { + DBConnection anotherDbConnection = mock(DBConnection.class); + when(anotherDbConnection.getDatabaseName()).thenReturn("Another"); + correctnessTest = new ComparisonTest( + esConnection, new DBConnection[]{otherDbConnection, anotherDbConnection} + ); + + when(esConnection.select(anyString())).thenReturn( + new DBResult("ES", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))) + ); + when(otherDbConnection.select(anyString())).thenThrow(new RuntimeException("Unsupported feature")); + when(anotherDbConnection.select(anyString())).thenReturn( + new DBResult("Another DB", ImmutableMap.of("firstname", "text"), asList(new Row(asList("John")))) + ); + + TestReport expected = new TestReport(); + expected.addTestCase(new SuccessTestCase(1, "SELECT * FROM accounts")); + TestReport actual = correctnessTest.verify(querySet("SELECT * FROM accounts")); + assertEquals(expected, actual); + } + + private TestQuerySet querySet(String query) { + return new TestQuerySet(new String[]{ query }); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/DBResultTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/DBResultTest.java new file mode 100644 index 0000000000..d3c16b7a27 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/DBResultTest.java @@ -0,0 +1,52 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.google.common.collect.ImmutableMap; +import org.junit.Test; + +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * Unit tests for {@link DBResult} + */ +public class DBResultTest { + + @Test + public void dbResultFromDifferentDbNameShouldEqual() { + DBResult result1 = new DBResult("DB 1", ImmutableMap.of("name", "VARCHAR"), emptyList()); + DBResult result2 = new DBResult("DB 2", ImmutableMap.of("name", "VARCHAR"), emptyList()); + assertEquals(result1, result2); + } + + @Test + public void dbResultWithDifferentColumnShouldNotEqual() { + DBResult result1 = new DBResult("DB 1", ImmutableMap.of("name", "VARCHAR"), emptyList()); + DBResult result2 = new DBResult("DB 2", ImmutableMap.of("age", "INT"), emptyList()); + assertNotEquals(result1, result2); + } + + @Test + public void dbResultWithDifferentColumnTypeShouldNotEqual() { + DBResult result1 = new DBResult("DB 1", ImmutableMap.of("age", "FLOAT"), emptyList()); + DBResult result2 = new DBResult("DB 2", ImmutableMap.of("age", "INT"), emptyList()); + assertNotEquals(result1, result2); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ESConnectionTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ESConnectionTest.java new file mode 100644 index 0000000000..aa4f10a6f9 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/ESConnectionTest.java @@ -0,0 +1,109 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.ESConnection; +import com.google.common.io.CharStreams; +import org.apache.http.ProtocolVersion; +import org.apache.http.message.BasicStatusLine; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link ESConnection} + */ +@RunWith(MockitoJUnitRunner.class) +public class ESConnectionTest { + + @Mock + private RestClient client; + + private ESConnection conn; + + @Before + public void setUp() throws IOException { + conn = new ESConnection("jdbc:elasticsearch://localhost:12345", client); + + Response response = mock(Response.class); + when(client.performRequest(any(Request.class))).thenReturn(response); + when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 2, 0), 200, "")); + } + + @Test + public void testCreateTable() throws IOException { + conn.create("test", "mapping"); + + Request actual = captureActualArg(); + assertEquals("PUT", actual.getMethod()); + assertEquals("/test", actual.getEndpoint()); + assertEquals("mapping", getBody(actual)); + } + + @Test + public void testInsertData() throws IOException { + conn.insert("test", new String[]{"name"}, Arrays.asList(new String[]{"John"}, new String[]{"Hank"})); + + Request actual = captureActualArg(); + assertEquals("POST", actual.getMethod()); + assertEquals("/test/_bulk", actual.getEndpoint()); + assertEquals( + "{\"index\":{}}\n" + + "{\"name\":\"John\"}\n" + + "{\"index\":{}}\n" + + "{\"name\":\"Hank\"}\n", + getBody(actual) + ); + } + + @Test + public void testDropTable() throws IOException { + conn.drop("test"); + + Request actual = captureActualArg(); + assertEquals("DELETE", actual.getMethod()); + assertEquals("/test", actual.getEndpoint()); + } + + private Request captureActualArg() throws IOException { + ArgumentCaptor argCap = ArgumentCaptor.forClass(Request.class); + verify(client).performRequest(argCap.capture()); + return argCap.getValue(); + } + + private String getBody(Request request) throws IOException { + InputStream inputStream = request.getEntity().getContent(); + return CharStreams.toString(new InputStreamReader(inputStream)); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/JDBCConnectionTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/JDBCConnectionTest.java new file mode 100644 index 0000000000..b039bd94a1 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/JDBCConnectionTest.java @@ -0,0 +1,187 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.connection.JDBCConnection; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.Row; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.OngoingStubbing; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link JDBCConnection} + */ +@RunWith(MockitoJUnitRunner.class) +public class JDBCConnectionTest { + + @Mock + private Connection connection; + + @Mock + private Statement statement; + + private JDBCConnection conn; + + @Before + public void setUp() throws SQLException { + conn = new JDBCConnection("Test DB", "jdbc:testdb://localhost:12345"); + conn.setConnection(connection); + + when(connection.createStatement()).thenReturn(statement); + } + + @Test + public void testCreateTable() throws SQLException { + conn.create("test", "{\"mappings\":{\"properties\":{\"name\":{\"type\":\"keyword\"},\"age\":{\"type\":\"INT\"}}}}"); + + ArgumentCaptor argCap = ArgumentCaptor.forClass(String.class); + verify(statement).executeUpdate(argCap.capture()); + String actual = argCap.getValue(); + + assertEquals("CREATE TABLE test(name VARCHAR,age INT)", actual); + } + + @Test + public void testDropTable() throws SQLException { + conn.drop("test"); + + ArgumentCaptor argCap = ArgumentCaptor.forClass(String.class); + verify(statement).executeUpdate(argCap.capture()); + String actual = argCap.getValue(); + + assertEquals("DROP TABLE test", actual); + } + + @Test + public void testInsertData() throws SQLException { + conn.insert("test", new String[]{"name", "age"}, + Arrays.asList(new String[]{"John", "25"}, new String[]{"Hank", "30"})); + + ArgumentCaptor argCap = ArgumentCaptor.forClass(String.class); + verify(statement, times(2)).addBatch(argCap.capture()); + List actual = argCap.getAllValues(); + + assertEquals( + Arrays.asList( + "INSERT INTO test(name,age) VALUES ('John','25')", + "INSERT INTO test(name,age) VALUES ('Hank','30')" + ), actual + ); + } + + @Test + public void testSelectQuery() throws SQLException { + ResultSetMetaData metaData = mockMetaData(ImmutableMap.of("name", "VARCHAR", "age", "INT")); + ResultSet resultSet = mockResultSet(new Object[]{"John", 25}, new Object[]{"Hank", 30}); + when(statement.executeQuery(anyString())).thenReturn(resultSet); + when(resultSet.getMetaData()).thenReturn(metaData); + + DBResult result = conn.select("SELECT * FROM test"); + assertEquals("Test DB", result.getDatabaseName()); + assertEquals( + ImmutableMap.of("NAME", "VARCHAR", "AGE", "INT"), + result.getColumnNameAndTypes() + ); + assertEquals( + Sets.newHashSet( + Arrays.asList("John", 25), + Arrays.asList("Hank", 30) + ), + result.getDataRows() + ); + } + + @Test + public void testSelectQueryWithFloatInResultSet() throws SQLException { + ResultSetMetaData metaData = mockMetaData(ImmutableMap.of("name", "VARCHAR", "balance", "FLOAT")); + ResultSet resultSet = mockResultSet( + new Object[]{"John", 25.123}, + new Object[]{"Hank", 30.456}, + new Object[]{"Allen", 15.1} + ); + when(statement.executeQuery(anyString())).thenReturn(resultSet); + when(resultSet.getMetaData()).thenReturn(metaData); + + DBResult result = conn.select("SELECT * FROM test"); + assertEquals( + Sets.newHashSet( + Arrays.asList("John", 25.13), + Arrays.asList("Hank", 30.46), + Arrays.asList("Allen", 15.1) + ), + result.getDataRows() + ); + } + + private ResultSet mockResultSet(Object[]... rows) throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + OngoingStubbing next = when(resultSet.next()); + for (int i = 0; i < rows.length; i++) { + next = next.thenReturn(true); + } + next.thenReturn(false); + + OngoingStubbing getObject = when(resultSet.getObject(anyInt())); + for (Object[] row : rows) { + for (Object val : row) { + getObject = getObject.thenReturn(val); + } + } + return resultSet; + } + + private ResultSetMetaData mockMetaData(Map nameAndTypes) throws SQLException { + ResultSetMetaData metaData = mock(ResultSetMetaData.class); + + OngoingStubbing getColumnName = when(metaData.getColumnName(anyInt())); + for (String name : nameAndTypes.keySet()) { + getColumnName = getColumnName.thenReturn(name); + } + + OngoingStubbing getColumnTypeName = when(metaData.getColumnTypeName(anyInt())); + for (String value : nameAndTypes.values()) { + getColumnTypeName = getColumnTypeName.thenReturn(value); + } + + when(metaData.getColumnCount()).thenReturn(nameAndTypes.size()); + return metaData; + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/RowTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/RowTest.java new file mode 100644 index 0000000000..5992c975f1 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/RowTest.java @@ -0,0 +1,49 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.Row; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * Unit test {@link Row} + */ +public class RowTest { + + @Test + public void rowShouldEqualToOtherRowWithSimilarFloat() { + Row row1 = new Row(); + Row row2 = new Row(); + row1.add(1.000001); + row2.add(1.000002); + assertEquals(row1, row2); + assertEquals(row2, row1); + } + + @Test + public void rowShouldNotEqualToOtherRowWithDifferentString() { + Row row1 = new Row(); + Row row2 = new Row(); + row1.add("hello"); + row2.add("hello1"); + assertNotEquals(row1, row2); + assertNotEquals(row2, row1); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestConfigTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestConfigTest.java new file mode 100644 index 0000000000..a66719e4eb --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestConfigTest.java @@ -0,0 +1,72 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.TestConfig; +import com.google.common.collect.ImmutableMap; +import org.junit.Test; + +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link TestConfig} + */ +public class TestConfigTest { + + @Test + public void testDefaultConfig() { + TestConfig config = new TestConfig(emptyMap()); + assertThat(config.getESHostUrl(), is(emptyString())); + assertThat( + config.getOtherDbConnectionNameAndUrls(), + allOf( + hasEntry("H2", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"), + hasEntry("SQLite", "jdbc:sqlite::memory:") + ) + ); + } + + @Test + public void testCustomESUrls() { + Map args = ImmutableMap.of("esHost", "localhost:9200"); + TestConfig config = new TestConfig(args); + assertThat(config.getESHostUrl(), is("localhost:9200")); + } + + @Test + public void testCustomDbUrls() { + Map args = ImmutableMap.of("otherDbUrls", + "H2=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1," + + "Derby=jdbc:derby:memory:myDb;create=true"); + + TestConfig config = new TestConfig(args); + assertThat( + config.getOtherDbConnectionNameAndUrls(), + allOf( + hasEntry("H2", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"), + hasEntry("Derby", "jdbc:derby:memory:myDb;create=true") + ) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestDataSetTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestDataSetTest.java new file mode 100644 index 0000000000..e67ba4e8c4 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestDataSetTest.java @@ -0,0 +1,70 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestDataSet; +import org.junit.Test; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link TestDataSet} + */ +public class TestDataSetTest { + + @Test + public void testDataSetWithSingleColumnData() { + TestDataSet dataSet = new TestDataSet("test", "mappings", "hello\nworld\n123"); + assertEquals("test", dataSet.getTableName()); + assertEquals("mappings", dataSet.getSchema()); + assertThat( + dataSet.getDataRows(), + contains( + new String[]{"hello"}, + new String[]{"world"}, + new String[]{"123"} + ) + ); + } + + @Test + public void testDataSetWithMultiColumnsData() { + TestDataSet dataSet = new TestDataSet("test", "mappings", "hello,world\n123"); + assertThat( + dataSet.getDataRows(), + contains( + new String[]{"hello", "world"}, + new String[]{"123"} + ) + ); + } + + @Test + public void testDataSetWithEscapedComma() { + TestDataSet dataSet = new TestDataSet("test", "mappings", "hello,\"hello,world,123\"\n123\n\"[abc,def,ghi]\",456"); + assertThat( + dataSet.getDataRows(), + contains( + new String[]{"hello", "hello,world,123"}, + new String[]{"123"}, + new String[]{"[abc,def,ghi]", "456"} + ) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestQuerySetTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestQuerySetTest.java new file mode 100644 index 0000000000..61b33d2927 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestQuerySetTest.java @@ -0,0 +1,41 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.testset.TestQuerySet; +import org.junit.Test; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link TestQuerySet} + */ +public class TestQuerySetTest { + + @Test + public void testQuerySet() { + TestQuerySet querySet = new TestQuerySet("SELECT * FROM accounts\nSELECT * FROM accounts LIMIT 5"); + assertThat( + querySet, + contains( + "SELECT * FROM accounts", + "SELECT * FROM accounts LIMIT 5" + ) + ); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestReportTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestReportTest.java new file mode 100644 index 0000000000..4bf917e25b --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/TestReportTest.java @@ -0,0 +1,137 @@ +/* + * 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.correctness.tests; + +import com.amazon.opendistroforelasticsearch.sql.correctness.report.ErrorTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.FailedTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.SuccessTestCase; +import com.amazon.opendistroforelasticsearch.sql.correctness.report.TestReport; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.DBResult; +import com.amazon.opendistroforelasticsearch.sql.correctness.runner.resultset.Row; +import com.google.common.collect.ImmutableMap; +import org.json.JSONObject; +import org.junit.Test; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.fail; + +/** + * Test for {@link TestReport} + */ +public class TestReportTest { + + private TestReport report = new TestReport(); + + @Test + public void testSuccessReport() { + report.addTestCase(new SuccessTestCase(1, "SELECT * FROM accounts")); + JSONObject actual = new JSONObject(report); + JSONObject expected = new JSONObject( + "{" + + " \"summary\": {" + + " \"total\": 1," + + " \"success\": 1," + + " \"failure\": 0" + + " }," + + " \"tests\": [" + + " {" + + " \"id\": 1," + + " \"result\": 'Success'," + + " \"sql\": \"SELECT * FROM accounts\"," + + " }" + + " ]" + + "}" + ); + + if (!actual.similar(expected)) { + fail("Actual JSON is different from expected: " + actual.toString(2)); + } + } + + @Test + public void testFailedReport() { + report.addTestCase(new FailedTestCase(1, "SELECT * FROM accounts", asList( + new DBResult("Elasticsearch", ImmutableMap.of("firstName", "text"), singleton(new Row(asList("hello")))), + new DBResult("H2", ImmutableMap.of("firstName", "text"), singleton(new Row(asList("world")))) + ))); + JSONObject actual = new JSONObject(report); + JSONObject expected = new JSONObject( + "{" + + " \"summary\": {" + + " \"total\": 1," + + " \"success\": 0," + + " \"failure\": 1" + + " }," + + " \"tests\": [" + + " {" + + " \"id\": 1," + + " \"result\": 'Failed'," + + " \"sql\": \"SELECT * FROM accounts\"," + + " \"resultSets\": [" + + " {" + + " \"database\": \"Elasticsearch\"," + + " \"schema\": {" + + " \"firstName\": \"text\"" + + " }," + + " \"dataRows\": [[\"hello\"]]" + + " }," + + " {" + + " \"database\": \"H2\"," + + " \"schema\": {" + + " \"firstName\": \"text\"" + + " }," + + " \"dataRows\": [[\"world\"]]" + + " }" + + " ]" + + " }" + + " ]" + + "}" + ); + + if (!actual.similar(expected)) { + fail("Actual JSON is different from expected: " + actual.toString(2)); + } + } + + @Test + public void testErrorReport() { + report.addTestCase(new ErrorTestCase(1, "SELECT * FROM", "Missing table name in query")); + JSONObject actual = new JSONObject(report); + JSONObject expected = new JSONObject( + "{" + + " \"summary\": {" + + " \"total\": 1," + + " \"success\": 0," + + " \"failure\": 1" + + " }," + + " \"tests\": [" + + " {" + + " \"id\": 1," + + " \"result\": 'Failed'," + + " \"sql\": \"SELECT * FROM\"," + + " \"reason\": \"Missing table name in query\"," + + " }" + + " ]" + + "}" + ); + + if (!actual.similar(expected)) { + fail("Actual JSON is different from expected: " + actual.toString(2)); + } + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/UnitTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/UnitTests.java new file mode 100644 index 0000000000..dc766150b5 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/tests/UnitTests.java @@ -0,0 +1,34 @@ +/* + * 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.correctness.tests; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ComparisonTestTest.class, + TestConfigTest.class, + TestDataSetTest.class, + TestQuerySetTest.class, + TestReportTest.class, + ESConnectionTest.class, + JDBCConnectionTest.class, + DBResultTest.class, + RowTest.class, +}) +public class UnitTests { +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestDataSet.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestDataSet.java new file mode 100644 index 0000000000..1294afe3c7 --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestDataSet.java @@ -0,0 +1,96 @@ +/* + * 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.correctness.testset; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.amazon.opendistroforelasticsearch.sql.utils.StringUtils.unquoteSingleField; +import static java.util.stream.Collectors.joining; + +/** + * Test data set + */ +public class TestDataSet { + + private final String tableName; + private final String schema; + private final List dataRows; + + public TestDataSet(String tableName, String schemaFileContent, String dataFileContent) { + this.tableName = tableName; + this.schema = schemaFileContent; + this.dataRows = splitColumns(dataFileContent, ','); + } + + public String getTableName() { + return tableName; + } + + public String getSchema() { + return schema; + } + + public List getDataRows() { + return dataRows; + } + + /** Split columns in each line by separator and ignore escaped separator(s) in quoted string. */ + private List splitColumns(String content, char separator) { + List result = new ArrayList<>(); + for (String line : content.split("\\r?\\n")) { + + List columns = new ArrayList<>(); + boolean isQuoted = false; + int start = 0; + for (int i = 0; i < line.length(); i++) { + + char c = line.charAt(i); + if (c == separator) { + if (isQuoted) { // Ignore comma(s) in quoted string + continue; + } + + String column = line.substring(start, i); + columns.add(unquoteSingleField(column, "\"")); + start = i + 1; + + } else if (c == '\"') { + isQuoted = !isQuoted; + } + } + + columns.add(unquoteSingleField(line.substring(start), "\"")); + result.add(columns.toArray(new String[0])); + } + return result; + } + + @Override + public String toString() { + int total = dataRows.size(); + return "Test data set :\n" + + " Table name: " + tableName + '\n' + + " Schema: " + schema + '\n' + + " Data rows (first 5 in " + total + "):" + + dataRows.stream(). + limit(5). + map(Arrays::toString). + collect(joining("\n ", "\n ", "\n")); + } + +} diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestQuerySet.java b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestQuerySet.java new file mode 100644 index 0000000000..dbad112b2f --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/sql/correctness/testset/TestQuerySet.java @@ -0,0 +1,65 @@ +/* + * 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.correctness.testset; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static java.util.stream.Collectors.joining; + +/** + * Test query set including SQL queries for comparison testing. + */ +public class TestQuerySet implements Iterable { + + private List queries; + + /** + * Construct by a test query file. + * @param queryFileContent file content with query per line + */ + public TestQuerySet(String queryFileContent) { + queries = lines(queryFileContent); + } + + /** + * Construct by a test query array. + * @param queries query in array + */ + public TestQuerySet(String[] queries) { + this.queries = Arrays.asList(queries); + } + + @Override + public Iterator iterator() { + return queries.iterator(); + } + + private List lines(String content) { + return Arrays.asList(content.split("\\r?\\n")); + } + + @Override + public String toString() { + int total = queries.size(); + return "SQL queries (first 5 in " + total + "):" + + queries.stream(). + limit(5). + collect(joining("\n ", "\n ", "\n")); + } + +} diff --git a/src/test/resources/correctness/kibana_sample_data_ecommerce.csv b/src/test/resources/correctness/kibana_sample_data_ecommerce.csv new file mode 100644 index 0000000000..fbb0486a5d --- /dev/null +++ b/src/test/resources/correctness/kibana_sample_data_ecommerce.csv @@ -0,0 +1,21 @@ +customer_first_name,customer_phone,type,manufacturer,customer_full_name,order_date,customer_last_name,day_of_week_i,total_quantity,currency,taxless_total_price,total_unique_products,category,customer_id,sku,order_id,user,customer_gender,email,day_of_week,taxful_total_price +Irwin,,order,"[Elitelligence, Microlutions]",Irwin Mcdonald,2019-12-19T23:21:07+00:00,Mcdonald,3,2,EUR,26.98,2,[Men's Clothing],14,"[ZO0564605646, ZO0117401174]",551689,irwin,MALE,irwin@mcdonald-family.zzz,Thursday,26.98 +Wilhemina St.,,order,"[Spherecords Maternity, Oceanavigations]",Wilhemina St. Washington,2019-12-19T08:03:50+00:00,Washington,3,2,EUR,72.98,2,[Women's Clothing],17,"[ZO0705107051, ZO0270302703]",550817,wilhemina,FEMALE,wilhemina st.@washington-family.zzz,Thursday,72.98 +Kamal,,order,"[Elitelligence, Oceanavigations]",Kamal Foster,2019-12-19T08:47:02+00:00,Foster,3,2,EUR,45.98,2,[Men's Clothing],39,"[ZO0532905329, ZO0277802778]",550864,kamal,MALE,kamal@foster-family.zzz,Thursday,45.98 +Diane,,order,"[Tigress Enterprises, Low Tide Media]",Diane Turner,2019-12-22T13:45:07+00:00,Turner,6,2,EUR,107.94,2,"[Women's Clothing, Women's Shoes]",22,"[ZO0059900599, ZO0381103811]",555222,diane,FEMALE,diane@turner-family.zzz,Sunday,107.94 +Elyssa,,order,"[Pyramidustries, Low Tide Media]",Elyssa Willis,2019-12-22T15:00:00+00:00,Willis,6,2,EUR,84.98,2,[Women's Shoes],27,"[ZO0132901329, ZO0370903709]",555282,elyssa,FEMALE,elyssa@willis-family.zzz,Sunday,84.98 +Betty,,order,"[Spherecords, Angeldale]",Betty Simpson,2019-12-19T09:17:17+00:00,Simpson,3,2,EUR,78.98,2,"[Women's Clothing, Women's Shoes]",44,"[ZO0633506335, ZO0669306693]",550896,betty,FEMALE,betty@simpson-family.zzz,Thursday,78.98 +Abd,,order,"[Low Tide Media, Elitelligence]",Abd Benson,2019-12-20T10:27:50+00:00,Benson,4,2,EUR,61.98,2,"[Men's Shoes, Men's Clothing]",52,"[ZO0387703877, ZO0595705957]",552324,abd,MALE,abd@benson-family.zzz,Friday,61.98 +Abigail,,order,"[Low Tide Media, Champion Arts]",Abigail Thompson,2019-12-20T10:56:38+00:00,Thompson,4,2,EUR,84.98,2,"[Women's Shoes, Women's Clothing]",46,"[ZO0382403824, ZO0486904869]",552363,abigail,FEMALE,abigail@thompson-family.zzz,Friday,84.98 +Pia,,order,[Tigress Enterprises],Pia Banks,2019-12-20T03:07:12+00:00,Banks,4,2,EUR,41.98,2,"[Women's Shoes, Women's Clothing]",45,"[ZO0021800218, ZO0055100551]",551923,pia,FEMALE,pia@banks-family.zzz,Friday,41.98 +Samir,,order,"[Elitelligence, Low Tide Media]",Samir Baker,2019-12-20T04:10:34+00:00,Baker,4,2,EUR,74.98,2,[Men's Clothing],34,"[ZO0586805868, ZO0428604286]",551981,samir,MALE,samir@baker-family.zzz,Friday,74.98 +Oliver,,order,"[Angeldale, Elitelligence]",Oliver Hampton,2019-12-20T04:43:41+00:00,Hampton,4,2,EUR,105.98,2,"[Men's Shoes, Men's Clothing]",7,"[ZO0691806918, ZO0595805958]",552009,oliver,MALE,oliver@hampton-family.zzz,Friday,105.98 +Yasmine,,order,"[Oceanavigations, Pyramidustries]",Yasmine Lewis,2019-12-24T21:50:24+00:00,Lewis,1,2,EUR,46.98,2,[Women's Clothing],43,"[ZO0261802618, ZO0166001660]",558404,yasmine,FEMALE,yasmine@lewis-family.zzz,Tuesday,46.98 +Hicham,,order,"[Microlutions, Elitelligence]",Hicham Gregory,2019-12-26T23:08:10+00:00,Gregory,3,2,EUR,49.98,2,"[Men's Accessories, Men's Clothing]",8,"[ZO0126901269, ZO0533205332]",561156,hicham,MALE,hicham@gregory-family.zzz,Thursday,49.98 +Samir,,order,[Elitelligence],Samir Holland,2019-12-25T00:07:12+00:00,Holland,2,1,EUR,32.99,1,[Men's Clothing],34,[ZO0539205392],739226,samir,MALE,samir@holland-family.zzz,Wednesday,32.99 +Brigitte,,order,"[Tigress Enterprises, Low Tide Media]",Brigitte Sutton,2020-01-03T13:43:41+00:00,Sutton,4,2,EUR,79.98,2,[Women's Shoes],12,"[ZO0019300193, ZO0367503675]",571473,brigitte,FEMALE,brigitte@sutton-family.zzz,Friday,79.98 +Phil,,order,"[Low Tide Media, Angeldale]",Phil Rowe,2020-01-03T18:31:41+00:00,Rowe,4,2,EUR,95.98,2,"[Men's Clothing, Men's Shoes]",50,"[ZO0439004390, ZO0690706907]",571728,phil,MALE,phil@rowe-family.zzz,Friday,95.98 +Betty,,order,"[Tigress Enterprises, Spherecords]",Betty Reyes,2020-01-03T19:10:34+00:00,Reyes,4,2,EUR,53.98,2,"[Women's Shoes, Women's Clothing]",44,"[ZO0002200022, ZO0661506615]",571757,betty,FEMALE,betty@reyes-family.zzz,Friday,53.98 +Wilhemina St.,,order,"[Champion Arts, Tigress Enterprises]",Wilhemina St. Marshall,2020-01-03T17:29:46+00:00,Marshall,4,2,EUR,43.98,2,[Women's Clothing],17,"[ZO0505405054, ZO0081700817]",571672,wilhemina,FEMALE,wilhemina st.@marshall-family.zzz,Friday,43.98 +rania,,order,"[Pyramidustries, Gnomehouse]",rania Fletcher,2020-01-15T23:29:46+00:00,Fletcher,2,2,EUR,82.98,2,[Women's Clothing],24,"[ZO0174901749, ZO0352403524]",588090,rani,FEMALE,rania@fletcher-family.zzz,Wednesday,82.98 +Gwen,,order,"[Tigress Enterprises, Tigress Enterprises MAMA]",Gwen Marshall,2019-12-26T21:57:36+00:00,Marshall,3,2,EUR,43.98,2,[Women's Clothing],26,"[ZO0033900339, ZO0228102281]",561090,gwen,FEMALE,gwen@marshall-family.zzz,Thursday,43.98 diff --git a/src/test/resources/correctness/kibana_sample_data_ecommerce.json b/src/test/resources/correctness/kibana_sample_data_ecommerce.json new file mode 100644 index 0000000000..dad8d6a1ba --- /dev/null +++ b/src/test/resources/correctness/kibana_sample_data_ecommerce.json @@ -0,0 +1,100 @@ +{ + "mappings": { + "properties": { + "category": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "currency": { + "type": "keyword" + }, + "customer_birth_date": { + "type": "date" + }, + "customer_first_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_full_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_gender": { + "type": "keyword" + }, + "customer_id": { + "type": "keyword" + }, + "customer_last_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_phone": { + "type": "keyword" + }, + "day_of_week": { + "type": "keyword" + }, + "day_of_week_i": { + "type": "integer" + }, + "email": { + "type": "keyword" + }, + "manufacturer": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "order_date": { + "type": "date" + }, + "order_id": { + "type": "keyword" + }, + "sku": { + "type": "keyword" + }, + "taxful_total_price": { + "type": "half_float" + }, + "taxless_total_price": { + "type": "half_float" + }, + "total_quantity": { + "type": "integer" + }, + "total_unique_products": { + "type": "integer" + }, + "type": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/correctness/kibana_sample_data_flights.csv b/src/test/resources/correctness/kibana_sample_data_flights.csv new file mode 100644 index 0000000000..b3ffb73e23 --- /dev/null +++ b/src/test/resources/correctness/kibana_sample_data_flights.csv @@ -0,0 +1,21 @@ +FlightNum,Origin,FlightDelay,DistanceMiles,FlightTimeMin,OriginWeather,dayOfWeek,AvgTicketPrice,Carrier,FlightDelayMin,OriginRegion,FlightDelayType,DestAirportID,Dest,FlightTimeHour,Cancelled,DistanceKilometers,OriginCityName,DestWeather,OriginCountry,DestCountry,DestRegion,OriginAirportID,DestCityName,timestamp +RGXY9H5,Chubu Centrair International Airport,false,1619.970725161303,124.1471507959044,Heavy Fog,0,626.1297405910661,Kibana Airlines,0,SE-BD,No Delay,CAN,Guangzhou Baiyun International Airport,2.06911917993174,true,2607.0901667139924,Tokoname,Clear,JP,CN,SE-BD,NGO,Guangzhou,2019-12-23T11:19:32 +WOPNZEP,Munich Airport,true,198.57903689856937,34.9738738474057,Sunny,0,681.9911763989377,Kibana Airlines,15,DE-BY,Carrier Delay,VE05,Venice Marco Polo Airport,0.5828978974567617,false,319.58198155849124,Munich,Cloudy,DE,IT,IT-34,MUC,Venice,2019-12-23T12:32:26 +G9J5O2V,Frankfurt am Main Airport,false,4857.154739888458,651.402736475921,Clear,0,868.0507463122127,Kibana Airlines,0,DE-HE,No Delay,XIY,Xi'an Xianyang International Airport,10.856712274598683,false,7816.832837711051,Frankfurt am Main,Thunder & Lightning,DE,CN,SE-BD,FRA,Xi'an,2019-12-23T03:48:33 +HM80A5V,Itami Airport,false,5862.6666599206,555.0027890084269,Heavy Fog,0,765.0413127727119,Logstash Airways,0,SE-BD,No Delay,TV01,Treviso-Sant'Angelo Airport,9.250046483473783,true,9435.047413143258,Osaka,Clear,JP,IT,IT-34,ITM,Treviso,2019-12-23T19:50:48 +84B0Y32,Charles de Gaulle International Airport,false,4397.926660603864,372.51457282541395,Thunder & Lightning,0,913.1638984616233,Kibana Airlines,0,FR-J,No Delay,STL,St Louis Lambert International Airport,6.208576213756899,false,7077.776883682865,Paris,Thunder & Lightning,FR,US,US-MO,CDG,St Louis,2019-12-23T11:30:48 +2AZWPJX,Rajiv Gandhi International Airport,true,0,180,Sunny,0,103.25307304704197,Logstash Airways,180,SE-BD,Security Delay,HYD,Rajiv Gandhi International Airport,3,false,0,Hyderabad,Hail,IN,IN,SE-BD,HYD,Hyderabad,2019-12-23T19:52:54 +SFLRI9O,Erie International Tom Ridge Field,false,6961.655654280931,622.4277087379495,Clear,0,775.1109173747694,Kibana Airlines,0,US-PA,No Delay,CJU,Jeju International Airport,10.373795145632492,false,11203.698757283091,Erie,Clear,US,KR,SE-BD,ERI,Jeju City,2019-12-23T07:32:32 +QDQMOD6,Brisbane International Airport,false,8013.330880747018,716.4558873858294,Thunder & Lightning,0,832.082612870741,ES-Air,0,SE-BD,No Delay,DEN,Denver International Airport,11.94093145643049,false,12896.20597294493,Brisbane,Cloudy,AU,US,US-CO,BNE,Denver,2019-12-23T10:59:26 +XTGFN9A,Jorge Chavez International Airport,false,3946.924514217792,396.99745533808243,Thunder & Lightning,0,348.23579123315324,Kibana Airlines,0,SE-BD,No Delay,YOW,Ottawa Macdonald-Cartier International Airport,6.616624255634707,false,6351.959285409319,Lima,Rain,PE,CA,CA-ON,LIM,Ottawa,2019-12-23T21:10:09 +USRQ3KK,Stockholm-Arlanda Airport,false,996.8381561540818,94.36797091633146,Clear,0,661.3465606549407,ES-Air,0,SE-AB,No Delay,TV01,Treviso-Sant'Angelo Airport,1.572799515272191,false,1604.2555055776347,Stockholm,Clear,SE,IT,IT-34,ARN,Treviso,2019-12-23T04:33:56 +PK46NHH,Milano Linate Airport,false,5261.396351845886,604.8140464617903,Rain,0,600.4401843290168,JetBeats,0,IT-25,No Delay,GEG,Spokane International Airport,10.080234107696505,false,8467.396650465065,Milan,Clear,IT,US,US-WA,MI11,Spokane,2019-12-23T20:35:25 +G80VHCJ,Bari Karol Wojty__a Airport,false,5630.111629019724,604.0524246328747,Sunny,0,738.260189539631,Logstash Airways,0,IT-75,No Delay,CJU,Jeju International Airport,10.067540410547911,false,9060.78636949312,Bari,Rain,IT,KR,SE-BD,BA02,Jeju City,2019-12-23T10:59:56 +PDS4U17,El Dorado International Airport,false,5591.079567130033,499.887241937962,Thunder & Lightning,0,437.9253204442997,ES-Air,0,SE-BD,No Delay,TO11,Turin Airport,8.331454032299368,false,8997.970354883317,Bogota,Hail,CO,IT,IT-21,BOG,Torino,2019-12-23T10:33:53 +2MXRGRK,Abu Dhabi International Airport,false,8160.7690090650885,656.6742320062424,Cloudy,0,825.9174161826418,JetBeats,0,SE-BD,No Delay,ABQ,Albuquerque International Sunport Airport,10.944570533437373,false,13133.484640124847,Abu Dhabi,Thunder & Lightning,AE,US,US-NM,AUH,Albuquerque,2019-12-23T19:27:11 +57CZEDA,London Heathrow Airport,true,4757.876231054233,720.4152685405732,Damaging Wind,0,836.1010286937247,ES-Air,270,GB-ENG,Carrier Delay,XHBU,Ukrainka Air Base,12.006921142342886,false,7657.059565189745,London,Sunny,GB,RU,RU-AMU,LHR,Belogorsk,2019-12-23T18:48:49 +5FYALP0,Malpensa International Airport,false,5812.230334559898,492.30936923905085,Damaging Wind,0,417.34744554513884,JetBeats,0,IT-25,No Delay,LAS,McCarran International Airport,8.20515615398418,false,9353.878015541966,Milan,Clear,IT,US,US-NV,MI12,Las Vegas,2019-12-23T10:37:54 +HVWAL6J,Comodoro Arturo Merino Benitez International Airport,false,7292.7292896018525,617.7110592550002,Cloudy,0,946.888426456834,Logstash Airways,0,SE-BD,No Delay,PA03,Falcone Borsellino Airport,10.29518432091667,false,11736.510125845005,Santiago,Cloudy,CL,IT,IT-82,SCL,Palermo,2019-12-23T03:54:12 +7ORM12S,Leonardo da Vinci___Fiumicino Airport,false,160.39074208529965,23.46580713004768,Sunny,0,118.37483602607261,Kibana Airlines,0,IT-62,No Delay,PI05,Pisa International Airport,0.39109678550079463,false,258.1238784305245,Rome,Sunny,IT,IT,IT-52,RM11,Pisa,2019-12-23T03:54:12 +2P36OEP,New Chitose Airport,true,5340.290617241973,941.1970552595557,Cloudy,0,705.7149863531135,Kibana Airlines,225,SE-BD,Late Aircraft Delay,VIE,Vienna International Airport,15.686617587659262,false,8594.364663114668,Chitose / Tomakomai,Rain,JP,AT,AT-9,CTS,Vienna,2019-12-23T09:41:52 +HLNZHCX,Verona Villafranca Airport,false,0,0,Sunny,0,172.3790782673846,ES-Air,0,IT-34,No Delay,VR10,Verona Villafranca Airport,0,false,0,Verona,Sunny,IT,IT,IT-34,VR10,Verona,2019-12-23T19:34:51 diff --git a/src/test/resources/correctness/kibana_sample_data_flights.json b/src/test/resources/correctness/kibana_sample_data_flights.json new file mode 100644 index 0000000000..ca6030983a --- /dev/null +++ b/src/test/resources/correctness/kibana_sample_data_flights.json @@ -0,0 +1,81 @@ +{ + "mappings": { + "properties": { + "AvgTicketPrice": { + "type": "float" + }, + "Cancelled": { + "type": "boolean" + }, + "Carrier": { + "type": "keyword" + }, + "Dest": { + "type": "keyword" + }, + "DestAirportID": { + "type": "keyword" + }, + "DestCityName": { + "type": "keyword" + }, + "DestCountry": { + "type": "keyword" + }, + "DestRegion": { + "type": "keyword" + }, + "DestWeather": { + "type": "keyword" + }, + "DistanceKilometers": { + "type": "float" + }, + "DistanceMiles": { + "type": "float" + }, + "FlightDelay": { + "type": "boolean" + }, + "FlightDelayMin": { + "type": "integer" + }, + "FlightDelayType": { + "type": "keyword" + }, + "FlightNum": { + "type": "keyword" + }, + "FlightTimeHour": { + "type": "keyword" + }, + "FlightTimeMin": { + "type": "float" + }, + "Origin": { + "type": "keyword" + }, + "OriginAirportID": { + "type": "keyword" + }, + "OriginCityName": { + "type": "keyword" + }, + "OriginCountry": { + "type": "keyword" + }, + "OriginRegion": { + "type": "keyword" + }, + "OriginWeather": { + "type": "keyword" + }, + "dayOfWeek": { + "type": "integer" + }, + "timestamp" : { + "type" : "date" + } + } + } +} diff --git a/src/test/resources/correctness/sanity_integration_tests.txt b/src/test/resources/correctness/sanity_integration_tests.txt new file mode 100644 index 0000000000..82cd2f8471 --- /dev/null +++ b/src/test/resources/correctness/sanity_integration_tests.txt @@ -0,0 +1,8 @@ +SELECT AvgTicketPrice, Cancelled, Carrier, FlightDelayMin, timestamp FROM kibana_sample_data_flights +SELECT AvgTicketPrice AS avg, Cancelled AS cancel, Carrier AS carrier, FlightDelayMin AS delay, timestamp AS ts FROM kibana_sample_data_flights +SELECT f.AvgTicketPrice AS avg, f.Cancelled AS cancel, f.Carrier AS carrier, f.FlightDelayMin AS delay, timestamp AS ts FROM kibana_sample_data_flights f +SELECT Carrier, AVG(FlightDelayMin) FROM kibana_sample_data_flights GROUP BY Carrier +SELECT Carrier, AVG(FlightDelayMin) FROM kibana_sample_data_flights GROUP BY Carrier HAVING AVG(FlightDelayMin) > 5 +SELECT YEAR(timestamp) FROM kibana_sample_data_flights +SELECT AvgTicketPrice AS AvgTicketPrice, DestCountry AS DestCountry FROM kibana_sample_data_ecommerce e JOIN kibana_sample_data_flights f ON (e.day_of_week_i = f.dayOfWeek) LIMIT 1000 +SELECT AvgTicketPrice AS AvgTicketPrice, DestCountry AS DestCountry FROM kibana_sample_data_ecommerce e LEFT JOIN kibana_sample_data_flights f ON (e.day_of_week_i = f.dayOfWeek) LIMIT 1000 diff --git a/src/test/resources/correctness/tableau_integration_tests.txt b/src/test/resources/correctness/tableau_integration_tests.txt new file mode 100644 index 0000000000..e10ef3ae5a --- /dev/null +++ b/src/test/resources/correctness/tableau_integration_tests.txt @@ -0,0 +1,215 @@ +SELECT SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) AS `OriginWeather` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`FlightDelayMin`) AS `sum_Offset_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`FlightDelay`) AS `sum_FlightDelay_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`DistanceMiles`) AS `sum_DistanceMiles_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(`kibana_sample_data_flights`.`timestamp`) AS `yr_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ABS(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ACOS(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252358221825_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ASIN(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252358545410_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ATAN(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252358811651_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ATAN2(`kibana_sample_data_flights`.`dayOfWeek`,`kibana_sample_data_flights`.`FlightDelayMin`)) AS `sum_Calculation_160722252358811651_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(COS(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(COT(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(DEGREES(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM((`kibana_sample_data_flights`.`dayOfWeek` DIV `kibana_sample_data_flights`.`FlightDelayMin`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(EXP(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM((((CASE WHEN (ABS((`kibana_sample_data_flights`.`FlightDelayMin`) - (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((`kibana_sample_data_flights`.`dayOfWeek`) - (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN 1.5 ELSE 0.0 END) - (CASE WHEN ((`kibana_sample_data_flights`.`dayOfWeek`) - (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0) < 0.0) AND ((CASE WHEN (ABS((`kibana_sample_data_flights`.`FlightDelayMin`) - (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((`kibana_sample_data_flights`.`dayOfWeek`) - (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) > 0.0) THEN 3.0 ELSE 0.0 END)) + (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0))) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ROUND( (((CASE WHEN (ABS((`kibana_sample_data_flights`.`FlightDelayMin`) - (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((`kibana_sample_data_flights`.`dayOfWeek`) - (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) - (CASE WHEN ((`kibana_sample_data_flights`.`FlightDelayMin`) - (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0)) < 0.0) AND ((CASE WHEN (ABS((`kibana_sample_data_flights`.`FlightDelayMin`) - (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((`kibana_sample_data_flights`.`dayOfWeek`) - (ROUND( ( (`kibana_sample_data_flights`.`dayOfWeek`) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) > 0.0) THEN SQRT(3.0) ELSE 0.0 END)) + (ROUND( ( (`kibana_sample_data_flights`.`FlightDelayMin`) / SQRT(3.0) ), 0 ) * SQRT(3.0))), 3)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(LOG(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM((LOG(`kibana_sample_data_flights`.`dayOfWeek`)/LOG(10))) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`dayOfWeek`) AS `usr_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`dayOfWeek`) AS `usr_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM((CASE WHEN `kibana_sample_data_flights`.`dayOfWeek` >= 0 OR FLOOR(2) = 2 THEN POWER(`kibana_sample_data_flights`.`dayOfWeek`,2) END)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(RADIANS(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ROUND(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(SIGN(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(SIN(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(SQRT(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(POWER(`kibana_sample_data_flights`.`dayOfWeek`, 2)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(TAN(`kibana_sample_data_flights`.`dayOfWeek`)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(ASCII(SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024))) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT `kibana_sample_data_flights`.`Dest` AS `Dest` FROM `kibana_sample_data_flights` WHERE ((`kibana_sample_data_flights`.`Dest` = 'caching_sha2_password') AND (LOCATE('in',LOWER(`kibana_sample_data_flights`.`Dest`)) > 0)) GROUP BY 1 +SELECT SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) AS `OriginWeather` FROM `kibana_sample_data_flights` WHERE (RIGHT(RTRIM(LOWER(SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024))), LENGTH('.')) = '.') GROUP BY 1 +SELECT SUM(IF(ISNULL(1), NULL, LOCATE('Cape',`kibana_sample_data_flights`.`Origin`,GREATEST(1,FLOOR(1))))) AS `sum_Calculation_462181953493630977_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN 3 >= 0 THEN LEFT(`kibana_sample_data_flights`.`Origin`,3) ELSE NULL END) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT LENGTH(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT LOWER(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT LTRIM(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`Origin`) AS `usr_Calculation_462181953493630977_nk` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT IF(ISNULL(0), NULL, SUBSTRING(`kibana_sample_data_flights`.`Origin`,GREATEST(1,FLOOR(0)),FLOOR(5))) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`Origin`) AS `usr_Calculation_462181953493630977_nk` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT REPLACE(`kibana_sample_data_flights`.`Origin`,'Airport','') AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN 3 >= 0 THEN RIGHT(`kibana_sample_data_flights`.`Origin`,3) ELSE NULL END) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT RTRIM(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT IF(`kibana_sample_data_flights`.`AvgTicketPrice` >= 0, SPACE(FLOOR(`kibana_sample_data_flights`.`AvgTicketPrice`)), NULL) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT TRIM(LEADING '-' FROM TRIM(LEADING SUBSTRING_INDEX(`kibana_sample_data_flights`.`Origin`, '-', (2 - 1)) FROM SUBSTRING_INDEX(`kibana_sample_data_flights`.`Origin`, '-', 2))) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) AS `OriginWeather` FROM `kibana_sample_data_flights` WHERE (LEFT(LTRIM(LOWER(SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024))), LENGTH('$')) = '$') GROUP BY 1 +SELECT TRIM(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT UPPER(`kibana_sample_data_flights`.`Origin`) AS `Calculation_462181953493630977` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT ADDDATE( DATE_FORMAT( DATE(`kibana_sample_data_flights`.`password_last_changed`), '%Y-01-01 00:00:00' ), INTERVAL 0 SECOND ) AS `tyr_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(DATE(`kibana_sample_data_flights`.`timestamp`)) AS `yr_Calculation_462181953481519104_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (YEAR(`kibana_sample_data_flights`.`timestamp`) - YEAR(DATE('1990-01-01'))) AS `Calculation_1706301351891775489` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MONTHNAME(`kibana_sample_data_flights`.`timestamp`) AS `Calculation_1706301351891775489` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(TIMESTAMP(STR_TO_DATE('5.April.2004', '%d.%i.%Y'))) AS `yr_Calculation_462181953481519104_ok` FROM `kibana_sample_data_flights` +SELECT YEAR(ADDDATE( CONCAT( DATE_FORMAT( `kibana_sample_data_flights`.`timestamp`, '%Y-' ), (3*(QUARTER(`kibana_sample_data_flights`.`timestamp`)-1)+1), '-01 00:00:00' ), INTERVAL 0 SECOND )) AS `yr_Calculation_1706301351891775489_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT DAYOFMONTH(`kibana_sample_data_flights`.`timestamp`) AS `Calculation_462181953481519104` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 2019 AS `yr_Calculation_462181953481519104_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(ADDTIME(CAST(CAST(`kibana_sample_data_flights`.`timestamp` AS DATE) AS DATETIME), TIME(`kibana_sample_data_flights`.`timestamp`))) AS `yr_Calculation_1706301351891775489_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(MAKETIME(`kibana_sample_data_flights`.`dayOfWeek`, `kibana_sample_data_flights`.`dayOfWeek`, `kibana_sample_data_flights`.`dayOfWeek`)) AS `yr_Calculation_1706301351891775489_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`timestamp`) AS `max_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`timestamp`) AS `min_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MONTH(`kibana_sample_data_flights`.`timestamp`) AS `mn_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(NOW()) AS `yr_Calculation_462181953481519104_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(CURDATE()) AS `yr_Calculation_462181953481519104_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT YEAR(`kibana_sample_data_flights`.`timestamp`) AS `yr_timestamp_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT ((`kibana_sample_data_flights`.`Origin` = 'Frankfurt am Main Airport') AND (`kibana_sample_data_flights`.`Dest` = 'Sydney Kingsford Smith International Airport')) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE `kibana_sample_data_flights`.`OriginWeather` WHEN 'Sunny' THEN '1' WHEN 'Rain' THEN '0' ELSE 'NA' END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`FlightDelay` = 0) THEN 'No delay' ELSE CAST(NULL AS CHAR(1)) END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`FlightDelay` = 0) THEN 'No delay' WHEN (`kibana_sample_data_flights`.`FlightDelay` = 1) THEN 'Delay' ELSE CAST(NULL AS CHAR(1)) END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (RIGHT(RTRIM(`kibana_sample_data_flights`.`Origin`), LENGTH('Airport')) = 'Airport') AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`FlightDelay` = 0) THEN 'No delay' ELSE CAST(NULL AS CHAR(1)) END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT IFNULL(`kibana_sample_data_flights`.`Cancelled`, `kibana_sample_data_flights`.`AvgTicketPrice`) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'High' WHEN NOT (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'Low' ELSE NULL END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (NOT ISNULL(DATE_FORMAT(`kibana_sample_data_flights`.`Origin`, '%Y'))) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT ISNULL(`kibana_sample_data_flights`.`FlightNum`) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`dayOfWeek`) AS `max_max_questions_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`dayOfWeek`) AS `min_max_questions_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (NOT ISNULL(DATE_FORMAT(`kibana_sample_data_flights`.`Origin`, '%Y'))) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT ((`kibana_sample_data_flights`.`Origin` = 'Frankfurt am Main Airport') OR (`kibana_sample_data_flights`.`Dest` = 'Sydney Kingsford Smith International Airport')) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'High' WHEN NOT (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'Low' ELSE NULL END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT (CASE WHEN (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'High' WHEN NOT (`kibana_sample_data_flights`.`AvgTicketPrice` > 500) THEN 'Low' ELSE NULL END) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT IFNULL(`kibana_sample_data_flights`.`FlightDelay`, 0) AS `Calculation_462181953506873347` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`Origin`) AS `TEMP(Calculation_462181953504628738)(2376748618)(0)`, MAX(`kibana_sample_data_flights`.`Origin`) AS `TEMP(Calculation_462181953504628738)(2968235173)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT AVG(`kibana_sample_data_flights`.`FlightDelayMin`) AS `avg_max_user_connections_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(1) AS `cnt_max_user_connections_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`max_questions`) AS `max_max_questions_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`dayOfWeek`) AS `min_max_questions_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM((`kibana_sample_data_flights`.`AvgTicketPrice` * `kibana_sample_data_flights`.`AvgTicketPrice`)) AS `TEMP(Calculation_462181953506873347)(1705728846)(0)`, SUM(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(Calculation_462181953506873347)(2465277995)(0)`, COUNT(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(Calculation_462181953506873347)(2633997250)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT COUNT(`kibana_sample_data_flights`.`DistanceMiles`) AS `TEMP(Calculation_462181953506873347)(2070533874)(0)`, SUM(`kibana_sample_data_flights`.`DistanceMiles`) AS `TEMP(Calculation_462181953506873347)(3496560911)(0)`, SUM((`kibana_sample_data_flights`.`DistanceMiles` * `kibana_sample_data_flights`.`DistanceMiles`)) AS `TEMP(Calculation_462181953506873347)(3595387140)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `usr_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(Calculation_462181953506873347)(105357904)(0)`, COUNT(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(Calculation_462181953506873347)(2584840543)(0)`, SUM(((`kibana_sample_data_flights`.`dayOfWeek` + 0.0) * (`kibana_sample_data_flights`.`dayOfWeek` + 0.0))) AS `TEMP(Calculation_462181953506873347)(3340567470)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`DistanceKilometers`) AS `TEMP(Calculation_462181953506873347)(1474522238)(0)`, COUNT(`kibana_sample_data_flights`.`DistanceKilometers`) AS `TEMP(Calculation_462181953506873347)(2841334535)(0)`, SUM((`kibana_sample_data_flights`.`DistanceKilometers` * `kibana_sample_data_flights`.`DistanceKilometers`)) AS `TEMP(Calculation_462181953506873347)(461715975)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) AS `OriginWeather` FROM `kibana_sample_data_flights` WHERE (SUBSTRING(`kibana_sample_data_flights`.`OriginWeather`, 1, 1024) = 'ABC') GROUP BY 1 +SELECT SUM((CASE \tWHEN ISNULL(`kibana_sample_data_flights`.`dayOfWeek`) THEN NULL \tWHEN ISNULL(10) THEN NULL \tELSE GREATEST(`kibana_sample_data_flights`.`dayOfWeek`, 10) END)) AS `sum_Calculation_160722252357632000_ok` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT `kibana_sample_data_flights`.`AvgTicketPrice` AS `AvgTicketPrice`, `kibana_sample_data_flights`.`Cancelled` AS `Cancelled`, `kibana_sample_data_flights`.`Carrier` AS `Carrier`, `kibana_sample_data_flights`.`DestAirportID` AS `DestAirportID`, `kibana_sample_data_flights`.`DestCityName` AS `DestCityName`, `kibana_sample_data_flights`.`DestCountry` AS `DestCountry`, `kibana_sample_data_flights`.`DestLocation` AS `DestLocation`, `kibana_sample_data_flights`.`DestRegion` AS `Dest~~~<<>>~~~e`.`kibana_sample_data_flights` AS `kibana_sample_data_flights` FROM `kibana_sample_data_ecommerce` LEFT JOIN `kibana_sample_data_flights` ON (`kibana_sample_data_ecommerce`.`day_of_week_i` = `kibana_sample_data_flights`.`dayOfWeek`) LIMIT 1000 +SELECT `kibana_sample_data_flights`.`AvgTicketPrice` AS `AvgTicketPrice`, `kibana_sample_data_flights`.`Cancelled` AS `Cancelled`, `kibana_sample_data_flights`.`Carrier` AS `Carrier`, `kibana_sample_data_flights`.`DestAirportID` AS `DestAirportID`, `kibana_sample_data_flights`.`DestCityName` AS `DestCityName`, `kibana_sample_data_flights`.`DestCountry` AS `DestCountry`, `kibana_sample_data_flights`.`DestLocation` AS `DestLocation`, `kibana_sample_data_flights`.`DestRegion` AS `Dest~~~<<>>~~~`.`kibana_sample_data_flights` AS `kibana_sample_data_flights` FROM `kibana_sample_data_ecommerce` RIGHT JOIN `kibana_sample_data_flights` ON (`kibana_sample_data_ecommerce`.`day_of_week_i` = `kibana_sample_data_flights`.`dayOfWeek`) LIMIT 1000 +SELECT `kibana_sample_data_flights`.`OriginCityName` AS `OriginCityName` FROM `kibana_sample_data_flights` GROUP BY 1 ORDER BY `OriginCityName` ASC +SELECT `kibana_sample_data_flights`.`OriginCityName` AS `OriginCityName` FROM `kibana_sample_data_flights` GROUP BY 1 ORDER BY `OriginCityName` ASC +SELECT `kibana_sample_data_flights`.`DestCityName` AS `DestCityName`, SUM(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `$__alias__0` FROM `kibana_sample_data_flights` GROUP BY 1 ORDER BY `$__alias__0` DESC, `DestCityName` ASC LIMIT 10 +SELECT 'DESKTOP-7APIVOE\\\\Rupal' AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 0 AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 0 AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 1 AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 'DESKTOP-7APIVOE' AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 'ABC' AS `Calculation_1122522251639717888` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TC_)(3575797393)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(TEMP(TC_)(4001152001)(0))(105357904)(0)`, COUNT(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(TEMP(TC_)(4001152001)(0))(2584840543)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TC_)(2076389572)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TEMP(TC_)(4079199159)(0))(2465277995)(0)`, COUNT(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TEMP(TC_)(4079199159)(0))(2633997250)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TC_)(2465277995)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT COUNT(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TC_)(2633997250)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MAX(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(TC_)(718966039)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT MIN(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(TC_)(2462140059)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT SUM(`kibana_sample_data_flights`.`dayOfWeek`) AS `TEMP(TC_)(105357904)(0)`, SUM(`kibana_sample_data_flights`.`AvgTicketPrice`) AS `TEMP(TC_)(2465277995)(0)` FROM `kibana_sample_data_flights` GROUP BY 1 +SELECT 1 AS `empty` FROM `kibana_sample_data_flights` +SELECT substring(OriginWeather, 1, 2) AS OriginWeather FROM kibana_sample_data_flights +SELECT SUM(FlightDelayMin) AS sum_FlightDelayMin_ok FROM kibana_sample_data_flights +SELECT SUM(FlightDelay) AS sum_FlightDelay_ok FROM kibana_sample_data_flights +SELECT SUM(DistanceMiles) AS sum_DistanceMiles_ok FROM kibana_sample_data_flights +SELECT year(timestamp) AS yr_timestamp_ok FROM kibana_sample_data_flights +SELECT abs(AvgTicketPrice) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT acos(FlightDelayMin) AS sum_Calculation_160722252358221825_ok FROM kibana_sample_data_flights +SELECT asin(FlightDelayMin) AS sum_Calculation_160722252358545410_ok FROM kibana_sample_data_flights +SELECT atan(FlightDelayMin) AS sum_Calculation_160722252358811651_ok FROM kibana_sample_data_flights +SELECT atan2(FlightDelayMin,dayOfWeek) AS sum_Calculation_160722252358811651_ok FROM kibana_sample_data_flights +SELECT SUM(FlightDelayMin) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT cos(FlightDelayMin) AS sum_Calculation_160722252358221825_ok FROM kibana_sample_data_flights +SELECT cot(AvgTicketPrice) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT degrees(FlightDelayMin) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT FlightDelayMin div AvgTicketPrice AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT exp(FlightDelayMin) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT SUM(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT SUM((((CASE WHEN (ABS((AvgTicketPrice) - (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((FlightDelayMin) - (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN 1.5 ELSE 0.0 END) - (CASE WHEN ((FlightDelayMin) - (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0) < 0.0) AND ((CASE WHEN (ABS((AvgTicketPrice) - (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((FlightDelayMin) - (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) > 0.0) THEN 3.0 ELSE 0.0 END)) + (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0))) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT SUM(ROUND( (((CASE WHEN (ABS((AvgTicketPrice) - (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((FlightDelayMin) - (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) - (CASE WHEN ((AvgTicketPrice) - (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0)) < 0.0) AND ((CASE WHEN (ABS((AvgTicketPrice) - (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0)))) + SQRT(3.0) * ((ABS((FlightDelayMin) - (ROUND( ( (FlightDelayMin) / 3.0 ), 0 ) * 3.0))) - 1.0) > 0.0 THEN SQRT(3.0) / 2.0 ELSE 0.0 END) > 0.0) THEN SQRT(3.0) ELSE 0.0 END)) + (ROUND( ( (AvgTicketPrice) / SQRT(3.0) ), 0 ) * SQRT(3.0))), 3)) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT log(FlightDelayMin) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT (log(FlightDelayMin)/log(10)) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT MAX(FlightDelayMin) AS usr_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT MIN(FlightDelayMin) AS usr_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT sum((case when dayOfWeek >= 0 or floor(2) = 2 then power(dayOfWeek,2) end)) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT radians(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT round(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT sign(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT sin(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT sqrt(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT power(dayOfWeek, 2) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT tan(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT SUM(dayOfWeek) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT ascii(substring(OriginWeather, 1, 5)) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT Dest, locate('air',Dest) FROM kibana_sample_data_flights +SELECT substring(OriginWeather, 1, 1024) AS OriginWeather FROM kibana_sample_data_flights WHERE (right(rtrim(lower(substring(OriginWeather, 1, 5))), length('.')) ='.') +SELECT sum(if(isnull(1), null, locate('Cape',Origin,greatest(1,floor(1))))) AS sum_Calculation_462181953493630977_ok FROM kibana_sample_data_flights +SELECT (case when 3 >= 0 then left(Origin,3) else null end) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT length(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT lower(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT ltrim(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT max(Origin) AS usr_Calculation_462181953493630977_nk FROM kibana_sample_data_flights +SELECT if(isnull(0), null, substring(Origin,greatest(1,floor(0)),floor(5))) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT min(Origin) AS usr_Calculation_462181953493630977_nk FROM kibana_sample_data_flights +SELECT replace(Origin,'Airport','') AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT (case when 3 >= 0 then right(Origin,3) else null end) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT rtrim(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT if(AvgTicketPrice >= 0, space(floor(AvgTicketPrice)), null) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT trim(leading '-' FROM trim(leading substring(Origin, '-', (2 - 1)) FROM substring_index(Origin, '-', 2))) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT substring(OriginWeather, 1, 5) AS OriginWeather FROM kibana_sample_data_flights where (left(ltrim(lower(substring(OriginWeather, 1, 5))), length('$')) = '$') +SELECT trim(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT upper(Origin) AS Calculation_462181953493630977 FROM kibana_sample_data_flights +SELECT adddate( date_format( date(timestamp), '%Y-01-01 00:00:00' ), interval 0 second ) AS tyr_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT year(date(timestamp)) AS yr_Calculation_462181953481519104_ok FROM kibana_sample_data_flights +SELECT year(timestamp(str_to_date('5.April.2004', '%d.%i.%Y'))) AS yr_Calculation_462181953481519104_ok FROM kibana_sample_data_flights +SELECT dayofmonth(timestamp) AS Calculation_462181953481519104 FROM kibana_sample_data_flights +SELECT 2019 AS yr_Calculation_462181953481519104_ok FROM kibana_sample_data_flights +SELECT max(timestamp) AS max_timestamp_ok FROM kibana_sample_data_flights +SELECT min(timestamp) AS max_timestamp_ok FROM kibana_sample_data_flights +SELECT month(timestamp) AS mn_timestamp_ok FROM kibana_sample_data_flights +SELECT year(now()) AS yr_Calculation_462181953481519104_ok FROM kibana_sample_data_flights +SELECT year(curdate()) AS yr_Calculation_462181953481519104_ok FROM kibana_sample_data_flights +SELECT year(timestamp) AS yr_timestamp_ok FROM kibana_sample_data_flights +SELECT ((Origin = 'Frankfurt am Main Airport') and (Dest = 'Sydney Kingsford Smith International Airport')) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case OriginWeather when 'Sunny' then '1' when 'Rain' then '0' else 'NA' end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (FlightDelay = 0) then 'No delay' else cast(null as char(1)) end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (FlightDelay = 0) then 'No delay' when (FlightDelay = 1) then 'Delay' else cast(null as char(1)) end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (right(rtrim(Origin), length('Airport')) = 'Airport') AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (FlightDelay = 0) then 'No delay' else cast(null as char(1)) end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT ifnull(Cancelled, AvgTicketPrice) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (AvgTicketPrice > 500) THEN 'High' when not (AvgTicketPrice > 500) then 'Low' else null end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (not isnull(date_format(Origin, '%Y'))) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT isnull(FlightNum) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT MAX(AvgTicketPrice) AS max_AvgTicketPrice_ok FROM kibana_sample_data_flights +SELECT MIN(AvgTicketPrice) AS min_AvgTicketPrice_ok FROM kibana_sample_data_flights +SELECT (not isnull(date_format(Origin, '%Y'))) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT ((Origin = 'Frankfurt am Main Airport') or (Dest = 'Sydney Kingsford Smith International Airport')) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (AvgTicketPrice > 500) THEN 'High' when not (AvgTicketPrice > 500) then 'Low' else null end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT (case when (AvgTicketPrice > 500) THEN 'High' when not (AvgTicketPrice > 500) then 'Low' else null end) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT ifnull(FlightDelay, 0) AS Calculation_462181953506873347 FROM kibana_sample_data_flights +SELECT min(Origin) AS temp(Calculation_462181953504628738)(2376748618)(0), max(Origin) AS temp(Calculation_462181953504628738)(2968235173)(0) FROM kibana_sample_data_flights +SELECT AVG(dayOfWeek) AS avg_dayOfWeek_ok FROM kibana_sample_data_flights +SELECT SUM(1) AS cnt_dayOfWeek_ok FROM kibana_sample_data_flights +SELECT COUNT(DISTINCT AvgTicketPrice) AS ctd_AvgTicketPrice_ok FROM kibana_sample_data_flights +SELECT MAX(AvgTicketPrice) AS max_AvgTicketPrice_ok FROM kibana_sample_data_flights +SELECT MIN(AvgTicketPrice) AS min_AvgTicketPrice_ok FROM kibana_sample_data_flights +SELECT sum((AvgTicketPrice * AvgTicketPrice)) AS temp(Calculation_462181953506873347)(1705728846)(0), sum(AvgTicketPrice) AS temp(Calculation_462181953506873347)(2465277995)(0), count(AvgTicketPrice) AS temp(Calculation_462181953506873347)(2633997250)(0) FROM kibana_sample_data_flights +SELECT count(DistanceMiles) AS temp(Calculation_462181953506873347)(2070533874)(0), sum(DistanceMiles) AS temp(Calculation_462181953506873347)(3496560911)(0), sum((DistanceMiles * DistanceMiles)) AS temp(Calculation_462181953506873347)(3595387140)(0) FROM kibana_sample_data_flights +SELECT SUM(dayOfWeek) AS usr_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT substring(OriginWeather, 1, 5) AS OriginWeather FROM kibana_sample_data_flights WHERE (substring(OriginWeather, 1, 5) = 'ABC') +SELECT sum((case when isnull(FlightDelayMin) then null when isnull(10) then null else greatest(FlightDelayMin, 10) end)) AS sum_Calculation_160722252357632000_ok FROM kibana_sample_data_flights +SELECT AvgTicketPrice AS AvgTicketPrice, Cancelled AS Cancelled, Carrier AS Carrier, DestAirportID AS DestAirportID, DestCityName AS DestCityName, DestCountry AS DestCountry, DestLocation AS DestLocation FROM kibana_sample_data_ecommerce INNER JOIN kibana_sample_data_flights ON (kibana_sample_data_ecommerce.day_of_week_i = dayOfWeek) LIMIT 1000 +SELECT AvgTicketPrice AS AvgTicketPrice, Cancelled AS Cancelled, Carrier AS Carrier, DestAirportID AS DestAirportID, DestCityName AS DestCityName, DestCountry AS DestCountry, DestLocation AS DestLocation FROM kibana_sample_data_ecommerce LEFT JOIN kibana_sample_data_flights ON (kibana_sample_data_ecommerce.day_of_week_i = dayOfWeek) LIMIT 1000 +SELECT AvgTicketPrice AS AvgTicketPrice, Cancelled AS Cancelled, Carrier AS Carrier, DestAirportID AS DestAirportID, DestCityName AS DestCityName, DestCountry AS DestCountry, DestLocation AS DestLocation FROM kibana_sample_data_ecommerce RIGHT JOIN kibana_sample_data_flights ON (kibana_sample_data_ecommerce.day_of_week_i = dayOfWeek) LIMIT 1000 +SELECT OriginCityName FROM kibana_sample_data_flights ORDER BY OriginCityName ASC +SELECT OriginCityName FROM kibana_sample_data_flights ORDER BY OriginCityName DESC +SELECT DestCityName, SUM(AvgTicketPrice) AS $__alias__0 FROM kibana_sample_data_flights ORDER BY $__alias__0 DESC, DestCityName ASC LIMIT 10 +SELECT AvgTicketPrice AS AvgTicketPrice, Cancelled AS Cancelled, Carrier AS Carrier FROM kibana_sample_data_ecommerce INNER JOIN kibana_sample_data_flights ON (kibana_sample_data_ecommerce.day_of_week_i = dayOfWeek) LIMIT 1000 +SELECT AvgTicketPrice AS AvgTicketPrice, Cancelled AS Cancelled, Carrier AS Carrier FROM kibana_sample_data_ecommerce LEFT JOIN kibana_sample_data_flights ON (kibana_sample_data_ecommerce.day_of_week_i = dayOfWeek) LIMIT 1000 diff --git a/src/test/resources/correctness/test_data_test.csv b/src/test/resources/correctness/test_data_test.csv new file mode 100644 index 0000000000..29489d2f77 --- /dev/null +++ b/src/test/resources/correctness/test_data_test.csv @@ -0,0 +1,3 @@ +AvgTicketPrice,Cancelled,Carrier,dayOfWeek,timestamp +1000,true,Delta,1,123 +2000,false,Southwest,2,456 \ No newline at end of file diff --git a/src/test/resources/correctness/test_data_test.json b/src/test/resources/correctness/test_data_test.json new file mode 100644 index 0000000000..c77772d674 --- /dev/null +++ b/src/test/resources/correctness/test_data_test.json @@ -0,0 +1,21 @@ +{ + "mappings": { + "properties": { + "AvgTicketPrice": { + "type": "float" + }, + "Cancelled": { + "type": "boolean" + }, + "Carrier": { + "type": "keyword" + }, + "dayOfWeek": { + "type": "integer" + }, + "timestamp": { + "type": "date" + } + } + } +}