Move the JDBC functionality integration tests from `:sql:qa` to a separate module `:sql:qa:jdbc`. This way the tests are isolated from the rest of the integration tests and they only depend to the `:sql:jdbc` module, thus removing the danger of accidentally pulling in some dependency that may hide bugs. Moreover this is a preparation for #56722, so that we can run those tests between different JDBC and ES node versions and ensure forward compatibility. Move the rest of existing tests inside a new `:sql:qa:server` project, so that the `:sql:qa` becomes the parent project for both and one can run all the integration tests by using this parent project. (cherry picked from commit c09f4a04484b8a43934fe58fbc41bd90b7dbcc76)
This commit is contained in:
parent
d8165a3439
commit
b91bae30b1
|
@ -3,10 +3,11 @@
|
|||
[[xpack-sql]]
|
||||
= SQL access
|
||||
|
||||
:sql-tests: {xes-repo-dir}/../../plugin/sql/qa
|
||||
:sql-specs: {sql-tests}/src/main/resources/
|
||||
:jdbc-tests: {sql-tests}/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc
|
||||
:security-tests: {sql-tests}/security/src/test/java/org/elasticsearch/xpack/sql/qa/security
|
||||
:sql-tests: {xes-repo-dir}/../../plugin/sql/qa/
|
||||
:sql-specs: {sql-tests}server/src/main/resources/
|
||||
:jdbc-tests: {sql-tests}jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc
|
||||
:security-tests: {sql-tests}server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security
|
||||
:es-sql: Elasticsearch SQL
|
||||
|
||||
[partintro]
|
||||
--
|
||||
|
|
|
@ -35,6 +35,6 @@ indices:
|
|||
|
||||
[source, yaml]
|
||||
--------------------------------------------------
|
||||
include-tagged::{sql-tests}/security/roles.yml[cli_drivers]
|
||||
include-tagged::{sql-tests}server/security/roles.yml[cli_drivers]
|
||||
--------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,128 +1 @@
|
|||
description = 'Integration tests for SQL'
|
||||
apply plugin: 'elasticsearch.build'
|
||||
archivesBaseName = 'qa-sql'
|
||||
group = "org.elasticsearch.x-pack.qa.sql"
|
||||
|
||||
dependencies {
|
||||
compile project(":test:framework")
|
||||
|
||||
// JDBC testing dependencies
|
||||
compile project(path: xpackModule('sql:jdbc'))
|
||||
|
||||
compile "net.sourceforge.csvjdbc:csvjdbc:${csvjdbcVersion}"
|
||||
|
||||
// CLI testing dependencies
|
||||
compile project(path: xpackModule('sql:sql-cli'))
|
||||
|
||||
// H2GIS testing dependencies
|
||||
compile("org.orbisgis:h2gis:${h2gisVersion}") {
|
||||
exclude group: "org.locationtech.jts"
|
||||
}
|
||||
|
||||
// select just the parts of JLine that are needed
|
||||
compile("org.jline:jline-terminal-jna:${jlineVersion}") {
|
||||
exclude group: "net.java.dev.jna"
|
||||
}
|
||||
compile "org.jline:jline-terminal:${jlineVersion}"
|
||||
compile "org.jline:jline-reader:${jlineVersion}"
|
||||
compile "org.jline:jline-style:${jlineVersion}"
|
||||
|
||||
testRuntime "org.elasticsearch:jna:${versions.jna}"
|
||||
}
|
||||
|
||||
/* disable unit tests because these are all integration tests used
|
||||
* other qa projects. */
|
||||
test.enabled = false
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
dependenciesInfo.enabled = false
|
||||
|
||||
// the main files are actually test files, so use the appropriate forbidden api sigs
|
||||
tasks.named('forbiddenApisMain').configure {
|
||||
replaceSignatureFiles 'es-all-signatures', 'es-test-signatures'
|
||||
}
|
||||
|
||||
// just a test fixture: we aren't using this jars in releases and H2GIS requires disabling a lot of checks
|
||||
thirdPartyAudit.enabled = false
|
||||
|
||||
subprojects {
|
||||
if (subprojects.isEmpty()) {
|
||||
// leaf project
|
||||
apply plugin: 'elasticsearch.standalone-rest-test'
|
||||
} else {
|
||||
apply plugin: 'elasticsearch.build'
|
||||
}
|
||||
|
||||
configurations.testRuntimeClasspath {
|
||||
resolutionStrategy.force "org.slf4j:slf4j-api:1.7.25"
|
||||
}
|
||||
configurations.testRuntime {
|
||||
// This is also required to make resolveAllDependencies work
|
||||
resolutionStrategy.force "org.slf4j:slf4j-api:1.7.25"
|
||||
}
|
||||
dependencies {
|
||||
|
||||
/* Since we're a standalone rest test we actually get transitive
|
||||
* dependencies but we don't really want them because they cause
|
||||
* all kinds of trouble with the jar hell checks. So we suppress
|
||||
* them explicitly for non-es projects. */
|
||||
testCompile(xpackProject('plugin:sql:qa')) {
|
||||
transitive = false
|
||||
}
|
||||
testCompile project(":test:framework")
|
||||
|
||||
// JDBC testing dependencies
|
||||
testRuntime "net.sourceforge.csvjdbc:csvjdbc:${csvjdbcVersion}"
|
||||
testRuntime "com.h2database:h2:${h2Version}"
|
||||
|
||||
// H2GIS testing dependencies
|
||||
testRuntime("org.orbisgis:h2gis:${h2gisVersion}") {
|
||||
exclude group: "org.locationtech.jts"
|
||||
exclude group: "com.fasterxml.jackson.core"
|
||||
}
|
||||
|
||||
testRuntime project(path: xpackModule('sql:jdbc'))
|
||||
testRuntime xpackProject('plugin:sql:sql-client')
|
||||
|
||||
// TODO check if needed
|
||||
testRuntime("org.antlr:antlr4-runtime:${antlrVersion}") {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
// CLI testing dependencies
|
||||
testRuntime project(path: xpackModule('sql:sql-cli'))
|
||||
testRuntime(xpackProject('plugin:sql:sql-action')) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
testRuntime("org.jline:jline-terminal-jna:${jlineVersion}") {
|
||||
exclude group: "net.java.dev.jna"
|
||||
}
|
||||
testRuntime "org.jline:jline-terminal:${jlineVersion}"
|
||||
testRuntime "org.jline:jline-reader:${jlineVersion}"
|
||||
testRuntime "org.jline:jline-style:${jlineVersion}"
|
||||
|
||||
testRuntime "org.elasticsearch:jna:${versions.jna}"
|
||||
|
||||
// spatial dependency
|
||||
testRuntime project(path: xpackModule('spatial'))
|
||||
}
|
||||
|
||||
if (project.name != 'security') {
|
||||
// The security project just configures its subprojects
|
||||
apply plugin: 'elasticsearch.testclusters'
|
||||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
testClusters.integTest {
|
||||
testDistribution = 'DEFAULT'
|
||||
setting 'xpack.ml.enabled', 'false'
|
||||
setting 'xpack.watcher.enabled', 'false'
|
||||
}
|
||||
|
||||
task runqa {
|
||||
doFirst {
|
||||
println "Run with `-Dtestclusters.inspect.failure=true integTest` to leave the cluster running after failure"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
description = 'Integration tests for SQL JDBC driver'
|
||||
apply plugin: 'elasticsearch.build'
|
||||
|
||||
// Avoid circular dependency
|
||||
group = "org.elasticsearch.x-pack.qa.sql.jdbc"
|
||||
|
||||
dependencies {
|
||||
compile project(":test:framework")
|
||||
|
||||
// JDBC testing dependencies
|
||||
compile project(path: xpackModule('sql:jdbc'))
|
||||
}
|
||||
|
||||
/* disable unit tests because these are all integration tests used
|
||||
* other qa projects. */
|
||||
test.enabled = false
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
dependenciesInfo.enabled = false
|
||||
|
||||
// the main files are actually test files, so use the appropriate forbidden api sigs
|
||||
tasks.named('forbiddenApisMain').configure {
|
||||
replaceSignatureFiles 'es-all-signatures', 'es-test-signatures'
|
||||
}
|
||||
|
||||
// just a test fixture: we aren't using this jars in releases and H2GIS requires disabling a lot of checks
|
||||
thirdPartyAudit.enabled = false
|
||||
|
||||
subprojects {
|
||||
if (subprojects.isEmpty()) {
|
||||
// leaf project
|
||||
apply plugin: 'elasticsearch.standalone-rest-test'
|
||||
} else {
|
||||
apply plugin: 'elasticsearch.build'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Since we're a standalone rest test we actually get transitive
|
||||
* dependencies but we don't really want them because they cause
|
||||
* all kinds of trouble with the jar hell checks. So we suppress
|
||||
* them explicitly for non-es projects. */
|
||||
testCompile(xpackProject('plugin:sql:qa:jdbc')) {
|
||||
transitive = false
|
||||
}
|
||||
testCompile project(":test:framework")
|
||||
|
||||
testRuntime project(path: xpackModule('sql:jdbc'))
|
||||
}
|
||||
|
||||
if (project.name != 'security') {
|
||||
// The security project just configures its subprojects
|
||||
apply plugin: 'elasticsearch.testclusters'
|
||||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
testClusters.integTest {
|
||||
testDistribution = 'DEFAULT'
|
||||
setting 'xpack.ml.enabled', 'false'
|
||||
setting 'xpack.watcher.enabled', 'false'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
description = 'Run SQL JDBC tests against multiple nodes'
|
||||
|
||||
testClusters.integTest {
|
||||
numberOfNodes = 2
|
||||
setting 'xpack.security.enabled', 'false'
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.single_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.multi_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ConnectionTestCase;
|
||||
|
||||
public class JdbcConnectionIT extends ConnectionTestCase {
|
||||
}
|
||||
public class JdbcConnectionIT extends ConnectionTestCase {}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.single_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.multi_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.FetchSizeTestCase;
|
||||
|
||||
public class JdbcFetchSizeIT extends FetchSizeTestCase {
|
||||
}
|
||||
public class JdbcFetchSizeIT extends FetchSizeTestCase {}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.multi_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.JdbcErrorsTestCase;
|
||||
|
||||
public class JdbcJdbcErrorsIT extends JdbcErrorsTestCase {}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.multi_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.multi_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.PreparedStatementTestCase;
|
||||
|
||||
public class JdbcPreparedStatementIT extends PreparedStatementTestCase {
|
||||
}
|
||||
public class JdbcPreparedStatementIT extends PreparedStatementTestCase {}
|
|
@ -0,0 +1,4 @@
|
|||
testClusters.integTest {
|
||||
setting 'xpack.security.enabled', 'false'
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
}
|
|
@ -4,10 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.sql.qa.no_sql;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.no_sql;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.JdbcNoSqlTestCase;
|
||||
|
||||
public class JdbcNoSqlIT extends JdbcNoSqlTestCase {
|
||||
|
||||
}
|
||||
public class JdbcNoSqlIT extends JdbcNoSqlTestCase {}
|
|
@ -0,0 +1,61 @@
|
|||
dependencies {
|
||||
testCompile project(':x-pack:plugin:core')
|
||||
}
|
||||
|
||||
Project mainProject = project
|
||||
|
||||
configurations.create('testArtifacts')
|
||||
|
||||
TaskProvider testJar = tasks.register("testJar", Jar) {
|
||||
appendix 'test'
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
artifacts {
|
||||
testArtifacts testJar
|
||||
}
|
||||
|
||||
// Tests are pushed down to subprojects and will be checked there.
|
||||
testingConventions.enabled = false
|
||||
|
||||
subprojects {
|
||||
// Use tests from the root security qa project in subprojects
|
||||
configurations.create('testArtifacts')
|
||||
|
||||
dependencies {
|
||||
testCompile project(":x-pack:plugin:core")
|
||||
testArtifacts project(path: mainProject.path, configuration: 'testArtifacts')
|
||||
}
|
||||
|
||||
testClusters.integTest {
|
||||
testDistribution = 'DEFAULT'
|
||||
// Setup auditing so we can use it in some tests
|
||||
setting 'xpack.security.audit.enabled', 'true'
|
||||
setting 'xpack.security.enabled', 'true'
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
// Setup roles used by tests
|
||||
extraConfigFile 'roles.yml', mainProject.file('roles.yml')
|
||||
/* Setup the one admin user that we run the tests as.
|
||||
* Tests use "run as" to get different users. */
|
||||
user username: "test_admin", password: "x-pack-test-password"
|
||||
}
|
||||
|
||||
File testArtifactsDir = project.file("$buildDir/testArtifacts")
|
||||
TaskProvider copyTestClasses = tasks.register("copyTestClasses", Copy) {
|
||||
dependsOn configurations.testArtifacts
|
||||
from { zipTree(configurations.testArtifacts.singleFile) }
|
||||
into testArtifactsDir
|
||||
}
|
||||
|
||||
integTest.runner {
|
||||
dependsOn copyTestClasses
|
||||
testClassesDirs += project.files(testArtifactsDir)
|
||||
classpath += configurations.testArtifacts
|
||||
nonInputProperties.systemProperty 'tests.audit.logfile',
|
||||
"${-> testClusters.integTest.singleNode().getAuditLog()}"
|
||||
nonInputProperties.systemProperty 'tests.audit.yesterday.logfile',
|
||||
"${-> testClusters.integTest.singleNode().getAuditLog().getParentFile()}/integTest_audit-${new Date().format('yyyy-MM-dd')}.json"
|
||||
}
|
||||
|
||||
testingConventions.enabled = false
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.Booleans;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ConnectionTestCase;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||
|
||||
public class JdbcConnectionIT extends ConnectionTestCase {
|
||||
|
||||
static final boolean SSL_ENABLED = Booleans.parseBoolean(System.getProperty("tests.ssl.enabled"), false);
|
||||
|
||||
static Settings securitySettings() {
|
||||
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||
Settings.Builder builder = Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token);
|
||||
if (SSL_ENABLED) {
|
||||
Path keyStore;
|
||||
try {
|
||||
keyStore = PathUtils.get(getTestClass().getResource("/test-node.jks").toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("exception while reading the store", e);
|
||||
}
|
||||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
builder.put(ESRestTestCase.TRUSTSTORE_PATH, keyStore).put(ESRestTestCase.TRUSTSTORE_PASSWORD, "keypass");
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
properties.putAll(JdbcSecurityUtils.adminProperties());
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.security;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.FetchSizeTestCase;
|
||||
|
@ -11,20 +11,21 @@ import org.elasticsearch.xpack.sql.qa.jdbc.FetchSizeTestCase;
|
|||
import java.util.Properties;
|
||||
|
||||
public class JdbcFetchSizeIT extends FetchSizeTestCase {
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return RestSqlIT.securitySettings();
|
||||
return JdbcConnectionIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
return JdbcConnectionIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
properties.putAll(JdbcSecurityIT.adminProperties());
|
||||
properties.putAll(JdbcSecurityUtils.adminProperties());
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -3,28 +3,29 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.security;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ConnectionTestCase;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.JdbcErrorsTestCase;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class JdbcConnectionIT extends ConnectionTestCase {
|
||||
public class JdbcJdbcErrorsIT extends JdbcErrorsTestCase {
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return RestSqlIT.securitySettings();
|
||||
return JdbcConnectionIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
return JdbcConnectionIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
properties.putAll(JdbcSecurityIT.adminProperties());
|
||||
properties.putAll(JdbcSecurityUtils.adminProperties());
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.security;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.PreparedStatementTestCase;
|
||||
|
@ -11,20 +11,21 @@ import org.elasticsearch.xpack.sql.qa.jdbc.PreparedStatementTestCase;
|
|||
import java.util.Properties;
|
||||
|
||||
public class JdbcPreparedStatementIT extends PreparedStatementTestCase {
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return RestSqlIT.securitySettings();
|
||||
return JdbcConnectionIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
return JdbcConnectionIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties sp = super.connectionProperties();
|
||||
sp.putAll(JdbcSecurityIT.adminProperties());
|
||||
sp.putAll(JdbcSecurityUtils.adminProperties());
|
||||
return sp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.apache.lucene.util.LuceneTestCase.getTestClass;
|
||||
|
||||
final class JdbcSecurityUtils {
|
||||
|
||||
private JdbcSecurityUtils() {}
|
||||
|
||||
static Properties adminProperties() {
|
||||
// tag::admin_properties
|
||||
Properties properties = new Properties();
|
||||
properties.put("user", "test_admin");
|
||||
properties.put("password", "x-pack-test-password");
|
||||
// end::admin_properties
|
||||
addSslPropertiesIfNeeded(properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static void addSslPropertiesIfNeeded(Properties properties) {
|
||||
if (false == JdbcConnectionIT.SSL_ENABLED) {
|
||||
return;
|
||||
}
|
||||
Path keyStore;
|
||||
try {
|
||||
keyStore = PathUtils.get(getTestClass().getResource("/test-node.jks").toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("exception while reading the store", e);
|
||||
}
|
||||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
String keyStoreStr = keyStore.toAbsolutePath().toString();
|
||||
|
||||
properties.put("ssl", "true");
|
||||
properties.put("ssl.keystore.location", keyStoreStr);
|
||||
properties.put("ssl.keystore.pass", "keypass");
|
||||
properties.put("ssl.truststore.location", keyStoreStr);
|
||||
properties.put("ssl.truststore.pass", "keypass");
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.security;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.SimpleExampleTestCase;
|
||||
|
@ -11,20 +11,21 @@ import org.elasticsearch.xpack.sql.qa.jdbc.SimpleExampleTestCase;
|
|||
import java.util.Properties;
|
||||
|
||||
public class JdbcSimpleExampleIT extends SimpleExampleTestCase {
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return RestSqlIT.securitySettings();
|
||||
return JdbcConnectionIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
return JdbcConnectionIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
properties.putAll(JdbcSecurityIT.adminProperties());
|
||||
properties.putAll(JdbcSecurityUtils.adminProperties());
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
import org.elasticsearch.gradle.LoggedExec
|
||||
import org.elasticsearch.gradle.info.BuildParams
|
||||
|
||||
// Tell the tests we're running with ssl enabled
|
||||
integTest.runner {
|
||||
systemProperty 'tests.ssl.enabled', 'true'
|
||||
}
|
||||
|
||||
// needed to be consistent with ssl host checking
|
||||
Object san = new SanEvaluator()
|
||||
|
||||
// needed to be consistent with ssl host checking
|
||||
String host = InetAddress.getLoopbackAddress().getHostAddress();
|
||||
|
||||
// location of generated keystores and certificates
|
||||
File keystoreDir = new File(project.buildDir, 'keystore')
|
||||
|
||||
// Generate the node's keystore
|
||||
File nodeKeystore = file("$keystoreDir/test-node.jks")
|
||||
task createNodeKeyStore(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (nodeKeystore.parentFile.exists() == false) {
|
||||
nodeKeystore.parentFile.mkdirs()
|
||||
}
|
||||
if (nodeKeystore.exists()) {
|
||||
delete nodeKeystore
|
||||
}
|
||||
}
|
||||
executable = "${BuildParams.compilerJavaHome}/bin/keytool"
|
||||
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
|
||||
args '-genkey',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', nodeKeystore,
|
||||
'-keyalg', 'RSA',
|
||||
'-keysize', '2048',
|
||||
'-validity', '712',
|
||||
'-dname', 'CN=' + host,
|
||||
'-keypass', 'keypass',
|
||||
'-storepass', 'keypass',
|
||||
'-ext', san
|
||||
}
|
||||
|
||||
// Generate the client's keystore
|
||||
File clientKeyStore = file("$keystoreDir/test-client.jks")
|
||||
task createClientKeyStore(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (clientKeyStore.parentFile.exists() == false) {
|
||||
clientKeyStore.parentFile.mkdirs()
|
||||
}
|
||||
if (clientKeyStore.exists()) {
|
||||
delete clientKeyStore
|
||||
}
|
||||
}
|
||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
|
||||
args '-genkey',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', clientKeyStore,
|
||||
'-keyalg', 'RSA',
|
||||
'-keysize', '2048',
|
||||
'-validity', '712',
|
||||
'-dname', 'CN=' + host,
|
||||
'-keypass', 'keypass',
|
||||
'-storepass', 'keypass',
|
||||
'-ext', san
|
||||
}
|
||||
|
||||
// Export the node's certificate
|
||||
File nodeCertificate = file("$keystoreDir/test-node.cert")
|
||||
task exportNodeCertificate(type: LoggedExec) {
|
||||
dependsOn createNodeKeyStore
|
||||
doFirst {
|
||||
if (nodeCertificate.parentFile.exists() == false) {
|
||||
nodeCertificate.parentFile.mkdirs()
|
||||
}
|
||||
if (nodeCertificate.exists()) {
|
||||
delete nodeCertificate
|
||||
}
|
||||
}
|
||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||
args '-export',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', nodeKeystore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', nodeCertificate
|
||||
}
|
||||
|
||||
// Import the node certificate in the client's keystore
|
||||
task importNodeCertificateInClientKeyStore(type: LoggedExec) {
|
||||
dependsOn createClientKeyStore, exportNodeCertificate
|
||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||
args '-import',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', clientKeyStore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', nodeCertificate,
|
||||
'-noprompt'
|
||||
}
|
||||
|
||||
// Export the client's certificate
|
||||
File clientCertificate = file("$keystoreDir/test-client.cert")
|
||||
task exportClientCertificate(type: LoggedExec) {
|
||||
dependsOn createClientKeyStore
|
||||
doFirst {
|
||||
if (clientCertificate.parentFile.exists() == false) {
|
||||
clientCertificate.parentFile.mkdirs()
|
||||
}
|
||||
if (clientCertificate.exists()) {
|
||||
delete clientCertificate
|
||||
}
|
||||
}
|
||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||
args '-export',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', clientKeyStore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', clientCertificate
|
||||
}
|
||||
|
||||
// Import the client certificate in the node's keystore
|
||||
task importClientCertificateInNodeKeyStore(type: LoggedExec) {
|
||||
dependsOn createNodeKeyStore, exportClientCertificate
|
||||
executable = "${BuildParams.runtimeJavaHome}/bin/keytool"
|
||||
args '-import',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', nodeKeystore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', clientCertificate,
|
||||
'-noprompt'
|
||||
}
|
||||
|
||||
forbiddenPatterns {
|
||||
exclude '**/*.cert'
|
||||
}
|
||||
|
||||
// Add keystores to test classpath: it expects it there
|
||||
sourceSets.test.resources.srcDir(keystoreDir)
|
||||
processTestResources.dependsOn(importNodeCertificateInClientKeyStore, importClientCertificateInNodeKeyStore)
|
||||
|
||||
integTest.runner {
|
||||
dependsOn(importClientCertificateInNodeKeyStore)
|
||||
onlyIf {
|
||||
// Do not attempt to form a cluster in a FIPS JVM, as doing so with a JKS keystore will fail.
|
||||
// TODO Revisit this when SQL CLI client can handle key/certificate instead of only Keystores.
|
||||
// https://github.com/elastic/elasticsearch/issues/32306
|
||||
BuildParams.inFipsJvm == false
|
||||
}
|
||||
}
|
||||
|
||||
testClusters.integTest {
|
||||
// The setup that we actually want
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
setting 'xpack.security.http.ssl.enabled', 'true'
|
||||
setting 'xpack.security.transport.ssl.enabled', 'true'
|
||||
|
||||
// ceremony to set up ssl
|
||||
setting 'xpack.security.transport.ssl.keystore.path', 'test-node.jks'
|
||||
setting 'xpack.security.http.ssl.keystore.path', 'test-node.jks'
|
||||
keystore 'xpack.security.transport.ssl.keystore.secure_password', 'keypass'
|
||||
keystore 'xpack.security.http.ssl.keystore.secure_password', 'keypass'
|
||||
|
||||
|
||||
// copy keystores into config/
|
||||
extraConfigFile nodeKeystore.name, nodeKeystore
|
||||
extraConfigFile clientKeyStore.name, clientKeyStore
|
||||
}
|
||||
|
||||
|
||||
/** A lazy evaluator to find the san to use for certificate generation. */
|
||||
class SanEvaluator {
|
||||
|
||||
private static String san = null
|
||||
|
||||
String toString() {
|
||||
synchronized (SanEvaluator.class) {
|
||||
if (san == null) {
|
||||
san = getSubjectAlternativeNameString()
|
||||
}
|
||||
}
|
||||
return san
|
||||
}
|
||||
|
||||
// Code stolen from NetworkUtils/InetAddresses/NetworkAddress to support SAN
|
||||
/** Return all interfaces (and subinterfaces) on the system */
|
||||
private static List<NetworkInterface> getInterfaces() throws SocketException {
|
||||
List<NetworkInterface> all = new ArrayList<>();
|
||||
addAllInterfaces(all, Collections.list(NetworkInterface.getNetworkInterfaces()));
|
||||
Collections.sort(all, new Comparator<NetworkInterface>() {
|
||||
@Override
|
||||
public int compare(NetworkInterface left, NetworkInterface right) {
|
||||
return Integer.compare(left.getIndex(), right.getIndex());
|
||||
}
|
||||
});
|
||||
return all;
|
||||
}
|
||||
|
||||
/** Helper for getInterfaces, recursively adds subinterfaces to {@code target} */
|
||||
private static void addAllInterfaces(List<NetworkInterface> target, List<NetworkInterface> level) {
|
||||
if (!level.isEmpty()) {
|
||||
target.addAll(level);
|
||||
for (NetworkInterface intf : level) {
|
||||
addAllInterfaces(target, Collections.list(intf.getSubInterfaces()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSubjectAlternativeNameString() {
|
||||
List<InetAddress> list = new ArrayList<>();
|
||||
for (NetworkInterface intf : getInterfaces()) {
|
||||
for (final InetAddress address : Collections.list(intf.getInetAddresses())) {
|
||||
/*
|
||||
* Some OS (e.g., BSD) assign a link-local address to the loopback interface. While technically not a loopback interface, some of
|
||||
* these OS treat them as one (e.g., localhost on macOS), so we must too. Otherwise, things just won't work out of the box. So we
|
||||
* include all addresses from loopback interfaces.
|
||||
*
|
||||
* By checking if the interface is a loopback interface or the address is a loopback address first, we avoid having to check if the
|
||||
* interface is up unless necessary. This means we can avoid checking if the interface is up for virtual ethernet devices which have
|
||||
* a tendency to disappear outside of our control (e.g., due to Docker).
|
||||
*/
|
||||
if ((intf.isLoopback() || address.isLoopbackAddress()) && isUp(intf, address)) {
|
||||
list.add(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
throw new IllegalArgumentException("no up-and-running loopback addresses found, got " + getInterfaces());
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder("san=");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
InetAddress address = list.get(i);
|
||||
String hostAddress;
|
||||
if (address instanceof Inet6Address) {
|
||||
hostAddress = compressedIPV6Address((Inet6Address) address);
|
||||
} else {
|
||||
hostAddress = address.getHostAddress();
|
||||
}
|
||||
builder.append("ip:").append(hostAddress);
|
||||
String hostname = address.getHostName();
|
||||
if (hostname.equals(address.getHostAddress()) == false) {
|
||||
builder.append(",dns:").append(hostname);
|
||||
}
|
||||
|
||||
if (i != (list.size() - 1)) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static boolean isUp(final NetworkInterface intf, final InetAddress address) throws IOException {
|
||||
try {
|
||||
return intf.isUp();
|
||||
} catch (final SocketException e) {
|
||||
/*
|
||||
* In Elasticsearch production code (NetworkUtils) we suppress this if the device is a virtual ethernet device. That should not happen
|
||||
* here since the interface must be a loopback device or the address a loopback address to get here to begin with.
|
||||
*/
|
||||
assert intf.isLoopback() || address.isLoopbackAddress()
|
||||
throw new IOException("failed to check if interface [" + intf.getName() + "] is up", e)
|
||||
}
|
||||
}
|
||||
|
||||
private static String compressedIPV6Address(Inet6Address inet6Address) {
|
||||
byte[] bytes = inet6Address.getAddress();
|
||||
int[] hextets = new int[8];
|
||||
for (int i = 0; i < hextets.length; i++) {
|
||||
hextets[i] = (bytes[2 * i] & 255) << 8 | bytes[2 * i + 1] & 255;
|
||||
}
|
||||
compressLongestRunOfZeroes(hextets);
|
||||
return hextetsToIPv6String(hextets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify and mark the longest run of zeroes in an IPv6 address.
|
||||
*
|
||||
* <p>Only runs of two or more hextets are considered. In case of a tie, the
|
||||
* leftmost run wins. If a qualifying run is found, its hextets are replaced
|
||||
* by the sentinel value -1.
|
||||
*
|
||||
* @param hextets {@code int[]} mutable array of eight 16-bit hextets
|
||||
*/
|
||||
private static void compressLongestRunOfZeroes(int[] hextets) {
|
||||
int bestRunStart = -1;
|
||||
int bestRunLength = -1;
|
||||
int runStart = -1;
|
||||
for (int i = 0; i < hextets.length + 1; i++) {
|
||||
if (i < hextets.length && hextets[i] == 0) {
|
||||
if (runStart < 0) {
|
||||
runStart = i;
|
||||
}
|
||||
} else if (runStart >= 0) {
|
||||
int runLength = i - runStart;
|
||||
if (runLength > bestRunLength) {
|
||||
bestRunStart = runStart;
|
||||
bestRunLength = runLength;
|
||||
}
|
||||
runStart = -1;
|
||||
}
|
||||
}
|
||||
if (bestRunLength >= 2) {
|
||||
Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of hextets into a human-readable IPv6 address.
|
||||
*
|
||||
* <p>In order for "::" compression to work, the input should contain negative
|
||||
* sentinel values in place of the elided zeroes.
|
||||
*
|
||||
* @param hextets {@code int[]} array of eight 16-bit hextets, or -1s
|
||||
*/
|
||||
private static String hextetsToIPv6String(int[] hextets) {
|
||||
/*
|
||||
* While scanning the array, handle these state transitions:
|
||||
* start->num => "num" start->gap => "::"
|
||||
* num->num => ":num" num->gap => "::"
|
||||
* gap->num => "num" gap->gap => ""
|
||||
*/
|
||||
StringBuilder buf = new StringBuilder(39);
|
||||
boolean lastWasNumber = false;
|
||||
for (int i = 0; i < hextets.length; i++) {
|
||||
boolean thisIsNumber = hextets[i] >= 0;
|
||||
if (thisIsNumber) {
|
||||
if (lastWasNumber) {
|
||||
buf.append(':');
|
||||
}
|
||||
buf.append(Integer.toHexString(hextets[i]));
|
||||
} else {
|
||||
if (i == 0 || lastWasNumber) {
|
||||
buf.append("::");
|
||||
}
|
||||
}
|
||||
lastWasNumber = thisIsNumber;
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
integTest.runner {
|
||||
systemProperty 'tests.ssl.enabled', 'false'
|
||||
}
|
||||
|
||||
testClusters.integTest {
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
testClusters.integTest {
|
||||
setting 'xpack.security.enabled', 'false'
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ConnectionTestCase;
|
||||
|
||||
public class JdbcConnectionIT extends ConnectionTestCase {}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.multi_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ErrorsTestCase;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.FetchSizeTestCase;
|
||||
|
||||
public class JdbcErrorsIT extends ErrorsTestCase {
|
||||
}
|
||||
public class JdbcFetchSizeIT extends FetchSizeTestCase {}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.JdbcErrorsTestCase;
|
||||
|
||||
public class JdbcJdbcErrorsIT extends JdbcErrorsTestCase {}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.single_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.PreparedStatementTestCase;
|
||||
|
||||
public class JdbcPreparedStatementIT extends PreparedStatementTestCase {
|
||||
}
|
||||
public class JdbcPreparedStatementIT extends PreparedStatementTestCase {}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ResultSetTestCase;
|
||||
|
||||
public class JdbcResultSetIT extends ResultSetTestCase {}
|
|
@ -4,10 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.sql.qa.single_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ResultSetMetaDataTestCase;
|
||||
|
||||
public class JdbcResultSetMetaDataIT extends ResultSetMetaDataTestCase {
|
||||
|
||||
}
|
||||
public class JdbcResultSetMetaDataIT extends ResultSetMetaDataTestCase {}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.JdbcIntegrationTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class JdbcShardFailureIT extends JdbcIntegrationTestCase {
|
||||
|
||||
@Before
|
||||
public void createTestIndex() throws IOException {
|
||||
Request createTest1 = new Request("PUT", "/test1");
|
||||
String body1 = "{\"aliases\":{\"test\":{}}, \"mappings\": {\"properties\": {\"test_field\":{\"type\":\"integer\"}}}}";
|
||||
createTest1.setJsonEntity(body1);
|
||||
client().performRequest(createTest1);
|
||||
|
||||
Request createTest2 = new Request("PUT", "/test2");
|
||||
String body2 = "{\"aliases\":{\"test\":{}}, \"mappings\": {\"properties\": {\"test_field\":{\"type\":\"integer\"}}},"
|
||||
+ "\"settings\": {\"index.routing.allocation.include.node\": \"nowhere\"}}";
|
||||
createTest2.setJsonEntity(body2);
|
||||
createTest2.addParameter("timeout", "100ms");
|
||||
client().performRequest(createTest2);
|
||||
|
||||
Request request = new Request("PUT", "/test1/_bulk");
|
||||
request.addParameter("refresh", "true");
|
||||
StringBuilder bulk = new StringBuilder();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
bulk.append("{\"index\":{}}\n");
|
||||
bulk.append("{\"test_field\":").append(i).append("}\n");
|
||||
}
|
||||
request.setJsonEntity(bulk.toString());
|
||||
client().performRequest(request);
|
||||
}
|
||||
|
||||
public void testPartialResponseHandling() throws SQLException {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
SQLException exception = expectThrows(SQLException.class, () -> s.executeQuery("SELECT * FROM test ORDER BY test_field ASC"));
|
||||
assertThat(exception.getMessage(), containsString("Search rejected due to missing shards"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.single_node;
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc.single_node;
|
||||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.SimpleExampleTestCase;
|
||||
|
||||
public class JdbcSimpleExampleIT extends SimpleExampleTestCase {
|
||||
}
|
||||
public class JdbcSimpleExampleIT extends SimpleExampleTestCase {}
|
|
@ -15,6 +15,7 @@ import java.sql.SQLException;
|
|||
* Test the jdbc {@link Connection} implementation.
|
||||
*/
|
||||
public abstract class ConnectionTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
public void testConnectionProperties() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
||||
assertFalse(c.isClosed());
|
||||
|
@ -34,7 +35,7 @@ public abstract class ConnectionTestCase extends JdbcIntegrationTestCase {
|
|||
/**
|
||||
* Tests that we throw report no transaction isolation and throw sensible errors if you ask for any.
|
||||
*/
|
||||
public void testTransactionIsolation() throws Exception {
|
||||
public void testTransactionIsolation() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
||||
assertEquals(Connection.TRANSACTION_NONE, c.getTransactionIsolation());
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE));
|
|
@ -21,14 +21,12 @@ import java.time.ZoneId;
|
|||
import java.time.ZonedDateTime;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE;
|
||||
import static org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase.assertNoSearchContexts;
|
||||
|
||||
/**
|
||||
* Tests for setting {@link Statement#setFetchSize(int)} and
|
||||
* {@link ResultSet#getFetchSize()}.
|
||||
*/
|
||||
public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
||||
public abstract class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
@Before
|
||||
public void createTestIndex() throws IOException {
|
||||
Request request = new Request("PUT", "/test");
|
||||
|
@ -59,7 +57,7 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
bulkLine.append(", \"nested\":[");
|
||||
// each document will have a nested field with 1 - 5 values
|
||||
for (int j = 0; j <= i % 5; j++) {
|
||||
bulkLine.append("{\"inner_field\":" + j + "}" + ((j == i % 5) ? "" : ","));
|
||||
bulkLine.append("{\"inner_field\":").append(j).append("}").append((j == i % 5) ? "" : ",");
|
||||
}
|
||||
bulkLine.append("]");
|
||||
bulk.append(bulkLine).append("}\n");
|
||||
|
@ -73,8 +71,7 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
* In this case the fetch size should be entirely respected.
|
||||
*/
|
||||
public void testScroll() throws SQLException {
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
s.setFetchSize(4);
|
||||
try (ResultSet rs = s.executeQuery("SELECT * FROM test ORDER BY test_field ASC")) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
|
@ -91,9 +88,8 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
* Test for {@code SELECT} that is implemented as a scroll query.
|
||||
* In this test we don't retrieve all records and rely on close() to clean the cursor
|
||||
*/
|
||||
public void testIncompleteScroll() throws Exception {
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
public void testIncompleteScroll() throws SQLException {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
s.setFetchSize(4);
|
||||
try (ResultSet rs = s.executeQuery("SELECT * FROM test ORDER BY test_field ASC")) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
|
@ -104,7 +100,6 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
assertTrue(rs.next());
|
||||
}
|
||||
}
|
||||
assertNoSearchContexts();
|
||||
}
|
||||
|
||||
public void testScrollWithDatetimeAndTimezoneParam() throws IOException, SQLException {
|
||||
|
@ -136,17 +131,17 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
|
||||
ZoneId zoneId = randomZone();
|
||||
Properties connectionProperties = connectionProperties();
|
||||
connectionProperties.put(JDBC_TIMEZONE, zoneId.toString());
|
||||
try (Connection c = esJdbc(connectionProperties);
|
||||
Statement s = c.createStatement()) {
|
||||
connectionProperties.put(JdbcTestUtils.JDBC_TIMEZONE, zoneId.toString());
|
||||
try (Connection c = esJdbc(connectionProperties); Statement s = c.createStatement()) {
|
||||
s.setFetchSize(2);
|
||||
try (ResultSet rs =
|
||||
s.executeQuery("SELECT DATE_PART('TZOFFSET', date) FROM test_date_timezone ORDER BY date")) {
|
||||
try (ResultSet rs = s.executeQuery("SELECT DATE_PART('TZOFFSET', date) FROM test_date_timezone ORDER BY date")) {
|
||||
for (int i = 0; i < datetimes.length; i++) {
|
||||
assertEquals(2, rs.getFetchSize());
|
||||
assertTrue("No more entries left at " + i, rs.next());
|
||||
assertEquals(ZonedDateTime.ofInstant(Instant.ofEpochMilli(datetimes[i]), zoneId).getOffset()
|
||||
.getTotalSeconds()/ 60, rs.getInt(1));
|
||||
assertEquals(
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(datetimes[i]), zoneId).getOffset().getTotalSeconds() / 60,
|
||||
rs.getInt(1)
|
||||
);
|
||||
}
|
||||
assertFalse(rs.next());
|
||||
}
|
||||
|
@ -157,8 +152,7 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
* Test for {@code SELECT} that is implemented as an aggregation.
|
||||
*/
|
||||
public void testAggregation() throws SQLException {
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
s.setFetchSize(4);
|
||||
try (ResultSet rs = s.executeQuery("SELECT test_field, COUNT(*) FROM test GROUP BY test_field")) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
|
@ -175,9 +169,8 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
/**
|
||||
* Test for nested documents.
|
||||
*/
|
||||
public void testNestedDocuments() throws Exception {
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
public void testNestedDocuments() throws SQLException {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
s.setFetchSize(5);
|
||||
try (ResultSet rs = s.executeQuery("SELECT test_field, nested.* FROM test ORDER BY test_field ASC")) {
|
||||
assertTrue("Empty result set!", rs.next());
|
||||
|
@ -188,7 +181,6 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
assertFalse(rs.next());
|
||||
}
|
||||
}
|
||||
assertNoSearchContexts();
|
||||
}
|
||||
|
||||
private void assertNestedDocuments(ResultSet rs, int i) throws SQLException {
|
||||
|
@ -196,7 +188,7 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
assertEquals(i, rs.getInt(1));
|
||||
assertEquals(j, rs.getInt(2));
|
||||
// don't check the very last row in the result set
|
||||
assertTrue("No more entries left after row " + rs.getRow(), (i+j == 23 || rs.next()));
|
||||
assertTrue("No more entries left after row " + rs.getRow(), (i + j == 23 || rs.next()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,15 +197,14 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
* Checks that the paging properly consumes the necessary amount of aggregations and the
|
||||
* page size affects the result not the intermediate query.
|
||||
*/
|
||||
public void testPivotPaging() throws Exception {
|
||||
public void testPivotPaging() throws IOException, SQLException {
|
||||
addPivotData();
|
||||
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
|
||||
String query = "SELECT * FROM "
|
||||
+ "(SELECT item, amount, location FROM test_pivot)"
|
||||
+ " PIVOT (AVG(amount) FOR location IN ( 'AF', 'AS', 'EU', 'NA', 'SA', 'AQ', 'AU') )";
|
||||
+ "(SELECT item, amount, location FROM test_pivot)"
|
||||
+ " PIVOT (AVG(amount) FOR location IN ( 'AF', 'AS', 'EU', 'NA', 'SA', 'AQ', 'AU') )";
|
||||
// set size smaller than an agg page
|
||||
s.setFetchSize(3);
|
||||
try (ResultSet rs = s.executeQuery(query)) {
|
||||
|
@ -239,20 +230,17 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
assertFalse(rs.next());
|
||||
}
|
||||
}
|
||||
assertNoSearchContexts();
|
||||
}
|
||||
|
||||
|
||||
public void testPivotPagingWithLimit() throws Exception {
|
||||
public void testPivotPagingWithLimit() throws IOException, SQLException {
|
||||
addPivotData();
|
||||
|
||||
try (Connection c = esJdbc();
|
||||
Statement s = c.createStatement()) {
|
||||
try (Connection c = esJdbc(); Statement s = c.createStatement()) {
|
||||
|
||||
// run a query with a limit that is not a multiple of the fetch size
|
||||
String query = "SELECT * FROM "
|
||||
+ "(SELECT item, amount, location FROM test_pivot)"
|
||||
+ " PIVOT (AVG(amount) FOR location IN ( 'EU', 'NA' ) ) LIMIT 5";
|
||||
+ "(SELECT item, amount, location FROM test_pivot)"
|
||||
+ " PIVOT (AVG(amount) FOR location IN ( 'EU', 'NA' ) ) LIMIT 5";
|
||||
// set size smaller than an agg page
|
||||
s.setFetchSize(20);
|
||||
try (ResultSet rs = s.executeQuery(query)) {
|
||||
|
@ -268,20 +256,24 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
|
|||
assertFalse("LIMIT should be reached", rs.next());
|
||||
}
|
||||
}
|
||||
assertNoSearchContexts();
|
||||
}
|
||||
|
||||
private void addPivotData() throws Exception {
|
||||
private void addPivotData() throws IOException {
|
||||
Request request = new Request("PUT", "/test_pivot/_bulk");
|
||||
request.addParameter("refresh", "true");
|
||||
StringBuilder bulk = new StringBuilder();
|
||||
String[] continent = new String[] { "AF", "AS", "EU", "NA", "SA", "AQ", "AU" };
|
||||
for (int i = 0; i <= 100; i++) {
|
||||
bulk.append("{\"index\":{}}\n");
|
||||
bulk.append("{\"item\":").append(i % 10)
|
||||
.append(", \"entry\":").append(i)
|
||||
.append(", \"amount\" : ").append(randomInt(999))
|
||||
.append(", \"location\" : \"").append(continent[i % (continent.length)]).append("\"")
|
||||
bulk.append("{\"item\":")
|
||||
.append(i % 10)
|
||||
.append(", \"entry\":")
|
||||
.append(i)
|
||||
.append(", \"amount\" : ")
|
||||
.append(randomInt(999))
|
||||
.append(", \"location\" : \"")
|
||||
.append(continent[i % (continent.length)])
|
||||
.append("\"")
|
||||
.append("}\n");
|
||||
}
|
||||
request.setJsonEntity(bulk.toString());
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.qa.jdbc;
|
|||
|
||||
import org.elasticsearch.client.Request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
|
@ -15,16 +16,15 @@ import static org.hamcrest.Matchers.startsWith;
|
|||
/**
|
||||
* Tests for exceptions and their messages.
|
||||
*/
|
||||
public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elasticsearch.xpack.sql.qa.ErrorsTestCase {
|
||||
@Override
|
||||
public void testSelectInvalidSql() throws Exception {
|
||||
public abstract class JdbcErrorsTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
public void testSelectInvalidSql() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT * FRO").executeQuery());
|
||||
assertEquals("Found 1 problem\nline 1:8: Cannot determine columns for [*]", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectFromMissingIndex() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT * FROM test").executeQuery());
|
||||
|
@ -32,16 +32,14 @@ public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elast
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectColumnFromMissingIndex() throws Exception {
|
||||
public void testSelectColumnFromMissingIndex() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT abc FROM test").executeQuery());
|
||||
assertEquals("Found 1 problem\nline 1:17: Unknown index [test]", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectFromEmptyIndex() throws Exception {
|
||||
public void testSelectFromEmptyIndex() throws IOException, SQLException {
|
||||
// Create an index without any types
|
||||
Request request = new Request("PUT", "/test");
|
||||
request.setJsonEntity("{}");
|
||||
|
@ -53,8 +51,7 @@ public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elast
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectColumnFromEmptyIndex() throws Exception {
|
||||
public void testSelectColumnFromEmptyIndex() throws IOException, SQLException {
|
||||
Request request = new Request("PUT", "/test");
|
||||
request.setJsonEntity("{}");
|
||||
client().performRequest(request);
|
||||
|
@ -65,8 +62,7 @@ public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elast
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectMissingField() throws Exception {
|
||||
public void testSelectMissingField() throws IOException, SQLException {
|
||||
index("test", body -> body.field("test", "test"));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT missing FROM test").executeQuery());
|
||||
|
@ -74,8 +70,7 @@ public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elast
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectMissingFunction() throws Exception {
|
||||
public void testSelectMissingFunction() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT missing(foo) FROM test").executeQuery());
|
||||
|
@ -83,64 +78,65 @@ public class ErrorsTestCase extends JdbcIntegrationTestCase implements org.elast
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectProjectScoreInAggContext() throws Exception {
|
||||
public void testSelectProjectScoreInAggContext() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT foo, SCORE(), COUNT(*) FROM test GROUP BY foo").executeQuery());
|
||||
SQLException e = expectThrows(
|
||||
SQLException.class,
|
||||
() -> c.prepareStatement("SELECT foo, SCORE(), COUNT(*) FROM test GROUP BY foo").executeQuery()
|
||||
);
|
||||
assertEquals("Found 1 problem\nline 1:13: Cannot use non-grouped column [SCORE()], expected [foo]", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectOrderByScoreInAggContext() throws Exception {
|
||||
public void testSelectOrderByScoreInAggContext() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT foo, COUNT(*) FROM test GROUP BY foo ORDER BY SCORE()").executeQuery());
|
||||
SQLException e = expectThrows(
|
||||
SQLException.class,
|
||||
() -> c.prepareStatement("SELECT foo, COUNT(*) FROM test GROUP BY foo ORDER BY SCORE()").executeQuery()
|
||||
);
|
||||
assertEquals(
|
||||
"Found 1 problem\nline 1:54: Cannot order by non-grouped column [SCORE()], expected [foo] or an aggregate function",
|
||||
e.getMessage());
|
||||
"Found 1 problem\nline 1:54: Cannot order by non-grouped column [SCORE()], expected [foo] or an aggregate function",
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectGroupByScore() throws Exception {
|
||||
public void testSelectGroupByScore() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT COUNT(*) FROM test GROUP BY SCORE()").executeQuery());
|
||||
SQLException e = expectThrows(
|
||||
SQLException.class,
|
||||
() -> c.prepareStatement("SELECT COUNT(*) FROM test GROUP BY SCORE()").executeQuery()
|
||||
);
|
||||
assertEquals("Found 1 problem\nline 1:36: Cannot use [SCORE()] for grouping", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectScoreSubField() throws Exception {
|
||||
public void testSelectScoreSubField() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT SCORE().bar FROM test").executeQuery());
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT SCORE().bar FROM test").executeQuery());
|
||||
assertThat(e.getMessage(), startsWith("line 1:15: extraneous input '.' expecting {<EOF>, ','"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testSelectScoreInScalar() throws Exception {
|
||||
public void testSelectScoreInScalar() throws IOException, SQLException {
|
||||
index("test", body -> body.field("foo", 1));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT SIN(SCORE()) FROM test").executeQuery());
|
||||
SQLException e = expectThrows(SQLException.class, () -> c.prepareStatement("SELECT SIN(SCORE()) FROM test").executeQuery());
|
||||
assertThat(e.getMessage(), startsWith("Found 1 problem\nline 1:12: [SCORE()] cannot be an argument to a function"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testHardLimitForSortOnAggregate() throws Exception {
|
||||
public void testHardLimitForSortOnAggregate() throws IOException, SQLException {
|
||||
index("test", body -> body.field("a", 1).field("b", 2));
|
||||
try (Connection c = esJdbc()) {
|
||||
SQLException e = expectThrows(SQLException.class, () ->
|
||||
c.prepareStatement("SELECT max(a) max FROM test GROUP BY b ORDER BY max LIMIT 12000").executeQuery());
|
||||
SQLException e = expectThrows(
|
||||
SQLException.class,
|
||||
() -> c.prepareStatement("SELECT max(a) max FROM test GROUP BY b ORDER BY max LIMIT 12000").executeQuery()
|
||||
);
|
||||
assertEquals("The maximum LIMIT for aggregate sorting is [10000], received [12000]", e.getMessage());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.common.CheckedConsumer;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.xpack.sql.jdbc.EsDataSource;
|
||||
import org.junit.After;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
||||
|
||||
public static final String JDBC_ES_URL_PREFIX = "jdbc:es://";
|
||||
|
||||
@After
|
||||
public void checkSearchContent() throws IOException {
|
||||
// Some context might linger due to fire and forget nature of scroll cleanup
|
||||
assertNoSearchContexts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an address for Elasticsearch suitable for the JDBC driver from the system properties.
|
||||
*/
|
||||
public static String elasticsearchAddress() {
|
||||
String cluster = System.getProperty("tests.rest.cluster");
|
||||
// JDBC only supports a single node at a time so we just give it one.
|
||||
return cluster.split(",")[0];
|
||||
/* This doesn't include "jdbc:es://" because we want the example in
|
||||
* esJdbc to be obvious and because we want to use getProtocol to add
|
||||
* https if we are running against https. */
|
||||
}
|
||||
|
||||
public Connection esJdbc() throws SQLException {
|
||||
return esJdbc(connectionProperties());
|
||||
}
|
||||
|
||||
public Connection esJdbc(Properties props) throws SQLException {
|
||||
return createConnection(props);
|
||||
}
|
||||
|
||||
protected Connection createConnection(Properties connectionProperties) throws SQLException {
|
||||
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||
String address = JDBC_ES_URL_PREFIX + elasticsearchAddress;
|
||||
Connection connection;
|
||||
if (randomBoolean()) {
|
||||
connection = DriverManager.getConnection(address, connectionProperties);
|
||||
} else {
|
||||
EsDataSource dataSource = new EsDataSource();
|
||||
dataSource.setUrl(address);
|
||||
dataSource.setProperties(connectionProperties);
|
||||
connection = dataSource.getConnection();
|
||||
}
|
||||
|
||||
assertNotNull("The timezone should be specified", connectionProperties.getProperty("timezone"));
|
||||
return connection;
|
||||
}
|
||||
|
||||
//
|
||||
// methods below are used inside the documentation only
|
||||
//
|
||||
protected Connection useDriverManager() throws SQLException {
|
||||
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||
// tag::connect-dm
|
||||
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||
Properties connectionProperties = connectionProperties(); // <2>
|
||||
Connection connection =
|
||||
DriverManager.getConnection(address, connectionProperties);
|
||||
// end::connect-dm
|
||||
assertNotNull("The timezone should be specified", connectionProperties.getProperty("timezone"));
|
||||
return connection;
|
||||
}
|
||||
|
||||
protected Connection useDataSource() throws SQLException {
|
||||
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||
// tag::connect-ds
|
||||
EsDataSource dataSource = new EsDataSource();
|
||||
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||
dataSource.setUrl(address);
|
||||
Properties connectionProperties = connectionProperties(); // <2>
|
||||
dataSource.setProperties(connectionProperties);
|
||||
Connection connection = dataSource.getConnection();
|
||||
// end::connect-ds
|
||||
assertNotNull("The timezone should be specified", connectionProperties.getProperty("timezone"));
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static void index(String index, CheckedConsumer<XContentBuilder, IOException> body) throws IOException {
|
||||
index(index, "1", body);
|
||||
}
|
||||
|
||||
public static void index(String index, String documentId, CheckedConsumer<XContentBuilder, IOException> body) throws IOException {
|
||||
Request request = new Request("PUT", "/" + index + "/_doc/" + documentId);
|
||||
request.addParameter("refresh", "true");
|
||||
XContentBuilder builder = JsonXContent.contentBuilder().startObject();
|
||||
body.accept(builder);
|
||||
builder.endObject();
|
||||
request.setJsonEntity(Strings.toString(builder));
|
||||
client().performRequest(request);
|
||||
}
|
||||
|
||||
public static void delete(String index, String documentId) throws IOException {
|
||||
Request request = new Request("DELETE", "/" + index + "/_doc/" + documentId);
|
||||
request.addParameter("refresh", "true");
|
||||
client().performRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* The properties used to build the connection.
|
||||
*/
|
||||
protected Properties connectionProperties() {
|
||||
Properties connectionProperties = new Properties();
|
||||
connectionProperties.put(JdbcTestUtils.JDBC_TIMEZONE, randomKnownTimeZone());
|
||||
// in the tests, don't be lenient towards multi values
|
||||
connectionProperties.put("field.multi.value.leniency", "false");
|
||||
return connectionProperties;
|
||||
}
|
||||
|
||||
public static String randomKnownTimeZone() {
|
||||
// We use system default timezone for the connection that is selected randomly by TestRuleSetupAndRestoreClassEnv
|
||||
// from all available JDK timezones. While Joda and JDK are generally in sync, some timezones might not be known
|
||||
// to the current version of Joda and in this case the test might fail. To avoid that, we specify a timezone
|
||||
// known for both Joda and JDK
|
||||
Set<String> timeZones = new HashSet<>(JODA_TIMEZONE_IDS);
|
||||
timeZones.retainAll(JAVA_TIMEZONE_IDS);
|
||||
List<String> ids = new ArrayList<>(timeZones);
|
||||
Collections.sort(ids);
|
||||
return randomFrom(ids);
|
||||
}
|
||||
|
||||
private static Map<String, Object> searchStats() throws IOException {
|
||||
Response response = client().performRequest(new Request("GET", "/_stats/search"));
|
||||
try (InputStream content = response.getEntity().getContent()) {
|
||||
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int getOpenContexts(Map<String, Object> stats, String index) {
|
||||
stats = (Map<String, Object>) stats.get("indices");
|
||||
stats = (Map<String, Object>) stats.get(index);
|
||||
stats = (Map<String, Object>) stats.get("total");
|
||||
stats = (Map<String, Object>) stats.get("search");
|
||||
return (Integer) stats.get("open_contexts");
|
||||
}
|
||||
|
||||
static void assertNoSearchContexts() throws IOException {
|
||||
Map<String, Object> stats = searchStats();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> indicesStats = (Map<String, Object>) stats.get("indices");
|
||||
for (String index : indicesStats.keySet()) {
|
||||
if (index.startsWith(".") == false) { // We are not interested in internal indices
|
||||
assertEquals(index + " should have no search contexts", 0, getOpenContexts(stats, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import java.sql.SQLException;
|
|||
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class JdbcNoSqlTestCase extends JdbcIntegrationTestCase {
|
||||
public abstract class JdbcNoSqlTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
public void testJdbcExceptionMessage() throws SQLException {
|
||||
try (Connection c = esJdbc()) {
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||
|
||||
import org.elasticsearch.xpack.sql.proto.StringUtils;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Time;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Calendar;
|
||||
|
||||
final class JdbcTestUtils {
|
||||
|
||||
private JdbcTestUtils() {}
|
||||
|
||||
static final ZoneId UTC = ZoneId.of("Z");
|
||||
static final String JDBC_TIMEZONE = "timezone";
|
||||
static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
|
||||
|
||||
static String of(long millis, String zoneId) {
|
||||
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId)));
|
||||
}
|
||||
|
||||
static Date asDate(long millis, ZoneId zoneId) {
|
||||
return new java.sql.Date(
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), zoneId).toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli()
|
||||
);
|
||||
}
|
||||
|
||||
static Time asTime(long millis, ZoneId zoneId) {
|
||||
return new Time(
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), zoneId)
|
||||
.toLocalTime()
|
||||
.atDate(JdbcTestUtils.EPOCH)
|
||||
.atZone(zoneId)
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
);
|
||||
}
|
||||
|
||||
static long convertFromCalendarToUTC(long value, Calendar cal) {
|
||||
if (cal == null) {
|
||||
return value;
|
||||
}
|
||||
Calendar c = (Calendar) cal.clone();
|
||||
c.setTimeInMillis(value);
|
||||
|
||||
ZonedDateTime convertedDateTime = ZonedDateTime.ofInstant(c.toInstant(), c.getTimeZone().toZoneId())
|
||||
.withZoneSameLocal(ZoneOffset.UTC);
|
||||
|
||||
return convertedDateTime.toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
|
@ -27,14 +27,10 @@ import java.util.Calendar;
|
|||
import java.util.Locale;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.UTC;
|
||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.asDate;
|
||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.asTime;
|
||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.convertFromCalendarToUTC;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
||||
public abstract class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
public void testSupportedTypes() throws SQLException {
|
||||
String stringVal = randomAlphaOfLength(randomIntBetween(0, 1000));
|
||||
|
@ -49,13 +45,19 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
long millis = randomNonNegativeLong();
|
||||
Calendar calendarVal = Calendar.getInstance(randomTimeZone(), Locale.ROOT);
|
||||
Timestamp timestampVal = new Timestamp(millis);
|
||||
Timestamp timestampValWithCal = new Timestamp(convertFromCalendarToUTC(timestampVal.getTime(), calendarVal));
|
||||
Date dateVal = asDate(millis, UTC);
|
||||
Date dateValWithCal = asDate(convertFromCalendarToUTC(dateVal.getTime(), calendarVal), UTC);
|
||||
Time timeVal = asTime(millis, UTC);
|
||||
Time timeValWithCal = asTime(convertFromCalendarToUTC(timeVal.getTime(), calendarVal), UTC);
|
||||
Timestamp timestampValWithCal = new Timestamp(JdbcTestUtils.convertFromCalendarToUTC(timestampVal.getTime(), calendarVal));
|
||||
Date dateVal = JdbcTestUtils.asDate(millis, JdbcTestUtils.UTC);
|
||||
Date dateValWithCal = JdbcTestUtils.asDate(
|
||||
JdbcTestUtils.convertFromCalendarToUTC(dateVal.getTime(), calendarVal),
|
||||
JdbcTestUtils.UTC
|
||||
);
|
||||
Time timeVal = JdbcTestUtils.asTime(millis, JdbcTestUtils.UTC);
|
||||
Time timeValWithCal = JdbcTestUtils.asTime(
|
||||
JdbcTestUtils.convertFromCalendarToUTC(timeVal.getTime(), calendarVal),
|
||||
JdbcTestUtils.UTC
|
||||
);
|
||||
java.util.Date utilDateVal = new java.util.Date(millis);
|
||||
LocalDateTime localDateTimeVal = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), UTC);
|
||||
LocalDateTime localDateTimeVal = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), JdbcTestUtils.UTC);
|
||||
|
||||
try (Connection connection = esJdbc()) {
|
||||
StringJoiner sql = new StringJoiner(",", "SELECT ", "");
|
||||
|
@ -140,10 +142,13 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
setupIndexForDateTimeTests(randomMillis);
|
||||
|
||||
try (Connection connection = esJdbc()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT id, birth_date FROM emps WHERE birth_date::date = ? " +
|
||||
"ORDER BY id")) {
|
||||
try (
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT id, birth_date FROM emps WHERE birth_date::date = ? " + "ORDER BY id"
|
||||
)
|
||||
) {
|
||||
|
||||
statement.setDate(1, new Date(asDate(randomMillis, UTC).getTime()));
|
||||
statement.setDate(1, new Date(JdbcTestUtils.asDate(randomMillis, JdbcTestUtils.UTC).getTime()));
|
||||
try (ResultSet results = statement.executeQuery()) {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
assertTrue(results.next());
|
||||
|
@ -162,7 +167,7 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
|
||||
try (Connection connection = esJdbc()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT id, birth_date FROM emps WHERE birth_date::time = ?")) {
|
||||
Time time = JdbcTestUtils.asTime(randomMillis, UTC);
|
||||
Time time = JdbcTestUtils.asTime(randomMillis, JdbcTestUtils.UTC);
|
||||
statement.setObject(1, time);
|
||||
try (ResultSet results = statement.executeQuery()) {
|
||||
assertTrue(results.next());
|
||||
|
@ -184,7 +189,7 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testUnsupportedParameterUse() throws Exception {
|
||||
public void testUnsupportedParameterUse() throws IOException, SQLException {
|
||||
index("library", builder -> {
|
||||
builder.field("name", "Don Quixote");
|
||||
builder.field("page_count", 1072);
|
||||
|
@ -202,7 +207,7 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testTooMayParameters() throws Exception {
|
||||
public void testTooMayParameters() throws IOException, SQLException {
|
||||
index("library", builder -> {
|
||||
builder.field("name", "Don Quixote");
|
||||
builder.field("page_count", 1072);
|
||||
|
@ -221,10 +226,9 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testStringEscaping() throws Exception {
|
||||
public void testStringEscaping() throws SQLException {
|
||||
try (Connection connection = esJdbc()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT ?, ?, ?, ?")) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT ?, ?, ?, ?")) {
|
||||
statement.setString(1, "foo --");
|
||||
statement.setString(2, "/* foo */");
|
||||
statement.setString(3, "\"foo");
|
||||
|
@ -246,10 +250,9 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testCommentsHandling() throws Exception {
|
||||
public void testCommentsHandling() throws SQLException {
|
||||
try (Connection connection = esJdbc()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT ?, /* ?, */ ? -- ?")) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT ?, /* ?, */ ? -- ?")) {
|
||||
assertEquals(2, statement.getParameterMetaData().getParameterCount());
|
||||
statement.setString(1, "foo");
|
||||
statement.setString(2, "bar");
|
||||
|
@ -265,7 +268,7 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testSingleParameterMultipleTypes() throws Exception {
|
||||
public void testSingleParameterMultipleTypes() throws SQLException {
|
||||
String stringVal = randomAlphaOfLength(randomIntBetween(0, 1000));
|
||||
int intVal = randomInt();
|
||||
long longVal = randomLong();
|
||||
|
@ -300,7 +303,7 @@ public class PreparedStatementTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private Tuple<Integer, Object> execute(PreparedStatement statement) throws Exception {
|
||||
private Tuple<Integer, Object> execute(PreparedStatement statement) throws SQLException {
|
||||
try (ResultSet results = statement.executeQuery()) {
|
||||
ResultSetMetaData resultSetMetaData = results.getMetaData();
|
||||
assertTrue(results.next());
|
|
@ -8,32 +8,41 @@ package org.elasticsearch.xpack.sql.qa.jdbc;
|
|||
|
||||
import org.elasticsearch.common.CheckedConsumer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ResultSetMetaDataTestCase extends JdbcIntegrationTestCase {
|
||||
public abstract class ResultSetMetaDataTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
private final String[] fieldsNames = new String[] {"test_byte", "test_integer", "test_long", "test_short",
|
||||
"test_double", "test_float", "test_keyword", "test_boolean", "test_date"};
|
||||
private final String[] fieldsNames = new String[] {
|
||||
"test_byte",
|
||||
"test_integer",
|
||||
"test_long",
|
||||
"test_short",
|
||||
"test_double",
|
||||
"test_float",
|
||||
"test_keyword",
|
||||
"test_boolean",
|
||||
"test_date" };
|
||||
|
||||
public void testValidGetObjectCalls() throws Exception {
|
||||
public void testValidGetObjectCalls() throws IOException, SQLException {
|
||||
ResultSetTestCase.createIndex("test");
|
||||
ResultSetTestCase.updateMapping("test", builder -> {
|
||||
for(String field : fieldsNames) {
|
||||
for (String field : fieldsNames) {
|
||||
builder.startObject(field).field("type", field.substring(5)).endObject();
|
||||
}
|
||||
});
|
||||
|
||||
String q = "SELECT test_byte, test_integer, test_long, test_short, test_double, test_float, test_keyword, "
|
||||
+ "test_boolean, test_date FROM test";
|
||||
doWithQuery(q, (r) -> assertColumnNamesAndLabels(r.getMetaData(), fieldsNames));
|
||||
+ "test_boolean, test_date FROM test";
|
||||
doWithQuery(q, r -> assertColumnNamesAndLabels(r.getMetaData(), fieldsNames));
|
||||
|
||||
q = "SELECT test_byte AS b, test_integer AS i, test_long AS l, test_short AS s, test_double AS d, test_float AS f, "
|
||||
+ "test_keyword AS k, test_boolean AS bool, test_date AS dt FROM test";
|
||||
doWithQuery(q, (r) -> assertColumnNamesAndLabels(r.getMetaData(), new String[] {"b", "i", "l", "s", "d", "f", "k", "bool", "dt"}));
|
||||
+ "test_keyword AS k, test_boolean AS bool, test_date AS dt FROM test";
|
||||
doWithQuery(q, r -> assertColumnNamesAndLabels(r.getMetaData(), new String[] { "b", "i", "l", "s", "d", "f", "k", "bool", "dt" }));
|
||||
}
|
||||
|
||||
private void doWithQuery(String query, CheckedConsumer<ResultSet, SQLException> consumer) throws SQLException {
|
||||
|
@ -47,10 +56,10 @@ public class ResultSetMetaDataTestCase extends JdbcIntegrationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertColumnNamesAndLabels(ResultSetMetaData metadata, String[] names) throws SQLException {
|
||||
for(int i = 0; i < fieldsNames.length; i++) {
|
||||
assertEquals(names[i], metadata.getColumnName(i + 1));
|
||||
assertEquals(names[i], metadata.getColumnLabel(i + 1));
|
||||
private void assertColumnNamesAndLabels(ResultSetMetaData metaData, String[] names) throws SQLException {
|
||||
for (int i = 0; i < fieldsNames.length; i++) {
|
||||
assertEquals(names[i], metaData.getColumnName(i + 1));
|
||||
assertEquals(names[i], metaData.getColumnLabel(i + 1));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
@ -12,8 +13,9 @@ import java.sql.Statement;
|
|||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class SimpleExampleTestCase extends JdbcIntegrationTestCase {
|
||||
public void testSimpleExample() throws Exception {
|
||||
public abstract class SimpleExampleTestCase extends JdbcIntegrationTestCase {
|
||||
|
||||
public void testSimpleExample() throws SQLException, IOException {
|
||||
index("library", builder -> {
|
||||
builder.field("name", "Don Quixote");
|
||||
builder.field("page_count", 1072);
|
||||
|
@ -22,10 +24,10 @@ public class SimpleExampleTestCase extends JdbcIntegrationTestCase {
|
|||
// tag::simple_example
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet results = statement.executeQuery(
|
||||
" SELECT name, page_count"
|
||||
+ " FROM library"
|
||||
" SELECT name, page_count"
|
||||
+ " FROM library"
|
||||
+ " ORDER BY page_count DESC"
|
||||
+ " LIMIT 1")) {
|
||||
+ " LIMIT 1")) {
|
||||
assertTrue(results.next());
|
||||
assertEquals("Don Quixote", results.getString(1));
|
||||
assertEquals(1072, results.getInt(2));
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.qa.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ErrorsTestCase;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class JdbcErrorsIT extends ErrorsTestCase {
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
properties.putAll(JdbcSecurityIT.adminProperties());
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
description = 'Integration tests for SQL'
|
||||
apply plugin: 'elasticsearch.build'
|
||||
|
||||
// the main files are actually test files, so use the appropriate forbidden api sigs
|
||||
tasks.named('forbiddenApisMain').configure {
|
||||
replaceSignatureFiles 'es-all-signatures', 'es-test-signatures'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":test:framework")
|
||||
|
||||
// JDBC testing dependencies
|
||||
compile project(path: xpackModule('sql:jdbc'))
|
||||
|
||||
compile "net.sourceforge.csvjdbc:csvjdbc:${csvjdbcVersion}"
|
||||
|
||||
// CLI testing dependencies
|
||||
compile project(path: xpackModule('sql:sql-cli'))
|
||||
|
||||
// H2GIS testing dependencies
|
||||
compile("org.orbisgis:h2gis:${h2gisVersion}") {
|
||||
exclude group: "org.locationtech.jts"
|
||||
}
|
||||
|
||||
// select just the parts of JLine that are needed
|
||||
compile("org.jline:jline-terminal-jna:${jlineVersion}") {
|
||||
exclude group: "net.java.dev.jna"
|
||||
}
|
||||
compile "org.jline:jline-terminal:${jlineVersion}"
|
||||
compile "org.jline:jline-reader:${jlineVersion}"
|
||||
compile "org.jline:jline-style:${jlineVersion}"
|
||||
|
||||
testRuntime "org.elasticsearch:jna:${versions.jna}"
|
||||
}
|
||||
|
||||
/* disable unit tests because these are all integration tests used
|
||||
* other qa projects. */
|
||||
test.enabled = false
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
dependenciesInfo.enabled = false
|
||||
|
||||
// just a test fixture: we aren't using this jars in releases and H2GIS requires disabling a lot of checks
|
||||
thirdPartyAudit.enabled = false
|
||||
|
||||
subprojects {
|
||||
if (subprojects.isEmpty()) {
|
||||
// leaf project
|
||||
apply plugin: 'elasticsearch.standalone-rest-test'
|
||||
} else {
|
||||
apply plugin: 'elasticsearch.build'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
configurations.testRuntimeClasspath {
|
||||
resolutionStrategy.force "org.slf4j:slf4j-api:1.7.25"
|
||||
}
|
||||
configurations.testRuntime {
|
||||
// This is also required to make resolveAllDependencies work
|
||||
resolutionStrategy.force "org.slf4j:slf4j-api:1.7.25"
|
||||
}
|
||||
|
||||
/* Since we're a standalone rest test we actually get transitive
|
||||
* dependencies but we don't really want them because they cause
|
||||
* all kinds of trouble with the jar hell checks. So we suppress
|
||||
* them explicitly for non-es projects. */
|
||||
testCompile(xpackProject('plugin:sql:qa:server')) {
|
||||
transitive = false
|
||||
}
|
||||
testCompile project(":test:framework")
|
||||
|
||||
// JDBC testing dependencies
|
||||
testRuntime "net.sourceforge.csvjdbc:csvjdbc:${csvjdbcVersion}"
|
||||
testRuntime "com.h2database:h2:${h2Version}"
|
||||
|
||||
// H2GIS testing dependencies
|
||||
testRuntime("org.orbisgis:h2gis:${h2gisVersion}") {
|
||||
exclude group: "org.locationtech.jts"
|
||||
exclude group: "com.fasterxml.jackson.core"
|
||||
}
|
||||
|
||||
testRuntime project(path: xpackModule('sql:jdbc'))
|
||||
testRuntime xpackProject('plugin:sql:sql-client')
|
||||
|
||||
// CLI testing dependencies
|
||||
testRuntime project(path: xpackModule('sql:sql-cli'))
|
||||
testRuntime(xpackProject('plugin:sql:sql-action')) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
testRuntime("org.jline:jline-terminal-jna:${jlineVersion}") {
|
||||
exclude group: "net.java.dev.jna"
|
||||
}
|
||||
testRuntime "org.jline:jline-terminal:${jlineVersion}"
|
||||
testRuntime "org.jline:jline-reader:${jlineVersion}"
|
||||
testRuntime "org.jline:jline-style:${jlineVersion}"
|
||||
|
||||
testRuntime "org.elasticsearch:jna:${versions.jna}"
|
||||
|
||||
// spatial dependency
|
||||
testRuntime project(path: xpackModule('spatial'))
|
||||
}
|
||||
|
||||
if (project.name != 'security') {
|
||||
// The security project just configures its subprojects
|
||||
apply plugin: 'elasticsearch.testclusters'
|
||||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
testClusters.integTest {
|
||||
testDistribution = 'DEFAULT'
|
||||
setting 'xpack.ml.enabled', 'false'
|
||||
setting 'xpack.watcher.enabled', 'false'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.multi_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.SelectTestCase;
|
||||
|
||||
public class CliSelectIT extends SelectTestCase {
|
||||
}
|
||||
public class CliSelectIT extends SelectTestCase {}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.multi_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.ShowTestCase;
|
||||
|
||||
public class CliShowIT extends ShowTestCase {
|
||||
}
|
||||
public class CliShowIT extends ShowTestCase {}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.multi_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.DatabaseMetaDataTestCase;
|
||||
|
||||
public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {
|
||||
}
|
||||
public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.multi_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ShowTablesTestCase;
|
||||
|
||||
public class JdbcShowTablesIT extends ShowTablesTestCase {
|
||||
}
|
||||
public class JdbcShowTablesIT extends ShowTablesTestCase {}
|
|
@ -11,5 +11,4 @@ import org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase;
|
|||
* Integration test for the rest sql action. The one that speaks json directly to a
|
||||
* user rather than to the JDBC driver or CLI.
|
||||
*/
|
||||
public class RestSqlIT extends RestSqlTestCase {
|
||||
}
|
||||
public class RestSqlIT extends RestSqlTestCase {}
|
|
@ -66,7 +66,8 @@ public class RestSqlMultinodeIT extends ESRestTestCase {
|
|||
assertNotNull("Didn't find first host among published addresses", firstHostName);
|
||||
|
||||
XContentBuilder index = JsonXContent.contentBuilder().prettyPrint().startObject();
|
||||
index.startObject("settings"); {
|
||||
index.startObject("settings");
|
||||
{
|
||||
index.field("routing.allocation.exclude._name", firstHostName);
|
||||
}
|
||||
index.endObject();
|
||||
|
@ -77,7 +78,7 @@ public class RestSqlMultinodeIT extends ESRestTestCase {
|
|||
int documents = between(10, 100);
|
||||
createTestData(documents);
|
||||
|
||||
try (RestClient firstNodeClient = buildClient(restClientSettings(), new HttpHost[] {firstHost})) {
|
||||
try (RestClient firstNodeClient = buildClient(restClientSettings(), new HttpHost[] { firstHost })) {
|
||||
assertCount(firstNodeClient, documents);
|
||||
}
|
||||
}
|
|
@ -8,5 +8,4 @@ package org.elasticsearch.xpack.sql.qa.multi_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.SqlProtocolTestCase;
|
||||
|
||||
public class SqlProtocolIT extends SqlProtocolTestCase {
|
||||
}
|
||||
public class SqlProtocolIT extends SqlProtocolTestCase {}
|
|
@ -4,8 +4,6 @@ dependencies {
|
|||
|
||||
Project mainProject = project
|
||||
|
||||
group = "${group}.x-pack.qa.sql.security"
|
||||
|
||||
configurations.create('testArtifacts')
|
||||
|
||||
TaskProvider testJar = tasks.register("testJar", Jar) {
|
|
@ -0,0 +1,91 @@
|
|||
# tag::rest
|
||||
rest_minimal:
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
- names: bort
|
||||
privileges: [read, "indices:admin/get"]
|
||||
# end::rest
|
||||
|
||||
# tag::cli_drivers
|
||||
cli_or_drivers_minimal:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
- names: bort
|
||||
privileges: [read, "indices:admin/get"]
|
||||
# end::cli_drivers
|
||||
|
||||
read_nothing:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
|
||||
read_something_else:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: something_that_isnt_test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
|
||||
read_test_a:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
field_security:
|
||||
grant: [a]
|
||||
|
||||
read_test_a_and_b:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
field_security:
|
||||
grant: ["*"]
|
||||
except: [c]
|
||||
|
||||
read_test_without_c_3:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
query: |
|
||||
{
|
||||
"bool": {
|
||||
"must_not": [
|
||||
{
|
||||
"match": {
|
||||
"c": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
read_bort:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: bort
|
||||
privileges: [read, "indices:admin/get"]
|
||||
|
||||
no_monitor_main:
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [read, "indices:admin/get"]
|
||||
- names: bort
|
||||
privileges: [read, "indices:admin/get"]
|
||||
|
||||
no_get_index:
|
||||
cluster:
|
||||
- "cluster:monitor/main"
|
||||
indices:
|
||||
- names: test
|
||||
privileges: [monitor]
|
||||
- names: bort
|
||||
privileges: [monitor]
|
|
@ -84,13 +84,15 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
public void expectScrollMatchesAdmin(String adminSql, String user, String userSql) throws Exception {
|
||||
expectMatchesAdmin(adminSql, user, userSql, cli -> {
|
||||
assertEquals("[?1l>[?1000l[?2004lfetch size set to [90m1[0m", cli.command("fetch size = 1"));
|
||||
assertEquals("[?1l>[?1000l[?2004lfetch separator set to \"[90m -- fetch sep -- [0m\"",
|
||||
cli.command("fetch separator = \" -- fetch sep -- \""));
|
||||
assertEquals(
|
||||
"[?1l>[?1000l[?2004lfetch separator set to \"[90m -- fetch sep -- [0m\"",
|
||||
cli.command("fetch separator = \" -- fetch sep -- \"")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public void expectMatchesAdmin(String adminSql, String user, String userSql,
|
||||
CheckedConsumer<EmbeddedCli, Exception> customizer) throws Exception {
|
||||
public void expectMatchesAdmin(String adminSql, String user, String userSql, CheckedConsumer<EmbeddedCli, Exception> customizer)
|
||||
throws Exception {
|
||||
List<String> adminResult = new ArrayList<>();
|
||||
try (EmbeddedCli cli = new EmbeddedCli(elasticsearchAddress(), true, adminSecurityConfig())) {
|
||||
customizer.accept(cli);
|
|
@ -82,10 +82,12 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
properties.put("ssl.truststore.pass", "keypass");
|
||||
}
|
||||
|
||||
static void expectActionMatchesAdmin(CheckedFunction<Connection, ResultSet, SQLException> adminAction,
|
||||
String user, CheckedFunction<Connection, ResultSet, SQLException> userAction) throws Exception {
|
||||
try (Connection adminConnection = es(adminProperties());
|
||||
Connection userConnection = es(userProperties(user))) {
|
||||
static void expectActionMatchesAdmin(
|
||||
CheckedFunction<Connection, ResultSet, SQLException> adminAction,
|
||||
String user,
|
||||
CheckedFunction<Connection, ResultSet, SQLException> userAction
|
||||
) throws Exception {
|
||||
try (Connection adminConnection = es(adminProperties()); Connection userConnection = es(userProperties(user))) {
|
||||
assertResultSets(adminAction.apply(adminConnection), userAction.apply(userConnection));
|
||||
}
|
||||
}
|
||||
|
@ -106,8 +108,8 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
assertThat(e.getMessage(), containsString(errorMessage));
|
||||
}
|
||||
|
||||
static void expectActionThrowsUnknownColumn(String user,
|
||||
CheckedConsumer<Connection, SQLException> action, String column) throws Exception {
|
||||
static void expectActionThrowsUnknownColumn(String user, CheckedConsumer<Connection, SQLException> action, String column)
|
||||
throws Exception {
|
||||
SQLException e;
|
||||
try (Connection connection = es(userProperties(user))) {
|
||||
e = expectThrows(SQLException.class, () -> action.accept(connection));
|
||||
|
@ -123,8 +125,7 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void queryWorksAsAdmin() throws Exception {
|
||||
try (Connection h2 = LocalH2.anonymousDb();
|
||||
Connection es = es(adminProperties())) {
|
||||
try (Connection h2 = LocalH2.anonymousDb(); Connection es = es(adminProperties())) {
|
||||
h2.createStatement().executeUpdate("CREATE TABLE test (a BIGINT, b BIGINT, c BIGINT)");
|
||||
h2.createStatement().executeUpdate("INSERT INTO test (a, b, c) VALUES (1, 2, 3), (4, 5, 6)");
|
||||
|
||||
|
@ -138,29 +139,26 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectActionMatchesAdmin(
|
||||
con -> con.createStatement().executeQuery(adminSql),
|
||||
user,
|
||||
con -> con.createStatement().executeQuery(userSql));
|
||||
con -> con.createStatement().executeQuery(userSql)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expectScrollMatchesAdmin(String adminSql, String user, String userSql) throws Exception {
|
||||
expectActionMatchesAdmin(
|
||||
con -> {
|
||||
Statement st = con.createStatement();
|
||||
st.setFetchSize(1);
|
||||
return st.executeQuery(adminSql);
|
||||
},
|
||||
user,
|
||||
con -> {
|
||||
Statement st = con.createStatement();
|
||||
st.setFetchSize(1);
|
||||
return st.executeQuery(userSql);
|
||||
});
|
||||
expectActionMatchesAdmin(con -> {
|
||||
Statement st = con.createStatement();
|
||||
st.setFetchSize(1);
|
||||
return st.executeQuery(adminSql);
|
||||
}, user, con -> {
|
||||
Statement st = con.createStatement();
|
||||
st.setFetchSize(1);
|
||||
return st.executeQuery(userSql);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expectDescribe(Map<String, List<String>> columns, String user) throws Exception {
|
||||
try (Connection h2 = LocalH2.anonymousDb();
|
||||
Connection es = es(userProperties(user))) {
|
||||
try (Connection h2 = LocalH2.anonymousDb(); Connection es = es(userProperties(user))) {
|
||||
// h2 doesn't have the same sort of DESCRIBE that we have so we emulate it
|
||||
h2.createStatement().executeUpdate("CREATE TABLE mock (column VARCHAR, type VARCHAR, mapping VARCHAR)");
|
||||
if (columns.size() > 0) {
|
||||
|
@ -222,10 +220,7 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void expectUnknownColumn(String user, String sql, String column) throws Exception {
|
||||
expectActionThrowsUnknownColumn(
|
||||
user,
|
||||
con -> con.createStatement().executeQuery(sql),
|
||||
column);
|
||||
expectActionThrowsUnknownColumn(user, con -> con.createStatement().executeQuery(sql), column);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -236,12 +231,12 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectUnauthorized("cluster:monitor/main", user, () -> es(userProperties(user)).getMetaData().getDatabaseMinorVersion());
|
||||
|
||||
// by moving to field caps these calls do not require the monitor permission
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("SELECT * FROM test"));
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("SHOW TABLES LIKE 'test'"));
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("DESCRIBE test"));
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("SELECT * FROM test"));
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("SHOW TABLES LIKE 'test'"));
|
||||
// expectUnauthorized("cluster:monitor/main", user,
|
||||
// () -> es(userProperties(user)).createStatement().executeQuery("DESCRIBE test"));
|
||||
}
|
||||
|
||||
private void expectUnauthorized(String action, String user, ThrowingRunnable r) {
|
||||
|
@ -261,7 +256,8 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getTables("%", "%", "%t", null),
|
||||
"full_access",
|
||||
con -> con.getMetaData().getTables("%", "%", "%", null));
|
||||
con -> con.getMetaData().getTables("%", "%", "%", null)
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetTablesWithNoAccess() throws Exception {
|
||||
|
@ -276,7 +272,8 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getTables("%", "%", "bort", null),
|
||||
"read_bort",
|
||||
con -> con.getMetaData().getTables("%", "%", "%", null));
|
||||
con -> con.getMetaData().getTables("%", "%", "%", null)
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetTablesWithInAccessibleIndex() throws Exception {
|
||||
|
@ -285,16 +282,18 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getTables("%", "%", "not_created", null),
|
||||
"read_bort",
|
||||
con -> con.getMetaData().getTables("%", "%", "test", null));
|
||||
con -> con.getMetaData().getTables("%", "%", "test", null)
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetColumnsWorksAsFullAccess() throws Exception {
|
||||
createUser("full_access", "cli_or_drivers_minimal");
|
||||
|
||||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getColumns(null, "%", "%t", "%"),
|
||||
con -> con.getMetaData().getColumns(null, "%", "%t", "%"),
|
||||
"full_access",
|
||||
con -> con.getMetaData().getColumns(null, "%", "%t", "%"));
|
||||
con -> con.getMetaData().getColumns(null, "%", "%t", "%")
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetColumnsWithNoAccess() throws Exception {
|
||||
|
@ -307,18 +306,20 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
createUser("wrong_access", "read_something_else");
|
||||
|
||||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getColumns(null, "%", "not_created", "%"),
|
||||
con -> con.getMetaData().getColumns(null, "%", "not_created", "%"),
|
||||
"wrong_access",
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%"));
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%")
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetColumnsSingleFieldGranted() throws Exception {
|
||||
createUser("only_a", "read_test_a");
|
||||
|
||||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "a"),
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "a"),
|
||||
"only_a",
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%"));
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%")
|
||||
);
|
||||
}
|
||||
|
||||
public void testMetaDataGetColumnsSingleFieldExcepted() throws Exception {
|
||||
|
@ -345,6 +346,7 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
expectActionMatchesAdmin(
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%"),
|
||||
"no_3s",
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%"));
|
||||
con -> con.getMetaData().getColumns(null, "%", "test", "%")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,8 +28,7 @@ public class RestSqlIT extends RestSqlTestCase {
|
|||
|
||||
static Settings securitySettings() {
|
||||
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(ThreadContext.PREFIX + ".Authorization", token);
|
||||
Settings.Builder builder = Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token);
|
||||
if (SSL_ENABLED) {
|
||||
Path keyStore;
|
||||
try {
|
||||
|
@ -40,8 +39,7 @@ public class RestSqlIT extends RestSqlTestCase {
|
|||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
builder.put(ESRestTestCase.TRUSTSTORE_PATH, keyStore)
|
||||
.put(ESRestTestCase.TRUSTSTORE_PASSWORD, "keypass");
|
||||
builder.put(ESRestTestCase.TRUSTSTORE_PATH, keyStore).put(ESRestTestCase.TRUSTSTORE_PASSWORD, "keypass");
|
||||
}
|
||||
return builder.build();
|
||||
}
|
|
@ -50,13 +50,15 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
public void queryWorksAsAdmin() throws Exception {
|
||||
String mode = randomMode();
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo(mode, "a", "long", JDBCType.BIGINT, 20),
|
||||
columnInfo(mode, "b", "long", JDBCType.BIGINT, 20),
|
||||
columnInfo(mode, "c", "long", JDBCType.BIGINT, 20)));
|
||||
expected.put("rows", Arrays.asList(
|
||||
Arrays.asList(1, 2, 3),
|
||||
Arrays.asList(4, 5, 6)));
|
||||
columnInfo(mode, "c", "long", JDBCType.BIGINT, 20)
|
||||
)
|
||||
);
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6)));
|
||||
|
||||
assertResponse(expected, runSql(null, mode, "SELECT * FROM test ORDER BY a"));
|
||||
}
|
||||
|
@ -70,10 +72,16 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
@Override
|
||||
public void expectScrollMatchesAdmin(String adminSql, String user, String userSql) throws Exception {
|
||||
String mode = randomMode();
|
||||
Map<String, Object> adminResponse = runSql(null,
|
||||
new StringEntity(query(adminSql).mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON), mode);
|
||||
Map<String, Object> otherResponse = runSql(user,
|
||||
new StringEntity(query(adminSql).mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON), mode);
|
||||
Map<String, Object> adminResponse = runSql(
|
||||
null,
|
||||
new StringEntity(query(adminSql).mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
);
|
||||
Map<String, Object> otherResponse = runSql(
|
||||
user,
|
||||
new StringEntity(query(adminSql).mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
);
|
||||
|
||||
String adminCursor = (String) adminResponse.remove("cursor");
|
||||
String otherCursor = (String) otherResponse.remove("cursor");
|
||||
|
@ -81,10 +89,16 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
assertNotNull(otherCursor);
|
||||
assertResponse(adminResponse, otherResponse);
|
||||
while (true) {
|
||||
adminResponse = runSql(null, new StringEntity(cursor(adminCursor).mode(mode).toString(),
|
||||
ContentType.APPLICATION_JSON), mode);
|
||||
otherResponse = runSql(user, new StringEntity(cursor(otherCursor).mode(mode).toString(),
|
||||
ContentType.APPLICATION_JSON), mode);
|
||||
adminResponse = runSql(
|
||||
null,
|
||||
new StringEntity(cursor(adminCursor).mode(mode).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
);
|
||||
otherResponse = runSql(
|
||||
user,
|
||||
new StringEntity(cursor(otherCursor).mode(mode).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
);
|
||||
adminCursor = (String) adminResponse.remove("cursor");
|
||||
otherCursor = (String) otherResponse.remove("cursor");
|
||||
assertResponse(adminResponse, otherResponse);
|
||||
|
@ -100,10 +114,14 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
public void expectDescribe(Map<String, List<String>> columns, String user) throws Exception {
|
||||
String mode = randomMode();
|
||||
Map<String, Object> expected = new HashMap<>(3);
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo(mode, "column", "keyword", JDBCType.VARCHAR, 32766),
|
||||
columnInfo(mode, "type", "keyword", JDBCType.VARCHAR, 32766),
|
||||
columnInfo(mode, "mapping", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
columnInfo(mode, "mapping", "keyword", JDBCType.VARCHAR, 32766)
|
||||
)
|
||||
);
|
||||
List<List<String>> rows = new ArrayList<>(columns.size());
|
||||
for (Map.Entry<String, List<String>> column : columns.entrySet()) {
|
||||
List<String> cols = new ArrayList<>();
|
||||
|
@ -142,10 +160,9 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
* by the time the test runs.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
List<List<String>> rowsNoSecurity = ((List<List<String>>) actual.get("rows"))
|
||||
.stream()
|
||||
.filter(ls -> ls.get(0).startsWith(".security") == false)
|
||||
.collect(Collectors.toList());
|
||||
List<List<String>> rowsNoSecurity = ((List<List<String>>) actual.get("rows")).stream()
|
||||
.filter(ls -> ls.get(0).startsWith(".security") == false)
|
||||
.collect(Collectors.toList());
|
||||
actual.put("rows", rowsNoSecurity);
|
||||
assertResponse(expected, actual);
|
||||
}
|
||||
|
@ -231,20 +248,28 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
createUser("full_access", "rest_minimal");
|
||||
final String mode = randomMode();
|
||||
|
||||
Map<String, Object> adminResponse = RestActions.runSql(null,
|
||||
new StringEntity(query("SELECT * FROM test").mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON), mode);
|
||||
Map<String, Object> adminResponse = RestActions.runSql(
|
||||
null,
|
||||
new StringEntity(query("SELECT * FROM test").mode(mode).fetchSize(1).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
);
|
||||
|
||||
String cursor = (String) adminResponse.remove("cursor");
|
||||
assertNotNull(cursor);
|
||||
|
||||
ResponseException e = expectThrows(ResponseException.class, () -> RestActions.runSql("full_access",
|
||||
new StringEntity(cursor(cursor).mode(mode).toString(), ContentType.APPLICATION_JSON), mode));
|
||||
ResponseException e = expectThrows(
|
||||
ResponseException.class,
|
||||
() -> RestActions.runSql(
|
||||
"full_access",
|
||||
new StringEntity(cursor(cursor).mode(mode).toString(), ContentType.APPLICATION_JSON),
|
||||
mode
|
||||
)
|
||||
);
|
||||
// TODO return a better error message for bad scrolls
|
||||
assertThat(e.getMessage(), containsString("No search context found for id"));
|
||||
assertEquals(404, e.getResponse().getStatusLine().getStatusCode());
|
||||
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expect(true, SQL_ACTION_NAME, "full_access", empty())
|
||||
// one scroll access denied per shard
|
||||
.expect("access_denied", SQL_ACTION_NAME, "full_access", "default_native", empty(), "InternalScrollSearchRequest")
|
||||
|
@ -253,21 +278,30 @@ public class RestSqlSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
protected class RestAuditLogAsserter extends AuditLogAsserter {
|
||||
@Override
|
||||
public AuditLogAsserter expect(String eventType, String action, String principal, String realm,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher, String request) {
|
||||
final Matcher<String> runByPrincipalMatcher = principal.equals("test_admin") ? Matchers.nullValue(String.class)
|
||||
: Matchers.is("test_admin");
|
||||
final Matcher<String> runByRealmMatcher = realm.equals("default_file") ? Matchers.nullValue(String.class)
|
||||
: Matchers.is("default_file");
|
||||
public AuditLogAsserter expect(
|
||||
String eventType,
|
||||
String action,
|
||||
String principal,
|
||||
String realm,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher,
|
||||
String request
|
||||
) {
|
||||
final Matcher<String> runByPrincipalMatcher = principal.equals("test_admin")
|
||||
? Matchers.nullValue(String.class)
|
||||
: Matchers.is("test_admin");
|
||||
final Matcher<String> runByRealmMatcher = realm.equals("default_file")
|
||||
? Matchers.nullValue(String.class)
|
||||
: Matchers.is("default_file");
|
||||
logCheckers.add(
|
||||
m -> eventType.equals(m.get("event.action"))
|
||||
&& action.equals(m.get("action"))
|
||||
&& principal.equals(m.get("user.name"))
|
||||
&& realm.equals(m.get("user.realm"))
|
||||
&& runByPrincipalMatcher.matches(m.get("user.run_by.name"))
|
||||
&& runByRealmMatcher.matches(m.get("user.run_by.realm"))
|
||||
&& indicesMatcher.matches(m.get("indices"))
|
||||
&& request.equals(m.get("request.name")));
|
||||
m -> eventType.equals(m.get("event.action"))
|
||||
&& action.equals(m.get("action"))
|
||||
&& principal.equals(m.get("user.name"))
|
||||
&& realm.equals(m.get("user.realm"))
|
||||
&& runByPrincipalMatcher.matches(m.get("user.run_by.name"))
|
||||
&& runByRealmMatcher.matches(m.get("user.run_by.realm"))
|
||||
&& indicesMatcher.matches(m.get("indices"))
|
||||
&& request.equals(m.get("request.name"))
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
|
@ -58,22 +58,31 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
*/
|
||||
protected interface Actions {
|
||||
String minimalPermissionsForAllActions();
|
||||
|
||||
void queryWorksAsAdmin() throws Exception;
|
||||
|
||||
/**
|
||||
* Assert that running some sql as a user returns the same result as running it as
|
||||
* the administrator.
|
||||
*/
|
||||
void expectMatchesAdmin(String adminSql, String user, String userSql) throws Exception;
|
||||
|
||||
/**
|
||||
* Same as {@link #expectMatchesAdmin(String, String, String)} but sets the scroll size
|
||||
* to 1 and completely scrolls the results.
|
||||
*/
|
||||
void expectScrollMatchesAdmin(String adminSql, String user, String userSql) throws Exception;
|
||||
|
||||
void expectDescribe(Map<String, List<String>> columns, String user) throws Exception;
|
||||
|
||||
void expectShowTables(List<String> tables, String user) throws Exception;
|
||||
|
||||
void expectForbidden(String user, String sql) throws Exception;
|
||||
|
||||
void expectUnknownIndex(String user, String sql) throws Exception;
|
||||
|
||||
void expectUnknownColumn(String user, String sql, String column) throws Exception;
|
||||
|
||||
void checkNoMonitorMain(String user) throws Exception;
|
||||
}
|
||||
|
||||
|
@ -87,23 +96,26 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
private static final Path AUDIT_LOG_FILE = lookupAuditLog();
|
||||
private static final Path ROLLED_OVER_AUDIT_LOG_FILE = lookupRolledOverAuditLog();
|
||||
|
||||
@SuppressForbidden(reason="security doesn't work with mock filesystem")
|
||||
@SuppressForbidden(reason = "security doesn't work with mock filesystem")
|
||||
private static Path lookupAuditLog() {
|
||||
String auditLogFileString = System.getProperty("tests.audit.logfile");
|
||||
if (null == auditLogFileString) {
|
||||
throw new IllegalStateException("tests.audit.logfile must be set to run this test. It is automatically "
|
||||
throw new IllegalStateException(
|
||||
"tests.audit.logfile must be set to run this test. It is automatically "
|
||||
+ "set by gradle. If you must set it yourself then it should be the absolute path to the audit "
|
||||
+ "log file generated by running x-pack with audit logging enabled.");
|
||||
+ "log file generated by running x-pack with audit logging enabled."
|
||||
);
|
||||
}
|
||||
return Paths.get(auditLogFileString);
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason="security doesn't work with mock filesystem")
|
||||
@SuppressForbidden(reason = "security doesn't work with mock filesystem")
|
||||
private static Path lookupRolledOverAuditLog() {
|
||||
String auditLogFileString = System.getProperty("tests.audit.yesterday.logfile");
|
||||
if (null == auditLogFileString) {
|
||||
throw new IllegalStateException("tests.audit.yesterday.logfile must be set to run this test. It should be automatically "
|
||||
+ "set by gradle.");
|
||||
throw new IllegalStateException(
|
||||
"tests.audit.yesterday.logfile must be set to run this test. It should be automatically " + "set by gradle."
|
||||
);
|
||||
}
|
||||
return Paths.get(auditLogFileString);
|
||||
}
|
||||
|
@ -205,7 +217,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
@AfterClass
|
||||
public static void wipeIndicesAfterTests() throws IOException {
|
||||
try {
|
||||
wipeAllIndices();
|
||||
wipeAllIndices();
|
||||
} finally {
|
||||
// Clear the static state so other subclasses can reuse it later
|
||||
oneTimeSetup = false;
|
||||
|
@ -220,17 +232,14 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
|
||||
public void testQueryWorksAsAdmin() throws Exception {
|
||||
actions.queryWorksAsAdmin();
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testQueryWithFullAccess() throws Exception {
|
||||
createUser("full_access", actions.minimalPermissionsForAllActions());
|
||||
|
||||
actions.expectMatchesAdmin("SELECT * FROM test ORDER BY a", "full_access", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("full_access", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -239,8 +248,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("full_access", actions.minimalPermissionsForAllActions());
|
||||
|
||||
actions.expectScrollMatchesAdmin("SELECT * FROM test ORDER BY a", "full_access", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
/* Scrolling doesn't have to access the index again, at least not through sql.
|
||||
* If we asserted query and scroll logs then we would see the scroll. */
|
||||
.expect(true, SQL_ACTION_NAME, "test_admin", empty())
|
||||
|
@ -255,9 +263,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("no_access", "read_nothing");
|
||||
|
||||
actions.expectForbidden("no_access", "SELECT * FROM test");
|
||||
createAuditLogAsserter()
|
||||
.expect(false, SQL_ACTION_NAME, "no_access", empty())
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expect(false, SQL_ACTION_NAME, "no_access", empty()).assertLogs();
|
||||
}
|
||||
|
||||
public void testQueryWrongAccess() throws Exception {
|
||||
|
@ -265,9 +271,9 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
|
||||
actions.expectUnknownIndex("wrong_access", "SELECT * FROM test");
|
||||
createAuditLogAsserter()
|
||||
//This user has permission to run sql queries so they are given preliminary authorization
|
||||
// This user has permission to run sql queries so they are given preliminary authorization
|
||||
.expect(true, SQL_ACTION_NAME, "wrong_access", empty())
|
||||
//the following get index is granted too but against the no indices placeholder, as ignore_unavailable=true
|
||||
// the following get index is granted too but against the no indices placeholder, as ignore_unavailable=true
|
||||
.expect(true, FieldCapabilitiesAction.NAME, "wrong_access", hasItems("*", "-*"))
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -276,8 +282,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("only_a", "read_test_a");
|
||||
|
||||
actions.expectMatchesAdmin("SELECT a FROM test ORDER BY a", "only_a", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("only_a", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -286,8 +291,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("only_a", "read_test_a");
|
||||
|
||||
actions.expectScrollMatchesAdmin("SELECT a FROM test ORDER BY a", "only_a", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
/* Scrolling doesn't have to access the index again, at least not through sql.
|
||||
* If we asserted query and scroll logs then we would see the scroll. */
|
||||
.expect(true, SQL_ACTION_NAME, "test_admin", empty())
|
||||
|
@ -308,17 +312,14 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
* query from the audit side because all the permissions checked
|
||||
* out but it failed in SQL because it couldn't compile the
|
||||
* query without the metadata for the missing field. */
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("only_a", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("only_a", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testQuerySingleFieldExcepted() throws Exception {
|
||||
createUser("not_c", "read_test_a_and_b");
|
||||
|
||||
actions.expectMatchesAdmin("SELECT a, b FROM test ORDER BY a", "not_c", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("not_c", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -327,8 +328,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("not_c", "read_test_a_and_b");
|
||||
|
||||
actions.expectScrollMatchesAdmin("SELECT a, b FROM test ORDER BY a", "not_c", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
/* Scrolling doesn't have to access the index again, at least not through sql.
|
||||
* If we asserted query and scroll logs then we would see the scroll. */
|
||||
.expect(true, SQL_ACTION_NAME, "test_admin", empty())
|
||||
|
@ -349,34 +349,28 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
* query from the audit side because all the permissions checked
|
||||
* out but it failed in SQL because it couldn't compile the
|
||||
* query without the metadata for the missing field. */
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("not_c", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("not_c", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testQueryDocumentExcluded() throws Exception {
|
||||
createUser("no_3s", "read_test_without_c_3");
|
||||
|
||||
actions.expectMatchesAdmin("SELECT * FROM test WHERE c != 3 ORDER BY a", "no_3s", "SELECT * FROM test ORDER BY a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("no_3s", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
||||
public void testShowTablesWorksAsAdmin() throws Exception {
|
||||
actions.expectShowTables(Arrays.asList("bort", "test"), null);
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionGetIndex("test_admin", "bort", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionGetIndex("test_admin", "bort", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testShowTablesWorksAsFullAccess() throws Exception {
|
||||
createUser("full_access", actions.minimalPermissionsForAllActions());
|
||||
|
||||
actions.expectMatchesAdmin("SHOW TABLES LIKE '%t'", "full_access", "SHOW TABLES");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionGetIndex("test_admin", "bort", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionGetIndex("test_admin", "bort", "test")
|
||||
.expectSqlCompositeActionGetIndex("full_access", "bort", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -385,17 +379,15 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("no_access", "read_nothing");
|
||||
|
||||
actions.expectForbidden("no_access", "SHOW TABLES");
|
||||
createAuditLogAsserter()
|
||||
.expect(false, SQL_ACTION_NAME, "no_access", empty())
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expect(false, SQL_ACTION_NAME, "no_access", empty()).assertLogs();
|
||||
}
|
||||
|
||||
public void testShowTablesWithLimitedAccess() throws Exception {
|
||||
createUser("read_bort", "read_bort");
|
||||
|
||||
actions.expectMatchesAdmin("SHOW TABLES LIKE 'bort'", "read_bort", "SHOW TABLES");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionGetIndex("test_admin", "bort").expectSqlCompositeActionGetIndex("read_bort", "bort")
|
||||
createAuditLogAsserter().expectSqlCompositeActionGetIndex("test_admin", "bort")
|
||||
.expectSqlCompositeActionGetIndex("read_bort", "bort")
|
||||
.assertLogs();
|
||||
}
|
||||
|
||||
|
@ -403,8 +395,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("read_bort", "read_bort");
|
||||
|
||||
actions.expectMatchesAdmin("SHOW TABLES LIKE 'not-created'", "read_bort", "SHOW TABLES LIKE 'test'");
|
||||
createAuditLogAsserter()
|
||||
.expect(true, SQL_ACTION_NAME, "test_admin", empty())
|
||||
createAuditLogAsserter().expect(true, SQL_ACTION_NAME, "test_admin", empty())
|
||||
.expect(true, GetIndexAction.NAME, "test_admin", contains("*", "-*"))
|
||||
.expect(true, SQL_ACTION_NAME, "read_bort", empty())
|
||||
.expect(true, GetIndexAction.NAME, "read_bort", contains("*", "-*"))
|
||||
|
@ -417,17 +408,14 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
expected.put("b", asList("BIGINT", "long"));
|
||||
expected.put("c", asList("BIGINT", "long"));
|
||||
actions.expectDescribe(expected, null);
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testDescribeWorksAsFullAccess() throws Exception {
|
||||
createUser("full_access", actions.minimalPermissionsForAllActions());
|
||||
|
||||
actions.expectMatchesAdmin("DESCRIBE test", "full_access", "DESCRIBE test");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("full_access", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -436,9 +424,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("no_access", "read_nothing");
|
||||
|
||||
actions.expectForbidden("no_access", "DESCRIBE test");
|
||||
createAuditLogAsserter()
|
||||
.expect(false, SQL_ACTION_NAME, "no_access", empty())
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expect(false, SQL_ACTION_NAME, "no_access", empty()).assertLogs();
|
||||
}
|
||||
|
||||
public void testDescribeWithWrongAccess() throws Exception {
|
||||
|
@ -446,9 +432,9 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
|
||||
actions.expectDescribe(Collections.emptyMap(), "wrong_access");
|
||||
createAuditLogAsserter()
|
||||
//This user has permission to run sql queries so they are given preliminary authorization
|
||||
// This user has permission to run sql queries so they are given preliminary authorization
|
||||
.expect(true, SQL_ACTION_NAME, "wrong_access", empty())
|
||||
//the following get index is granted too but against the no indices placeholder, as ignore_unavailable=true
|
||||
// the following get index is granted too but against the no indices placeholder, as ignore_unavailable=true
|
||||
.expect(true, FieldCapabilitiesAction.NAME, "wrong_access", hasItems("*", "-*"))
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -457,9 +443,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
createUser("only_a", "read_test_a");
|
||||
|
||||
actions.expectDescribe(singletonMap("a", asList("BIGINT", "long")), "only_a");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("only_a", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("only_a", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testDescribeSingleFieldExcepted() throws Exception {
|
||||
|
@ -469,17 +453,14 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
expected.put("a", asList("BIGINT", "long"));
|
||||
expected.put("b", asList("BIGINT", "long"));
|
||||
actions.expectDescribe(expected, "not_c");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("not_c", "test")
|
||||
.assertLogs();
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("not_c", "test").assertLogs();
|
||||
}
|
||||
|
||||
public void testDescribeDocumentExcluded() throws Exception {
|
||||
createUser("no_3s", "read_test_without_c_3");
|
||||
|
||||
actions.expectMatchesAdmin("DESCRIBE test", "no_3s", "DESCRIBE test");
|
||||
createAuditLogAsserter()
|
||||
.expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
createAuditLogAsserter().expectSqlCompositeActionFieldCaps("test_admin", "test")
|
||||
.expectSqlCompositeActionFieldCaps("no_3s", "test")
|
||||
.assertLogs();
|
||||
}
|
||||
|
@ -500,7 +481,8 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
protected static void createUser(String name, String role) throws IOException {
|
||||
Request request = new Request("PUT", "/_security/user/" + name);
|
||||
XContentBuilder user = JsonXContent.contentBuilder().prettyPrint();
|
||||
user.startObject(); {
|
||||
user.startObject();
|
||||
{
|
||||
user.field("password", "testpass");
|
||||
user.field("roles", role);
|
||||
}
|
||||
|
@ -533,45 +515,58 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuditLogAsserter expect(boolean granted, String action, String principal,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher) {
|
||||
public AuditLogAsserter expect(
|
||||
boolean granted,
|
||||
String action,
|
||||
String principal,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher
|
||||
) {
|
||||
String request;
|
||||
switch (action) {
|
||||
case SQL_ACTION_NAME:
|
||||
request = "SqlQueryRequest";
|
||||
break;
|
||||
case GetIndexAction.NAME:
|
||||
request = GetIndexRequest.class.getSimpleName();
|
||||
break;
|
||||
case SQL_ACTION_NAME:
|
||||
request = "SqlQueryRequest";
|
||||
break;
|
||||
case GetIndexAction.NAME:
|
||||
request = GetIndexRequest.class.getSimpleName();
|
||||
break;
|
||||
case FieldCapabilitiesAction.NAME:
|
||||
request = FieldCapabilitiesRequest.class.getSimpleName();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown action [" + action + "]");
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown action [" + action + "]");
|
||||
}
|
||||
final String eventAction = granted ? "access_granted" : "access_denied";
|
||||
final String realm = principal.equals("test_admin") ? "default_file" : "default_native";
|
||||
return expect(eventAction, action, principal, realm, indicesMatcher, request);
|
||||
}
|
||||
|
||||
public AuditLogAsserter expect(String eventAction, String action, String principal, String realm,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher, String request) {
|
||||
logCheckers.add(m ->
|
||||
eventAction.equals(m.get("event.action"))
|
||||
&& action.equals(m.get("action"))
|
||||
&& principal.equals(m.get("user.name"))
|
||||
&& realm.equals(m.get("user.realm"))
|
||||
&& Matchers.nullValue(String.class).matches(m.get("user.run_by.name"))
|
||||
&& Matchers.nullValue(String.class).matches(m.get("user.run_by.realm"))
|
||||
&& indicesMatcher.matches(m.get("indices"))
|
||||
&& request.equals(m.get("request.name"))
|
||||
public AuditLogAsserter expect(
|
||||
String eventAction,
|
||||
String action,
|
||||
String principal,
|
||||
String realm,
|
||||
Matcher<? extends Iterable<? extends String>> indicesMatcher,
|
||||
String request
|
||||
) {
|
||||
logCheckers.add(
|
||||
m -> eventAction.equals(m.get("event.action"))
|
||||
&& action.equals(m.get("action"))
|
||||
&& principal.equals(m.get("user.name"))
|
||||
&& realm.equals(m.get("user.realm"))
|
||||
&& Matchers.nullValue(String.class).matches(m.get("user.run_by.name"))
|
||||
&& Matchers.nullValue(String.class).matches(m.get("user.run_by.realm"))
|
||||
&& indicesMatcher.matches(m.get("indices"))
|
||||
&& request.equals(m.get("request.name"))
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void assertLogs() throws Exception {
|
||||
assertFalse("Previous test had an audit-related failure. All subsequent audit related assertions are bogus because we can't "
|
||||
+ "guarantee that we fully cleaned up after the last test.", auditFailure);
|
||||
assertFalse(
|
||||
"Previous test had an audit-related failure. All subsequent audit related assertions are bogus because we can't "
|
||||
+ "guarantee that we fully cleaned up after the last test.",
|
||||
auditFailure
|
||||
);
|
||||
try {
|
||||
// use a second variable since the `assertBusy()` block can be executed multiple times and the
|
||||
// static auditFileRolledOver value can change and mess up subsequent calls of this code block
|
||||
|
@ -624,18 +619,17 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
if (++index == 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
try {
|
||||
final Map<String, Object> log = XContentHelper.convertToMap(JsonXContent.jsonXContent, line, false);
|
||||
if (false == ("access_denied".equals(log.get("event.action"))
|
||||
|| "access_granted".equals(log.get("event.action")))) {
|
||||
|| "access_granted".equals(log.get("event.action")))) {
|
||||
continue;
|
||||
}
|
||||
assertThat(log.containsKey("action"), is(true));
|
||||
if (false == (SQL_ACTION_NAME.equals(log.get("action"))
|
||||
|| GetIndexAction.NAME.equals(log.get("action"))
|
||||
|| FieldCapabilitiesAction.NAME.equals(log.get("action")))) {
|
||||
|| GetIndexAction.NAME.equals(log.get("action"))
|
||||
|| FieldCapabilitiesAction.NAME.equals(log.get("action")))) {
|
||||
// TODO we may want to extend this and the assertions to SearchAction.NAME as well
|
||||
continue;
|
||||
}
|
||||
|
@ -651,8 +645,9 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
* SQL drops them from the interface. So we might have access to them, but we
|
||||
* don't show them.
|
||||
*/
|
||||
indices = indices.stream().filter(
|
||||
idx -> false == RestrictedIndicesNames.isRestricted(idx)).collect(Collectors.toList());
|
||||
indices = indices.stream()
|
||||
.filter(idx -> false == RestrictedIndicesNames.isRestricted(idx))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
// Use a sorted list for indices for consistent error reporting
|
||||
|
@ -678,8 +673,14 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
notMatching.add(c);
|
||||
}
|
||||
if (false == notMatching.isEmpty()) {
|
||||
fail("Some checkers " + notMatching + " didn't match any logs. All logs:" + logsMessage(allLogs)
|
||||
+ "\nRemaining logs:" + logsMessage(logs));
|
||||
fail(
|
||||
"Some checkers "
|
||||
+ notMatching
|
||||
+ " didn't match any logs. All logs:"
|
||||
+ logsMessage(allLogs)
|
||||
+ "\nRemaining logs:"
|
||||
+ logsMessage(logs)
|
||||
);
|
||||
}
|
||||
if (false == logs.isEmpty()) {
|
||||
fail("Not all logs matched. Unmatched logs:" + logsMessage(logs));
|
||||
|
@ -687,8 +688,10 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
});
|
||||
} catch (AssertionError e) {
|
||||
auditFailure = true;
|
||||
logger.warn("Failed to find an audit log. Skipping remaining tests in this class after this the missing audit"
|
||||
+ "logs could turn up later.");
|
||||
logger.warn(
|
||||
"Failed to find an audit log. Skipping remaining tests in this class after this the missing audit"
|
||||
+ "logs could turn up later."
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
|
||||
@Before
|
||||
private void setUpUsers() throws IOException {
|
||||
int usersCount = name.getMethodName().startsWith("testSingle") ? 1 : randomIntBetween(5, 15);
|
||||
int usersCount = name.getMethodName().startsWith("testSingle") ? 1 : randomIntBetween(5, 15);
|
||||
users = new ArrayList<>(usersCount);
|
||||
users.addAll(randomUnique(() -> randomAlphaOfLengthBetween(1, 15), usersCount));
|
||||
for (String user : users) {
|
||||
|
@ -77,8 +77,7 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
String randomUserName = users.get(0);
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("columns", Arrays.asList(columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomUserName)));
|
||||
Map<String, Object> actual = runSql(randomUserName, mode, SQL);
|
||||
|
||||
|
@ -86,32 +85,24 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
}
|
||||
|
||||
public void testSingleRandomUserWithWhereEvaluatingTrue() throws IOException {
|
||||
index("{\"test\":\"doc1\"}",
|
||||
"{\"test\":\"doc2\"}",
|
||||
"{\"test\":\"doc3\"}");
|
||||
index("{\"test\":\"doc1\"}", "{\"test\":\"doc2\"}", "{\"test\":\"doc3\"}");
|
||||
String mode = randomMode().toString();
|
||||
String randomUserName = users.get(0);
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomUserName),
|
||||
Arrays.asList(randomUserName),
|
||||
Arrays.asList(randomUserName)));
|
||||
expected.put("columns", Arrays.asList(columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomUserName), Arrays.asList(randomUserName), Arrays.asList(randomUserName)));
|
||||
Map<String, Object> actual = runSql(randomUserName, mode, SQL + " FROM test WHERE USER()='" + randomUserName + "' LIMIT 3");
|
||||
assertResponse(expected, actual);
|
||||
}
|
||||
|
||||
public void testSingleRandomUserWithWhereEvaluatingFalse() throws IOException {
|
||||
index("{\"test\":\"doc1\"}",
|
||||
"{\"test\":\"doc2\"}",
|
||||
"{\"test\":\"doc3\"}");
|
||||
index("{\"test\":\"doc1\"}", "{\"test\":\"doc2\"}", "{\"test\":\"doc3\"}");
|
||||
String mode = randomMode().toString();
|
||||
String randomUserName = users.get(0);
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("columns", Arrays.asList(columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Collections.<ArrayList<String>>emptyList());
|
||||
String anotherRandomUserName = randomValueOtherThan(randomUserName, () -> randomAlphaOfLengthBetween(1, 15));
|
||||
Map<String, Object> actual = runSql(randomUserName, mode, SQL + " FROM test WHERE USER()='" + anotherRandomUserName + "' LIMIT 3");
|
||||
|
@ -125,8 +116,7 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
String randomlyPickedUsername = randomFrom(users);
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("columns", Arrays.asList(columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomlyPickedUsername)));
|
||||
Map<String, Object> actual = runSql(randomlyPickedUsername, mode, SQL);
|
||||
|
||||
|
@ -136,18 +126,13 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
}
|
||||
|
||||
public void testSingleUserSelectFromIndex() throws IOException {
|
||||
index("{\"test\":\"doc1\"}",
|
||||
"{\"test\":\"doc2\"}",
|
||||
"{\"test\":\"doc3\"}");
|
||||
index("{\"test\":\"doc1\"}", "{\"test\":\"doc2\"}", "{\"test\":\"doc3\"}");
|
||||
String mode = randomMode().toString();
|
||||
String randomUserName = users.get(0);
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomUserName),
|
||||
Arrays.asList(randomUserName),
|
||||
Arrays.asList(randomUserName)));
|
||||
expected.put("columns", Arrays.asList(columnInfo(mode, "USER()", "keyword", JDBCType.VARCHAR, 32766)));
|
||||
expected.put("rows", Arrays.asList(Arrays.asList(randomUserName), Arrays.asList(randomUserName), Arrays.asList(randomUserName)));
|
||||
Map<String, Object> actual = runSql(randomUserName, mode, "SELECT USER() FROM test LIMIT 3");
|
||||
|
||||
assertResponse(expected, actual);
|
||||
|
@ -156,7 +141,8 @@ public class UserFunctionIT extends ESRestTestCase {
|
|||
private void createUser(String name, String role) throws IOException {
|
||||
Request request = new Request("PUT", "/_security/user/" + name);
|
||||
XContentBuilder user = JsonXContent.contentBuilder().prettyPrint();
|
||||
user.startObject(); {
|
||||
user.startObject();
|
||||
{
|
||||
user.field("password", "testpass");
|
||||
user.field("roles", role);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
grant {
|
||||
// Needed to read the audit log file
|
||||
permission java.io.FilePermission "${tests.audit.logfile}", "read";
|
||||
permission java.io.FilePermission "${tests.audit.yesterday.logfile}", "read";
|
||||
|
||||
//// Required by ssl subproject:
|
||||
// Required for the net client to setup ssl rather than use global ssl.
|
||||
permission java.lang.RuntimePermission "setFactory";
|
||||
};
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.ErrorsTestCase;
|
||||
|
||||
public class CliErrorsIT extends ErrorsTestCase {
|
||||
}
|
||||
public class CliErrorsIT extends ErrorsTestCase {}
|
|
@ -68,8 +68,10 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
|||
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[test]"));
|
||||
assertEquals("", readLine());
|
||||
|
||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test WHERE i = 2"),
|
||||
containsString("plan"));
|
||||
assertThat(
|
||||
command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test WHERE i = 2"),
|
||||
containsString("plan")
|
||||
);
|
||||
assertThat(readLine(), startsWith("----------"));
|
||||
assertThat(readLine(), startsWith("Project[[test.i{f}#"));
|
||||
assertThat(readLine(), startsWith("\\_Filter[test.i{f}#"));
|
||||
|
@ -123,8 +125,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
|||
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[test]"));
|
||||
assertEquals("", readLine());
|
||||
|
||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT COUNT(*) FROM test"),
|
||||
containsString("plan"));
|
||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT COUNT(*) FROM test"), containsString("plan"));
|
||||
assertThat(readLine(), startsWith("----------"));
|
||||
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(*)"));
|
||||
assertThat(readLine(), startsWith("\\_EsRelation[test][i{f}#"));
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.FetchSizeTestCase;
|
||||
|
||||
public class CliFetchSizeIT extends FetchSizeTestCase {
|
||||
}
|
||||
public class CliFetchSizeIT extends FetchSizeTestCase {}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.SelectTestCase;
|
||||
|
||||
public class CliSelectIT extends SelectTestCase {
|
||||
}
|
||||
public class CliSelectIT extends SelectTestCase {}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.cli.ShowTestCase;
|
||||
|
||||
public class CliShowIT extends ShowTestCase {
|
||||
}
|
||||
public class CliShowIT extends ShowTestCase {}
|
|
@ -33,6 +33,6 @@ public class JdbcCsvSpecIT extends CsvSpecTestCase {
|
|||
protected int fetchSize() {
|
||||
// using a smaller fetchSize for nested documents' tests to uncover bugs
|
||||
// similar to https://github.com/elastic/elasticsearch/issues/35176 quicker
|
||||
return fileName.startsWith("nested") && randomBoolean() ? randomIntBetween(1,5) : super.fetchSize();
|
||||
return fileName.startsWith("nested") && randomBoolean() ? randomIntBetween(1, 5) : super.fetchSize();
|
||||
}
|
||||
}
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.DatabaseMetaDataTestCase;
|
||||
|
||||
public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {
|
||||
}
|
||||
public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {}
|
|
@ -68,7 +68,7 @@ public class JdbcDocCsvSpecIT extends SpecBaseIntegrationTestCase {
|
|||
//
|
||||
// uncomment this to printout the result set and create new CSV tests
|
||||
//
|
||||
//JdbcTestUtils.logLikeCLI(elastic, log);
|
||||
// JdbcTestUtils.logLikeCLI(elastic, log);
|
||||
JdbcAssert.assertResultSets(expected, elastic, log, true, true);
|
||||
}
|
||||
|
|
@ -35,7 +35,6 @@ public class JdbcFrozenCsvSpecIT extends CsvSpecTestCase {
|
|||
return props;
|
||||
}
|
||||
|
||||
|
||||
public JdbcFrozenCsvSpecIT(String fileName, String groupName, String testName, Integer lineNumber, CsvTestCase testCase) {
|
||||
super(fileName, groupName, testName, lineNumber, testCase);
|
||||
}
|
|
@ -25,8 +25,8 @@ public class JdbcShardFailureIT extends JdbcIntegrationTestCase {
|
|||
client().performRequest(createTest1);
|
||||
|
||||
Request createTest2 = new Request("PUT", "/test2");
|
||||
String body2 = "{\"aliases\":{\"test\":{}}, \"mappings\": {\"properties\": {\"test_field\":{\"type\":\"integer\"}}}," +
|
||||
"\"settings\": {\"index.routing.allocation.include.node\": \"nowhere\"}}";
|
||||
String body2 = "{\"aliases\":{\"test\":{}}, \"mappings\": {\"properties\": {\"test_field\":{\"type\":\"integer\"}}},"
|
||||
+ "\"settings\": {\"index.routing.allocation.include.node\": \"nowhere\"}}";
|
||||
createTest2.setJsonEntity(body2);
|
||||
createTest2.addParameter("timeout", "100ms");
|
||||
client().performRequest(createTest2);
|
|
@ -7,5 +7,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.jdbc.ShowTablesTestCase;
|
||||
|
||||
public class JdbcShowTablesIT extends ShowTablesTestCase {
|
||||
}
|
||||
public class JdbcShowTablesIT extends ShowTablesTestCase {}
|
|
@ -17,40 +17,56 @@ import static org.hamcrest.Matchers.containsString;
|
|||
*/
|
||||
public class RestSqlIT extends RestSqlTestCase {
|
||||
|
||||
|
||||
public void testErrorMessageForTranslatingQueryWithWhereEvaluatingToFalse() throws IOException {
|
||||
index("{\"foo\":1}");
|
||||
expectBadRequest(() -> runTranslateSql(query("SELECT * FROM test WHERE foo = 1 AND foo = 2").toString()),
|
||||
containsString("Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates " +
|
||||
"to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: " +
|
||||
"[SELECT * FROM test WHERE foo = 1 AND foo = 2]"));
|
||||
expectBadRequest(
|
||||
() -> runTranslateSql(query("SELECT * FROM test WHERE foo = 1 AND foo = 2").toString()),
|
||||
containsString(
|
||||
"Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates "
|
||||
+ "to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: "
|
||||
+ "[SELECT * FROM test WHERE foo = 1 AND foo = 2]"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void testErrorMessageForTranslatingQueryWithLocalExecution() throws IOException {
|
||||
index("{\"foo\":1}");
|
||||
expectBadRequest(() -> runTranslateSql(query("SELECT SIN(PI())").toString()),
|
||||
containsString("Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates " +
|
||||
"to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: [SELECT SIN(PI())]"));
|
||||
expectBadRequest(
|
||||
() -> runTranslateSql(query("SELECT SIN(PI())").toString()),
|
||||
containsString(
|
||||
"Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates "
|
||||
+ "to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: [SELECT SIN(PI())]"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void testErrorMessageForTranslatingSQLCommandStatement() throws IOException {
|
||||
index("{\"foo\":1}");
|
||||
expectBadRequest(() -> runTranslateSql(query("SHOW FUNCTIONS").toString()),
|
||||
containsString("Cannot generate a query DSL for a special SQL command " +
|
||||
"(e.g.: DESCRIBE, SHOW), sql statement: [SHOW FUNCTIONS]"));
|
||||
expectBadRequest(
|
||||
() -> runTranslateSql(query("SHOW FUNCTIONS").toString()),
|
||||
containsString(
|
||||
"Cannot generate a query DSL for a special SQL command " + "(e.g.: DESCRIBE, SHOW), sql statement: [SHOW FUNCTIONS]"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void testErrorMessageForInvalidParamDataType() throws IOException {
|
||||
// proto.Mode not available
|
||||
expectBadRequest(() -> runTranslateSql(
|
||||
query("SELECT null WHERE 0 = ?").mode("odbc").params("[{\"type\":\"invalid\", \"value\":\"irrelevant\"}]").toString()),
|
||||
containsString("Invalid parameter data type [invalid]"));
|
||||
expectBadRequest(
|
||||
() -> runTranslateSql(
|
||||
query("SELECT null WHERE 0 = ?").mode("odbc").params("[{\"type\":\"invalid\", \"value\":\"irrelevant\"}]").toString()
|
||||
),
|
||||
containsString("Invalid parameter data type [invalid]")
|
||||
);
|
||||
}
|
||||
|
||||
public void testErrorMessageForInvalidParamSpec() throws IOException {
|
||||
expectBadRequest(() -> runTranslateSql(
|
||||
query("SELECT null WHERE 0 = ?").mode("odbc").params("[{\"type\":\"SHAPE\", \"value\":false}]").toString()),
|
||||
containsString("Cannot cast value [false] of type [BOOLEAN] to parameter type [SHAPE]"));
|
||||
expectBadRequest(
|
||||
() -> runTranslateSql(
|
||||
query("SELECT null WHERE 0 = ?").mode("odbc").params("[{\"type\":\"SHAPE\", \"value\":false}]").toString()
|
||||
),
|
||||
containsString("Cannot cast value [false] of type [BOOLEAN] to parameter type [SHAPE]")
|
||||
);
|
||||
|
||||
}
|
||||
}
|
|
@ -8,5 +8,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.rest.RestSqlUsageTestCase;
|
||||
|
||||
public class RestSqlUsageIT extends RestSqlUsageTestCase {
|
||||
}
|
||||
public class RestSqlUsageIT extends RestSqlUsageTestCase {}
|
|
@ -8,5 +8,4 @@ package org.elasticsearch.xpack.sql.qa.single_node;
|
|||
|
||||
import org.elasticsearch.xpack.sql.qa.SqlProtocolTestCase;
|
||||
|
||||
public class SqlProtocolIT extends SqlProtocolTestCase {
|
||||
}
|
||||
public class SqlProtocolIT extends SqlProtocolTestCase {}
|
|
@ -31,10 +31,14 @@ import java.util.Locale;
|
|||
*/
|
||||
public abstract class CustomDateFormatTestCase extends BaseRestSqlTestCase {
|
||||
|
||||
private static String[] customFormats = new String[] {"HH:mm yyyy-MM-dd", "HH:mm:ss yyyy-dd-MM", "HH:mm:ss VV", "HH:mm:ss VV z",
|
||||
"yyyy-MM-dd'T'HH:mm:ss'T'VV'T'z"};
|
||||
private static String[] nowFunctions = new String[] {"NOW()", "CURRENT_DATE()", "CURRENT_TIME()", "CURRENT_TIMESTAMP()"};
|
||||
private static String[] operators = new String[] {" < ", " > ", " <= ", " >= ", " = ", " != "};
|
||||
private static String[] customFormats = new String[] {
|
||||
"HH:mm yyyy-MM-dd",
|
||||
"HH:mm:ss yyyy-dd-MM",
|
||||
"HH:mm:ss VV",
|
||||
"HH:mm:ss VV z",
|
||||
"yyyy-MM-dd'T'HH:mm:ss'T'VV'T'z" };
|
||||
private static String[] nowFunctions = new String[] { "NOW()", "CURRENT_DATE()", "CURRENT_TIME()", "CURRENT_TIMESTAMP()" };
|
||||
private static String[] operators = new String[] { " < ", " > ", " <= ", " >= ", " = ", " != " };
|
||||
|
||||
public void testCustomDateFormatsWithNowFunctions() throws IOException {
|
||||
createIndex();
|
||||
|
@ -44,8 +48,11 @@ public abstract class CustomDateFormatTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
for (int i = 0; i < customFormats.length; i++) {
|
||||
String field = "date_" + i;
|
||||
docs[i] = "{\"" + field + "\":\"" +
|
||||
DateTimeFormatter.ofPattern(customFormats[i], Locale.ROOT).format(DateUtils.nowWithMillisResolution()) + "\"}";
|
||||
docs[i] = "{\""
|
||||
+ field
|
||||
+ "\":\""
|
||||
+ DateTimeFormatter.ofPattern(customFormats[i], Locale.ROOT).format(DateUtils.nowWithMillisResolution())
|
||||
+ "\"}";
|
||||
datesConditions.append(i > 0 ? " OR " : "").append(field + randomFrom(operators) + randomFrom(nowFunctions));
|
||||
}
|
||||
|
||||
|
@ -53,8 +60,7 @@ public abstract class CustomDateFormatTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
Request request = new Request("POST", RestSqlTestCase.SQL_QUERY_REST_ENDPOINT);
|
||||
final String query = "SELECT COUNT(*) AS c FROM test WHERE " + datesConditions.toString();
|
||||
request.setEntity(new StringEntity(query(query).mode(Mode.PLAIN).timeZone(zID).toString(),
|
||||
ContentType.APPLICATION_JSON));
|
||||
request.setEntity(new StringEntity(query(query).mode(Mode.PLAIN).timeZone(zID).toString(), ContentType.APPLICATION_JSON));
|
||||
|
||||
Response response = client().performRequest(request);
|
||||
String expectedJsonSnippet = "{\"columns\":[{\"name\":\"c\",\"type\":\"long\"}],\"rows\":[[";
|
||||
|
@ -75,17 +81,20 @@ public abstract class CustomDateFormatTestCase extends BaseRestSqlTestCase {
|
|||
Request request = new Request("PUT", "/test");
|
||||
XContentBuilder index = JsonXContent.contentBuilder().prettyPrint().startObject();
|
||||
|
||||
index.startObject("mappings"); {
|
||||
index.startObject("properties"); {
|
||||
index.startObject("mappings");
|
||||
{
|
||||
index.startObject("properties");
|
||||
{
|
||||
for (int i = 0; i < customFormats.length; i++) {
|
||||
String fieldName = "date_" + i;
|
||||
index.startObject(fieldName); {
|
||||
index.startObject(fieldName);
|
||||
{
|
||||
index.field("type", "date");
|
||||
index.field("format", customFormats[i]);
|
||||
}
|
||||
index.endObject();
|
||||
}
|
||||
index.endObject();
|
||||
index.endObject();
|
||||
}
|
||||
}
|
||||
index.endObject();
|
|
@ -12,16 +12,28 @@ package org.elasticsearch.xpack.sql.qa;
|
|||
*/
|
||||
public interface ErrorsTestCase {
|
||||
void testSelectInvalidSql() throws Exception;
|
||||
|
||||
void testSelectFromMissingIndex() throws Exception;
|
||||
|
||||
void testSelectColumnFromMissingIndex() throws Exception;
|
||||
|
||||
void testSelectFromEmptyIndex() throws Exception;
|
||||
|
||||
void testSelectColumnFromEmptyIndex() throws Exception;
|
||||
|
||||
void testSelectMissingField() throws Exception;
|
||||
|
||||
void testSelectMissingFunction() throws Exception;
|
||||
|
||||
void testSelectProjectScoreInAggContext() throws Exception;
|
||||
|
||||
void testSelectOrderByScoreInAggContext() throws Exception;
|
||||
|
||||
void testSelectGroupByScore() throws Exception;
|
||||
|
||||
void testSelectScoreSubField() throws Exception;
|
||||
|
||||
void testSelectScoreInScalar() throws Exception;
|
||||
|
||||
void testHardLimitForSortOnAggregate() throws Exception;
|
||||
}
|
|
@ -58,9 +58,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", "text_field", "text", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", "text_field", "text", JDBCType.VARCHAR, Integer.MAX_VALUE)));
|
||||
expected.put("rows", singletonList(singletonList(text)));
|
||||
assertResponse(expected, runSql(query));
|
||||
} else {
|
||||
|
@ -96,9 +94,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"keyword_field\":\"" + keyword + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", "keyword_field", "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", "keyword_field", "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)));
|
||||
expected.put("rows", singletonList(singletonList(ignoreAbove ? null : keyword)));
|
||||
assertResponse(expected, runSql("SELECT keyword_field FROM test"));
|
||||
}
|
||||
|
@ -130,9 +126,10 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"constant_keyword_field\":\"" + value + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", "constant_keyword_field", "constant_keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(columnInfo("plain", "constant_keyword_field", "constant_keyword", JDBCType.VARCHAR, Integer.MAX_VALUE))
|
||||
);
|
||||
expected.put("rows", singletonList(singletonList(value)));
|
||||
assertResponse(expected, runSql("SELECT constant_keyword_field FROM test"));
|
||||
}
|
||||
|
@ -150,9 +147,10 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"" + fieldType + "_field\":\"" + floatingPointNumber + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", fieldType + "_field", fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(columnInfo("plain", fieldType + "_field", fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE))
|
||||
);
|
||||
|
||||
// because "coerce" is true, a "123.456" floating point number STRING should be converted to 123, no matter the numeric field type
|
||||
expected.put("rows", singletonList(singletonList(123)));
|
||||
|
@ -183,14 +181,21 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"" + fieldType + "_field\":\"" + floatingPointNumber + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", fieldType + "_field", fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(columnInfo("plain", fieldType + "_field", fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE))
|
||||
);
|
||||
|
||||
// because "coerce" is true, a "123.456" floating point number STRING should be converted to 123.456 as number
|
||||
// and converted to 123.5 for "scaled_float" type
|
||||
expected.put("rows", singletonList(singletonList(
|
||||
isScaledFloat ? 123.5 : (fieldType != "double" ? Double.valueOf(123.456f) : Double.valueOf(floatingPointNumber)))));
|
||||
expected.put(
|
||||
"rows",
|
||||
singletonList(
|
||||
singletonList(
|
||||
isScaledFloat ? 123.5 : (fieldType != "double" ? Double.valueOf(123.456f) : Double.valueOf(floatingPointNumber))
|
||||
)
|
||||
)
|
||||
);
|
||||
assertResponse(expected, runSql("SELECT " + fieldType + "_field FROM test"));
|
||||
}
|
||||
|
||||
|
@ -264,9 +269,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", fieldName, fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", fieldName, fieldType, jdbcTypeFor(fieldType), Integer.MAX_VALUE)));
|
||||
expected.put("rows", singletonList(singletonList(ignoreMalformed ? null : actualValue)));
|
||||
assertResponse(expected, runSql(query));
|
||||
} else {
|
||||
|
@ -298,9 +301,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", "boolean_field", "boolean", JDBCType.BOOLEAN, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", "boolean_field", "boolean", JDBCType.BOOLEAN, Integer.MAX_VALUE)));
|
||||
// adding the boolean as a String here because parsing the response will yield a "true"/"false" String
|
||||
expected.put("rows", singletonList(singletonList(asString ? String.valueOf(booleanField) : booleanField)));
|
||||
assertResponse(expected, runSql(query));
|
||||
|
@ -328,9 +329,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", "ip_field", "ip", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", "ip_field", "ip", JDBCType.VARCHAR, Integer.MAX_VALUE)));
|
||||
expected.put("rows", singletonList(singletonList(ipField)));
|
||||
assertResponse(expected, runSql(query));
|
||||
} else {
|
||||
|
@ -358,11 +357,14 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"keyword_field\":\"" + keyword + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", "keyword_field", "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "keyword_field_alias", "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "a.b.c.keyword_field_alias", "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
expected.put("rows", singletonList(Arrays.asList(keyword, keyword, keyword)));
|
||||
assertResponse(expected, runSql("SELECT keyword_field, keyword_field_alias, a.b.c.keyword_field_alias FROM test"));
|
||||
}
|
||||
|
@ -387,11 +389,14 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"text_field\":\"" + text + "\"}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", "text_field", "text", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "text_field_alias", "text", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "a.b.c.text_field_alias", "text", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
expected.put("rows", singletonList(Arrays.asList(text, null, null)));
|
||||
assertResponse(expected, runSql("SELECT text_field, text_field_alias, a.b.c.text_field_alias FROM test"));
|
||||
}
|
||||
|
@ -416,11 +421,14 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"integer_field\":" + number + "}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", "integer_field", "integer", JDBCType.INTEGER, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "integer_field_alias", "integer", JDBCType.INTEGER, Integer.MAX_VALUE),
|
||||
columnInfo("plain", "a.b.c.integer_field_alias", "integer", JDBCType.INTEGER, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
expected.put("rows", singletonList(Arrays.asList(number, null, number)));
|
||||
assertResponse(expected, runSql("SELECT integer_field, integer_field_alias, a.b.c.integer_field_alias FROM test"));
|
||||
}
|
||||
|
@ -462,10 +470,13 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", fieldName, "text", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", subFieldName, "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
expected.put("rows", singletonList(Arrays.asList(text, ignoreAbove ? null : text)));
|
||||
assertResponse(expected, runSql(query));
|
||||
|
@ -474,9 +485,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
// even if the _source is disabled, selecting only the keyword sub-field should work as expected
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
columnInfo("plain", subFieldName, "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
expected.put("columns", Arrays.asList(columnInfo("plain", subFieldName, "keyword", JDBCType.VARCHAR, Integer.MAX_VALUE)));
|
||||
|
||||
expected.put("rows", singletonList(singletonList(ignoreAbove ? null : text)));
|
||||
assertResponse(expected, runSql("SELECT text_field.keyword_subfield FROM test"));
|
||||
|
@ -502,7 +511,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
Object actualValue = number;
|
||||
String fieldName = "text_field";
|
||||
String subFieldName = "text_field.integer_subfield";
|
||||
String query = "SELECT " + fieldName + "," + subFieldName +" FROM test";
|
||||
String query = "SELECT " + fieldName + "," + subFieldName + " FROM test";
|
||||
|
||||
Map<String, Object> indexProps = new HashMap<>(1);
|
||||
indexProps.put("_source", enableSource);
|
||||
|
@ -522,10 +531,13 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", fieldName, "text", JDBCType.VARCHAR, Integer.MAX_VALUE),
|
||||
columnInfo("plain", subFieldName, "integer", JDBCType.INTEGER, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
if (ignoreMalformed) {
|
||||
expected.put("rows", singletonList(Arrays.asList("foo", null)));
|
||||
} else {
|
||||
|
@ -559,7 +571,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
Object actualValue = number;
|
||||
String fieldName = "integer_field";
|
||||
String subFieldName = "integer_field." + (isKeyword ? "keyword_subfield" : "text_subfield");
|
||||
String query = "SELECT " + fieldName + "," + subFieldName +" FROM test";
|
||||
String query = "SELECT " + fieldName + "," + subFieldName + " FROM test";
|
||||
|
||||
Map<String, Object> indexProps = new HashMap<>(1);
|
||||
indexProps.put("_source", enableSource);
|
||||
|
@ -574,16 +586,24 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
actualValue = "foo";
|
||||
}
|
||||
|
||||
createIndexWithFieldTypeAndSubFields("integer", fieldProps, explicitSourceSetting ? indexProps : null, null,
|
||||
isKeyword ? "keyword" : "text");
|
||||
createIndexWithFieldTypeAndSubFields(
|
||||
"integer",
|
||||
fieldProps,
|
||||
explicitSourceSetting ? indexProps : null,
|
||||
null,
|
||||
isKeyword ? "keyword" : "text"
|
||||
);
|
||||
index("{\"" + fieldName + "\":\"" + actualValue + "\"}");
|
||||
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", fieldName, "integer", JDBCType.INTEGER, Integer.MAX_VALUE),
|
||||
columnInfo("plain", subFieldName, isKeyword ? "keyword" : "text", JDBCType.VARCHAR, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
if (ignoreMalformed) {
|
||||
expected.put("rows", singletonList(Arrays.asList(null, "foo")));
|
||||
} else {
|
||||
|
@ -655,10 +675,13 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"" + fieldName + "\":" + number + "}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", fieldName, "integer", JDBCType.INTEGER, Integer.MAX_VALUE),
|
||||
columnInfo("plain", subFieldName, "byte", JDBCType.TINYINT, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
if (isByte || subFieldIgnoreMalformed) {
|
||||
expected.put("rows", singletonList(Arrays.asList(number, isByte ? number : null)));
|
||||
|
@ -721,10 +744,13 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
index("{\"" + fieldName + "\":" + number + "}");
|
||||
|
||||
Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("columns", Arrays.asList(
|
||||
expected.put(
|
||||
"columns",
|
||||
Arrays.asList(
|
||||
columnInfo("plain", fieldName, "byte", JDBCType.TINYINT, Integer.MAX_VALUE),
|
||||
columnInfo("plain", subFieldName, "integer", JDBCType.INTEGER, Integer.MAX_VALUE)
|
||||
));
|
||||
)
|
||||
);
|
||||
if (explicitSourceSetting == false || enableSource) {
|
||||
if (isByte || rootIgnoreMalformed) {
|
||||
expected.put("rows", singletonList(Arrays.asList(isByte ? number : null, number)));
|
||||
|
@ -749,42 +775,59 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
}, containsString("unable to fetch fields from _source field: _source is disabled in the mappings for index [test]"));
|
||||
}
|
||||
|
||||
private void createIndexWithFieldTypeAndAlias(String type, Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps) throws IOException {
|
||||
private void createIndexWithFieldTypeAndAlias(String type, Map<String, Map<String, Object>> fieldProps, Map<String, Object> indexProps)
|
||||
throws IOException {
|
||||
createIndexWithFieldTypeAndProperties(type, fieldProps, indexProps, true, false, null);
|
||||
}
|
||||
|
||||
private void createIndexWithFieldTypeAndProperties(String type, Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps) throws IOException {
|
||||
private void createIndexWithFieldTypeAndProperties(
|
||||
String type,
|
||||
Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps
|
||||
) throws IOException {
|
||||
createIndexWithFieldTypeAndProperties(type, fieldProps, indexProps, false, false, null);
|
||||
}
|
||||
|
||||
private void createIndexWithFieldTypeAndSubFields(String type, Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps, Map<String, Map<String, Object>> subFieldsProps,
|
||||
String... subFieldsTypes) throws IOException {
|
||||
private void createIndexWithFieldTypeAndSubFields(
|
||||
String type,
|
||||
Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps,
|
||||
Map<String, Map<String, Object>> subFieldsProps,
|
||||
String... subFieldsTypes
|
||||
) throws IOException {
|
||||
createIndexWithFieldTypeAndProperties(type, fieldProps, indexProps, false, true, subFieldsProps, subFieldsTypes);
|
||||
}
|
||||
|
||||
private void createIndexWithFieldTypeAndProperties(String type, Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps, boolean withAlias, boolean withSubFields, Map<String, Map<String, Object>> subFieldsProps,
|
||||
String... subFieldsTypes) throws IOException {
|
||||
private void createIndexWithFieldTypeAndProperties(
|
||||
String type,
|
||||
Map<String, Map<String, Object>> fieldProps,
|
||||
Map<String, Object> indexProps,
|
||||
boolean withAlias,
|
||||
boolean withSubFields,
|
||||
Map<String, Map<String, Object>> subFieldsProps,
|
||||
String... subFieldsTypes
|
||||
) throws IOException {
|
||||
Request request = new Request("PUT", "/test");
|
||||
XContentBuilder index = JsonXContent.contentBuilder().prettyPrint().startObject();
|
||||
|
||||
index.startObject("mappings"); {
|
||||
index.startObject("mappings");
|
||||
{
|
||||
if (indexProps != null) {
|
||||
for (Entry<String, Object> prop : indexProps.entrySet()) {
|
||||
if (prop.getValue() instanceof Boolean) {
|
||||
index.startObject(prop.getKey()); {
|
||||
index.startObject(prop.getKey());
|
||||
{
|
||||
index.field("enabled", prop.getValue());
|
||||
}
|
||||
index.endObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
index.startObject("properties"); {
|
||||
index.startObject("properties");
|
||||
{
|
||||
String fieldName = type + "_field";
|
||||
index.startObject(fieldName); {
|
||||
index.startObject(fieldName);
|
||||
{
|
||||
index.field("type", type);
|
||||
if (fieldProps != null && fieldProps.containsKey(fieldName)) {
|
||||
for (Entry<String, Object> prop : fieldProps.get(fieldName).entrySet()) {
|
||||
|
@ -794,18 +837,18 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (withSubFields) {
|
||||
index.startObject("fields");
|
||||
for (String subFieldType : subFieldsTypes) {
|
||||
String subFieldName = subFieldType + "_subfield";
|
||||
String fullSubFieldName = fieldName + "." + subFieldName;
|
||||
index.startObject(subFieldName);
|
||||
index.field("type", subFieldType);
|
||||
if (subFieldsProps != null && subFieldsProps.containsKey(fullSubFieldName)) {
|
||||
for (Entry<String, Object> prop : subFieldsProps.get(fullSubFieldName).entrySet()) {
|
||||
index.field(prop.getKey(), prop.getValue());
|
||||
}
|
||||
}
|
||||
index.endObject();
|
||||
for (String subFieldType : subFieldsTypes) {
|
||||
String subFieldName = subFieldType + "_subfield";
|
||||
String fullSubFieldName = fieldName + "." + subFieldName;
|
||||
index.startObject(subFieldName);
|
||||
index.field("type", subFieldType);
|
||||
if (subFieldsProps != null && subFieldsProps.containsKey(fullSubFieldName)) {
|
||||
for (Entry<String, Object> prop : subFieldsProps.get(fullSubFieldName).entrySet()) {
|
||||
index.field(prop.getKey(), prop.getValue());
|
||||
}
|
||||
}
|
||||
index.endObject();
|
||||
}
|
||||
index.endObject();
|
||||
}
|
||||
}
|
||||
|
@ -813,12 +856,14 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
|
||||
if (withAlias) {
|
||||
// create two aliases - one within a hierarchy, the other just a simple field w/o hierarchy
|
||||
index.startObject(fieldName + "_alias"); {
|
||||
index.startObject(fieldName + "_alias");
|
||||
{
|
||||
index.field("type", "alias");
|
||||
index.field("path", fieldName);
|
||||
}
|
||||
index.endObject();
|
||||
index.startObject("a.b.c." + fieldName + "_alias"); {
|
||||
index.startObject("a.b.c." + fieldName + "_alias");
|
||||
{
|
||||
index.field("type", "alias");
|
||||
index.field("path", fieldName);
|
||||
}
|
||||
|
@ -851,7 +896,7 @@ public abstract class FieldExtractorTestCase extends BaseRestSqlTestCase {
|
|||
}
|
||||
|
||||
private JDBCType jdbcTypeFor(String esType) {
|
||||
switch(esType) {
|
||||
switch (esType) {
|
||||
case "long":
|
||||
return JDBCType.BIGINT;
|
||||
case "integer":
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue