diff --git a/e2e-test/java/org/alfresco/rest/search/AbstractSearchTest.java b/e2e-test/java/org/alfresco/rest/search/AbstractSearchTest.java
index 1d3a31b99..1b63456e7 100644
--- a/e2e-test/java/org/alfresco/rest/search/AbstractSearchTest.java
+++ b/e2e-test/java/org/alfresco/rest/search/AbstractSearchTest.java
@@ -147,7 +147,20 @@ public class AbstractSearchTest extends RestTest
throw new RuntimeException(exception);
}
}
-
+
+ /**
+ * Executes an SQL Query using "solr" as output format.
+ *
+ * @param sql the SQL statement.
+ */
+ protected RestResponse executeSqlAsSolr(String sql) throws Exception
+ {
+ SearchSqlRequest sqlRequest = new SearchSqlRequest();
+ sqlRequest.setSql(sql);
+ sqlRequest.setFormat("solr");
+ return searchSql(sqlRequest);
+ }
+
protected SearchRequest createQuery(String term)
{
SearchRequest query = new SearchRequest();
diff --git a/e2e-test/java/org/alfresco/rest/search/sql/SearchSQLWithQuotedIdentifiers.java b/e2e-test/java/org/alfresco/rest/search/sql/SearchSQLWithQuotedIdentifiers.java
new file mode 100644
index 000000000..dca6d1793
--- /dev/null
+++ b/e2e-test/java/org/alfresco/rest/search/sql/SearchSQLWithQuotedIdentifiers.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.search.sql;
+
+import java.util.UUID;
+
+import org.alfresco.rest.RestTest;
+import org.alfresco.rest.search.AbstractSearchTest;
+import org.alfresco.rest.search.SearchSqlRequest;
+import org.alfresco.utility.constants.UserRole;
+import org.alfresco.utility.data.CustomObjectTypeProperties;
+import org.alfresco.utility.model.*;
+import org.alfresco.utility.report.Bug;
+import org.hamcrest.Matchers;
+import org.springframework.http.HttpStatus;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for /sql end point Search API when a custom model contains identifiers that need to be quoted in queries.
+ * While it seems there is not an ANSI standard for naming database objects, on the other side the most popular
+ * databases don't like columns starting with a number.
+ * In those cases, the column identifier need to be quoted. How to quote a given object identifier (a column name, in
+ * this case) depends on the specific database lexicon: Oracle uses double quotes ("), MySQL uses back quotes(`).
+ *
+ * At time of writing, the lexicon used by the /sql endpoint is MySql so queries that use column names starting with
+ * a number need to be quoted with back quotes.
+ *
+ */
+public class SearchSQLWithQuotedIdentifiers extends AbstractSearchTest
+{
+
+ private String songName;
+ private String genre;
+ private String coProducer;
+
+ private String artistName;
+ private String voiceType;
+
+ private String bassistName;
+ private String drummerName;
+ private String saxophonistName;
+
+ private FileModel file5;
+
+ /**
+ * Setup fixture for this test case.
+ * Overrides the superlayer method because the data preparation requires a bit different preconditions.
+ * The method uses a transient test site created in the {@link RestTest#checkServerHealth()} and on top of that:
+ *
+ *
+ * - It adds a user which is added to the site as contributor
+ * -
+ * It deploys a custom model which declares a set of prefixes composed by a combination of digits, hyphens
+ * and underscores. Those prefixes are associated with four entities (song, artist, bassist, drummer and
+ * sax) with their corresponding attributes.
+ *
+ * -
+ * It creates a folder with 5 files associated with the types declared in the model.
+ * That allows those files to have a value for the properties/attributes included in the model definition.
+ *
+ *
+ *
+ * @see RestTest#checkServerHealth()
+ * @throws Exception hopefully never, otherwise the test fails.
+ */
+ @BeforeClass(alwaysRun = true)
+ public void dataPreparation() throws Exception
+ {
+ songName = "The Dry Cleaner from Des Moines ";
+ genre = "Jazz, vocal jazz ";
+ coProducer = "Roberta Joan Mitchell ";
+
+ artistName = "Joni Mitchell " + UUID.randomUUID();
+ voiceType = "Blue Mezzo (1965-1984) / Cloudy Contralto (1985-present) ";
+
+ bassistName = "Jaco Pastorius";
+ drummerName = "Peter Erskine";
+ saxophonistName = "Wayne Shorter";
+
+ userModel = dataUser.createRandomTestUser();
+
+ dataContent.usingAdmin().deployContentModel("models/SEARCH-1063.xml");
+
+ dataUser.addUserToSite(userModel, testSite, UserRole.SiteContributor);
+
+ FolderModel testFolder = dataContent.usingSite(testSite).usingUser(userModel).createFolder();
+
+ file = FileModel.getRandomFileModel(FileType.TEXT_PLAIN);
+ file2 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN);
+ file3 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN);
+ file4 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN);
+ file5 = FileModel.getRandomFileModel(FileType.TEXT_PLAIN);
+
+ dataContent.usingUser(userModel)
+ .usingResource(testFolder)
+ .createCustomContent(
+ file,
+ "D:1:song",
+ new CustomObjectTypeProperties()
+ .addProperty("1:name", songName)
+ .addProperty("1:genre", genre)
+ .addProperty("1:co-producer", coProducer));
+
+ dataContent.usingUser(userModel)
+ .usingResource(testFolder)
+ .createCustomContent(
+ file2,
+ "D:123:artist",
+ new CustomObjectTypeProperties()
+ .addProperty("123:name", artistName)
+ .addProperty("123:voice_type", voiceType));
+
+ dataContent.usingUser(userModel)
+ .usingResource(testFolder)
+ .createCustomContent(
+ file3,
+ "D:1_2_3:bassist",
+ new CustomObjectTypeProperties()
+ .addProperty("1_2_3:name", bassistName));
+
+ dataContent.usingUser(userModel)
+ .usingResource(testFolder)
+ .createCustomContent(
+ file4,
+ "D:1-2-3:drummer",
+ new CustomObjectTypeProperties()
+ .addProperty("1-2-3:name", drummerName));
+
+ ContentModel content =
+ dataContent.usingUser(userModel)
+ .usingResource(testFolder)
+ .createCustomContent(
+ file5,
+ "D:1-2_3:saxophonist",
+ new CustomObjectTypeProperties()
+ .addProperty("1-2_3:name", saxophonistName));
+
+ waitForIndexing(content.getName(), true);
+ }
+
+ @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1})
+ @Bug(id = "SEARCH-1063")
+ public void prefixIsComposedByOneNumber() throws Exception
+ {
+ executeSqlAsSolr("select cm_name, `1_name`, `1_genre`, `1_co-producer` from alfresco where TYPE='1:song' and SITE='" + testSite.getId() + "'");
+
+ restClient.assertStatusCodeIs(HttpStatus.OK);
+ restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file.getName()));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1_name", Matchers.equalTo(songName));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1_genre", Matchers.equalTo(genre));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1_co-producer", Matchers.equalTo(coProducer));
+ }
+
+ @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1})
+ @Bug(id = "SEARCH-1063")
+ public void prefixIsComposedByMultipleNumbers() throws Exception
+ {
+ executeSqlAsSolr("select cm_name, `123_name`, `123_voice_type` from alfresco where TYPE='123:artist' and SITE='" + testSite.getId() + "'");
+
+ restClient.assertStatusCodeIs(HttpStatus.OK);
+ restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file2.getName()));
+ restClient.onResponse().assertThat().body("result-set.docs[0].123_name", Matchers.equalTo(artistName));
+ restClient.onResponse().assertThat().body("result-set.docs[0].123_voice_type", Matchers.equalTo(voiceType));
+ }
+
+ @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1})
+ @Bug(id = "SEARCH-1063")
+ public void prefixIncludesUnderscore() throws Exception
+ {
+ executeSqlAsSolr("select cm_name, `1_2_3_name` from alfresco where TYPE='1_2_3:bassist' and SITE='" + testSite.getId() + "'");
+
+ restClient.assertStatusCodeIs(HttpStatus.OK);
+ restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file3.getName()));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1_2_3_name", Matchers.equalTo(bassistName));
+ }
+
+ @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1})
+ @Bug(id = "SEARCH-1063")
+ public void prefixIncludesHyphen() throws Exception
+ {
+ executeSqlAsSolr("select cm_name, `1-2-3_name` from alfresco where TYPE='1-2-3:drummer' and SITE='" + testSite.getId() + "'");
+
+ restClient.assertStatusCodeIs(HttpStatus.OK);
+ restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file4.getName()));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1-2-3_name", Matchers.equalTo(drummerName));
+ }
+
+ @Test(groups={TestGroup.SEARCH, TestGroup.REST_API, TestGroup.ASS_1})
+ @Bug(id = "SEARCH-1063")
+ public void prefixIncludesHyphenAndUnderscore() throws Exception
+ {
+ executeSqlAsSolr("select cm_name, `1-2_3_name` from alfresco where TYPE='1-2_3:saxophonist' and SITE='" + testSite.getId() + "'");
+
+ restClient.assertStatusCodeIs(HttpStatus.OK);
+ restClient.onResponse().assertThat().body("result-set.docs[0].cm_name", Matchers.equalTo(file5.getName()));
+ restClient.onResponse().assertThat().body("result-set.docs[0].1-2_3_name", Matchers.equalTo(saxophonistName));
+ }
+}
\ No newline at end of file
diff --git a/e2e-test/resources/models/SEARCH-1063.xml b/e2e-test/resources/models/SEARCH-1063.xml
new file mode 100755
index 000000000..816afd2bc
--- /dev/null
+++ b/e2e-test/resources/models/SEARCH-1063.xml
@@ -0,0 +1,159 @@
+
+
+ Administrator admin user
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ song
+ cm:content
+
+
+ Name
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+ Genre
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+ Producer
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+
+
+
+
+
+ Artist
+ cm:content
+
+
+ Name
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+ Voice Type
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+
+
+
+
+
+
+ Bassist
+ cm:content
+
+
+ Name
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+
+
+
+
+
+ Drummer
+ cm:content
+
+
+ Name
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+
+
+
+
+
+ Sax
+ cm:content
+
+
+ Name
+ d:text
+ false
+
+ true
+ BOTH
+ false
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file