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: + * + * + * + * @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