Permalink
Browse files

XWIKI-7741 : Implement a QueryFilter allowing to select distinct docu…

…ments in XWQL short queries
  • Loading branch information...
1 parent a813b59 commit a053d3c22d0faab2ef21de417b3d835f097bf565 @jvdrean jvdrean committed Apr 17, 2012
@@ -0,0 +1,81 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This 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 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.xwiki.query.internal;
+
+import org.slf4j.Logger;
+import org.xwiki.component.annotation.Component;
+import org.xwiki.query.Query;
+import org.xwiki.query.QueryFilter;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+/**
+ * Query filter making sure unique results are retrieved by a {@link org.xwiki.query.Query}.
+ *
+ * @version $Id$
+ * @since 4.1M1
+ */
+@Component
+@Named("unique")
+@Singleton
+public class UniqueDocumentFilter implements QueryFilter
+{
+ /**
+ * Select part to find and replace if it is present.
+ */
+ private static final String SELECT_CLAUSE = " doc.fullname from xwikidocument ";
+
+ /**
+ * Used to log debug information.
+ */
+ @Inject
+ private Logger logger;
+
+ /**
+ * @param statement statement to filter.
+ * @return true if the filter can be applied to the passed statement, false otherwise.
+ */
+ private boolean isFilterable(String statement)
+ {
+ return statement.indexOf(SELECT_CLAUSE) > -1 && statement.indexOf("distinct doc.fullname") == -1;
+ }
+
+ @Override
+ public String filterStatement(String statement, String language)
+ {
+ String result = statement.trim();
+ String lowerStatement = result.toLowerCase();
+ String original = result;
+
+ if (Query.HQL.equals(language) && isFilterable(lowerStatement)) {
+ int idx = lowerStatement.indexOf(SELECT_CLAUSE);
+ result = result.substring(0, idx) + " distinct doc.fullName from XWikiDocument "
+ + result.substring(idx + 33);
+ }
+
+ if (!original.equals(result)) {
+ logger.debug("Query [{}] has been transformed into [{}]", original, result);
+ }
+
+ return result;
+ }
+}
@@ -3,4 +3,5 @@ org.xwiki.query.internal.SecureQueryManager
org.xwiki.query.internal.DefaultQueryExecutorManager
org.xwiki.query.internal.SecureQueryExecutorManager
org.xwiki.query.internal.QueryManagerScriptService
-org.xwiki.query.internal.HiddenDocumentFilter
+org.xwiki.query.internal.HiddenDocumentFilter
+org.xwiki.query.internal.UniqueDocumentFilter
@@ -0,0 +1,76 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This 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 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.xwiki.query.internal;
+
+import org.jmock.Expectations;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.xwiki.query.Query;
+import org.xwiki.test.AbstractMockingComponentTestCase;
+import org.xwiki.test.annotation.MockingRequirement;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link org.xwiki.query.internal.UniqueDocumentFilter}
+ *
+ * @version $Id$
+ */
+public class UniqueDocumentFilterTest extends AbstractMockingComponentTestCase
+{
+ @MockingRequirement
+ private UniqueDocumentFilter filter;
+
+ @Override
+ public void configure() throws Exception
+ {
+ getMockery().checking(new Expectations() {{
+ ignoring(any(Logger.class)).method("debug");
+ }});
+ }
+
+ @Test
+ public void filterSelectStatement() throws Exception
+ {
+ assertEquals("select distinct doc.fullName from XWikiDocument doc",
+ filter.filterStatement("select doc.fullName from XWikiDocument doc", Query.HQL));
+ }
+
+ @Test
+ public void filterSelectWithAsStatement() throws Exception
@vmassol

vmassol Apr 17, 2012

Owner

I don't understand why your testing this since in the code under test I don't see any test on "as".

@jvdrean

jvdrean Apr 18, 2012

Contributor

It is testing that the distinct will be added even when using the "XWikiDocument as doc" notation and not only with "XWikiDocument doc"

+ {
+ assertEquals("select distinct doc.fullName from XWikiDocument as doc",
+ filter.filterStatement("select doc.fullName from XWikiDocument as doc", Query.HQL));
+ }
+
+ @Test
+ public void filterMismatchingSelectStatement() throws Exception
@vmassol

vmassol Apr 17, 2012

Owner

I don't understand what 'mismatching' means here. It seems the result is not changed by the filter, why not?

@jvdrean

jvdrean Apr 18, 2012

Contributor

The filter looks for 'doc.fullName from XWikiDocument doc', here the document alias is 'mydoc'.
We might want to transform the query no matter what's the XWikiDocument alias but we should also remember that the filter is only required for short queries. IE. when the user hasn't written the select part of the query, in this case our code always inserts "select doc.fullName from XWikiDocument doc".

+ {
+ assertEquals("select mydoc.fullName from XWikiDocument mydoc",
+ filter.filterStatement("select mydoc.fullName from XWikiDocument mydoc", Query.HQL));
+ }
+
+ @Test
@vmassol

vmassol Apr 17, 2012

Owner

indentation issue

+ public void filterSelectDistinctStatement() throws Exception
@vmassol

vmassol Apr 17, 2012

Owner

A better name would be: filterStatementWhenStatementAlreadyContainsDistinct()

@jvdrean

jvdrean Apr 18, 2012

Contributor

indeed

+ {
+ assertEquals("select distinct doc.fullName from XWikiDocument doc",
+ filter.filterStatement("select distinct doc.fullName from XWikiDocument doc", Query.HQL));
+ }
+}

0 comments on commit a053d3c

Please sign in to comment.