/*
* (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.storage.sql.jdbc.dialect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.hamcrest.core.StringContains;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.ecm.core.storage.FulltextQueryAnalyzer;
import org.nuxeo.ecm.core.storage.FulltextQueryAnalyzer.FulltextQuery;
import org.nuxeo.ecm.core.storage.FulltextQueryAnalyzer.Op;
import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor;
@RunWith(JMock.class)
public class TestDialectQuerySyntax {
Mockery jmcontext = new JUnit4Mockery();
public DatabaseMetaData metadata;
public RepositoryDescriptor repositoryDescriptor;
public Dialect dialect;
@Before
public void setUp() throws SQLException {
metadata = getMockDatabaseMetaData();
repositoryDescriptor = new RepositoryDescriptor();
}
protected DatabaseMetaData getMockDatabaseMetaData() throws SQLException {
final DatabaseMetaData m = jmcontext.mock(DatabaseMetaData.class);
jmcontext.checking(new Expectations() {
{
allowing(m).storesUpperCaseIdentifiers();
will(returnValue(false));
allowing(m).getDatabaseMajorVersion();
will(returnValue(9));
allowing(m).getDatabaseMinorVersion();
will(returnValue(0));
allowing(m).getColumns(with(any(String.class)), with(any(String.class)), with(any(String.class)),
with(any(String.class)));
will(returnValue(getMockEmptyResultSet()));
allowing(m).getConnection();
will(returnValue(getMockConnection()));
}
});
return m;
}
protected ResultSet getMockEmptyResultSet() throws SQLException {
final ResultSet m = jmcontext.mock(ResultSet.class, "empty");
jmcontext.checking(new Expectations() {
{
allowing(m).next();
will(returnValue(false));
}
});
return m;
}
protected Connection getMockConnection() throws SQLException {
final Connection m = jmcontext.mock(Connection.class);
jmcontext.checking(new Expectations() {
{
allowing(m).createStatement();
will(returnValue(getMockStatement()));
}
});
return m;
}
protected Statement getMockStatement() throws SQLException {
final Statement m = jmcontext.mock(Statement.class);
jmcontext.checking(new Expectations() {
{
allowing(m).executeQuery(with(new StringContains("is_read_committed_snapshot_on")));
will(returnValue(getMockResultSetReturningInt(1)));
allowing(m).executeQuery(with(new StringContains("EngineEdition")));
will(returnValue(getMockResultSetReturningInt(2)));
allowing(m).close();
will(returnValue(null));
}
});
return m;
}
protected ResultSet getMockResultSetReturningInt(final int value) throws SQLException {
final ResultSet m = jmcontext.mock(ResultSet.class, "ResultSetReturningInt:" + value);
jmcontext.checking(new Expectations() {
{
allowing(m).next();
will(returnValue(true));
allowing(m).getInt(with(any(Integer.class)));
will(returnValue(value));
allowing(m).close();
will(returnValue(null));
}
});
return m;
}
protected static void dumpFulltextQuery(FulltextQuery ft, StringBuilder buf) {
if (ft.op == Op.AND || ft.op == Op.OR) {
assertNull(ft.word);
buf.append('[');
for (int i = 0; i < ft.terms.size(); i++) {
if (i != 0) {
buf.append(' ');
buf.append(ft.op.name());
buf.append(' ');
}
dumpFulltextQuery(ft.terms.get(i), buf);
}
buf.append(']');
return;
} else {
assertNull(ft.terms);
if (ft.op == Op.NOTWORD) {
buf.append('~');
}
boolean isPhrase = ft.word.contains(" ");
if (isPhrase) {
buf.append('{');
}
buf.append(ft.word);
if (isPhrase) {
buf.append('}');
}
}
}
protected void assertPGPhraseBreak(String expected, String query) {
FulltextQuery ft = FulltextQueryAnalyzer.analyzeFulltextQuery(query);
FulltextQuery broken = DialectPostgreSQL.breakPhrases(ft);
StringBuilder buf = new StringBuilder();
dumpFulltextQuery(broken, buf);
assertEquals(expected, buf.toString());
}
protected void assertPGRemoveToplevelAndedWord(String expected, String query) {
FulltextQuery ft = FulltextQueryAnalyzer.analyzeFulltextQuery(query);
FulltextQuery simplified = DialectPostgreSQL.removeToplevelAndedWords(ft);
if (simplified == null) {
assertNull(expected);
} else {
StringBuilder buf = new StringBuilder();
dumpFulltextQuery(simplified, buf);
assertEquals(expected, buf.toString());
}
}
protected void assertPGLikeSql(String expected, String query) {
FulltextQuery ft = FulltextQueryAnalyzer.analyzeFulltextQuery(query);
StringBuilder buf = new StringBuilder();
DialectPostgreSQL.generateLikeSql(ft, buf);
assertEquals(expected, buf.toString());
}
protected void assertDialectFT(String expected, String query) {
assertEquals(expected, dialect.getDialectFulltextQuery(query));
}
@Test
public void testH2() throws Exception {
dialect = new DialectH2(metadata, repositoryDescriptor);
assertDialectFT("DONTMATCHANYTHINGFOREMPTYQUERY", "");
assertDialectFT("foo", "foo");
assertDialectFT("FOO", "FOO");
assertDialectFT("foo", "foo :");
assertDialectFT("foo", "foo :)");
assertDialectFT("foo", "foo -+-");
assertDialectFT("(foo AND bar)", "foo bar");
assertDialectFT("(foo NOT bar)", "foo -bar");
assertDialectFT("(bar NOT foo)", "-foo bar");
assertDialectFT("(foo OR bar)", "foo OR bar");
assertDialectFT("foo", "foo OR -bar");
assertDialectFT("((foo AND bar) OR baz)", "foo bar OR baz");
assertDialectFT("((bar NOT foo) OR baz)", "-foo bar OR baz");
assertDialectFT("((foo NOT bar) OR baz)", "foo -bar OR baz");
assertDialectFT("\"foo bar\"", "\"foo bar\"");
assertDialectFT("(\"foo bar\" AND baz)", "\"foo bar\" baz");
assertDialectFT("(\"foo bar\" OR baz)", "\"foo bar\" OR baz");
assertDialectFT("((\"foo bar\" AND baz) OR \"gee man\")", "\"foo bar\" baz OR \"gee man\"");
assertDialectFT("(\"foo bar\" NOT \"gee man\")", "\"foo bar\" -\"gee man\"");
assertDialectFT("foo*", "foo*");
assertDialectFT("(foo AND bar*)", "foo bar*");
}
@Test
public void testPostgreSQLPhraseBreak() throws Exception {
assertPGPhraseBreak("foo", "foo");
assertPGPhraseBreak("[foo AND bar]", "\"foo bar\"");
assertPGPhraseBreak("[foo AND bar AND baz]", "\"foo bar\" baz");
assertPGPhraseBreak("[foo AND bar AND baz]", "foo \"bar baz\"");
assertPGPhraseBreak("[[foo AND bar] OR baz]", "\"foo bar\" OR baz");
assertPGPhraseBreak("[[foo AND bar] OR [gee AND man]]", "\"foo bar\" OR \"gee man\"");
assertPGPhraseBreak("foo", "foo -\"bar baz\"");
assertPGPhraseBreak("foo", "foo OR -\"bar baz\"");
}
@Test
public void testPostgreSQLToplevelAndedWordRemoval() throws Exception {
assertPGRemoveToplevelAndedWord(null, "foo");
assertPGRemoveToplevelAndedWord(null, "foo bar");
assertPGRemoveToplevelAndedWord("{foo bar}", "\"foo bar\"");
assertPGRemoveToplevelAndedWord("{foo bar}", "\"foo bar\" baz");
assertPGRemoveToplevelAndedWord("{bar baz}", "foo \"bar baz\"");
assertPGRemoveToplevelAndedWord("[{foo bar} OR baz]", "\"foo bar\" OR baz");
assertPGRemoveToplevelAndedWord("[{foo bar} OR {gee man}]", "\"foo bar\" OR \"gee man\"");
assertPGRemoveToplevelAndedWord("~{bar baz}", "foo -\"bar baz\"");
assertPGRemoveToplevelAndedWord(null, "foo OR -\"bar baz\"");
}
@Test
public void testPostgreSQLLikeSql() throws Exception {
assertPGLikeSql("?? LIKE '% foo %'", "foo");
assertPGLikeSql("?? LIKE '% foo %'", "FOO");
assertPGLikeSql("?? LIKE '% caf\u00e9 %'", "CAF\u00c9");
assertPGLikeSql("(?? LIKE '% foo %' AND ?? LIKE '% bar %')", "foo bar");
assertPGLikeSql("?? LIKE '% foo bar %'", "\"foo bar\"");
assertPGLikeSql("(?? LIKE '% foo bar %' AND ?? LIKE '% baz %')", "\"foo bar\" baz");
assertPGLikeSql("(?? LIKE '% foo %' AND ?? LIKE '% bar baz %')", "foo \"bar baz\"");
assertPGLikeSql("(?? LIKE '% foo bar %' OR ?? LIKE '% baz %')", "\"foo bar\" OR baz");
assertPGLikeSql("(?? LIKE '% foo bar %' OR ?? LIKE '% gee man %')", "\"foo bar\" OR \"gee man\"");
assertPGLikeSql("(?? LIKE '% foo %' AND ?? NOT LIKE '% bar baz %')", "foo -\"bar baz\"");
assertPGLikeSql("?? LIKE '% foo %'", "foo OR -\"bar baz\"");
}
@Test
public void testPostgreSQL() throws Exception {
dialect = new DialectPostgreSQL(metadata, repositoryDescriptor);
assertDialectFT("", "-foo");
assertDialectFT("foo", "foo");
assertDialectFT("FOO", "FOO");
assertDialectFT("foo", "foo :");
assertDialectFT("foo", "foo :)");
assertDialectFT("foo", "foo -+-");
assertDialectFT("(foo & bar)", "foo bar ");
assertDialectFT("(foo & bar)", "foo & bar"); // compat
assertDialectFT("(foo & ! bar)", "foo -bar");
assertDialectFT("(bar & ! foo)", "-foo bar");
assertDialectFT("(foo | bar)", "foo OR bar");
assertDialectFT("foo", "foo OR -bar");
assertDialectFT("((foo & bar) | baz)", "foo bar OR baz");
assertDialectFT("((bar & ! foo) | baz)", "-foo bar OR baz");
assertDialectFT("((foo & ! bar) | baz)", "foo -bar OR baz");
assertDialectFT("(foo & bar) @#AND#@ ?? LIKE '% foo bar %'", "\"foo bar\"");
assertDialectFT("(foo & bar & baz) @#AND#@ ?? LIKE '% foo bar %'", "\"foo bar\" baz");
assertDialectFT("(foo & bar & baz) @#AND#@ (?? LIKE '% foo bar %' AND ?? NOT LIKE '% gee man %')",
"\"foo bar\" baz -\"gee man\"");
assertDialectFT("((foo & bar) | baz) @#AND#@ (?? LIKE '% foo bar %' OR ?? LIKE '% baz %')",
"\"foo bar\" OR baz");
assertDialectFT(
"((foo & bar & baz) | (gee & man)) @#AND#@ ((?? LIKE '% foo bar %' AND ?? LIKE '% baz %') OR ?? LIKE '% gee man %')",
"\"foo bar\" baz OR \"gee man\"");
assertDialectFT("(foo & bar) @#AND#@ (?? LIKE '% foo bar %' AND ?? NOT LIKE '% gee man %')",
"\"foo bar\" -\"gee man\"");
assertDialectFT("foo:*", "foo*");
assertDialectFT("(foo & bar:*)", "foo bar*");
assertDialectFT("(foo & bar:*) @#AND#@ ?? LIKE '% foo bar%'", "\"foo bar*\"");
}
@Test
public void testMySQL() throws Exception {
dialect = new DialectMySQL(metadata, repositoryDescriptor);
assertDialectFT("DONTMATCHANYTHINGFOREMPTYQUERY", "-foo");
assertDialectFT("foo", "foo");
assertDialectFT("FOO", "FOO");
assertDialectFT("foo", "foo :");
assertDialectFT("foo", "foo :)");
assertDialectFT("foo", "foo -+-");
assertDialectFT("(+foo +bar)", "foo bar");
assertDialectFT("(+foo -bar)", "foo -bar");
assertDialectFT("(+bar -foo)", "-foo bar");
assertDialectFT("(foo bar)", "foo OR bar");
assertDialectFT("foo", "foo OR -bar");
assertDialectFT("((+foo +bar) baz)", "foo bar OR baz");
assertDialectFT("((+bar -foo) baz)", "-foo bar OR baz");
assertDialectFT("((+foo -bar) baz)", "foo -bar OR baz");
assertDialectFT("\"foo bar\"", "\"foo bar\"");
assertDialectFT("(+\"foo bar\" +baz)", "\"foo bar\" baz");
assertDialectFT("(\"foo bar\" baz)", "\"foo bar\" OR baz");
assertDialectFT("((+\"foo bar\" +baz) \"gee man\")", "\"foo bar\" baz OR \"gee man\"");
assertDialectFT("(+\"foo bar\" -\"gee man\")", "\"foo bar\" -\"gee man\"");
assertDialectFT("foo*", "foo*");
assertDialectFT("(+foo +bar*)", "foo bar*");
}
@Test
public void testOracle() throws Exception {
dialect = new DialectOracle(metadata, repositoryDescriptor);
assertDialectFT("DONTMATCHANYTHINGFOREMPTYQUERY", "-foo");
assertDialectFT("{foo}", "foo");
assertDialectFT("{FOO}", "FOO");
assertDialectFT("{foo}", "foo :");
assertDialectFT("{foo}", "foo :)");
assertDialectFT("{foo}", "foo -+-");
assertDialectFT("{foo_bar}", "foo_bar");
assertDialectFT("({foo} AND {bar})", "foo bar");
assertDialectFT("({foo} NOT {bar})", "foo -bar");
assertDialectFT("({bar} NOT {foo})", "-foo bar");
assertDialectFT("({foo} OR {bar})", "foo OR bar");
assertDialectFT("{foo}", "foo OR -bar");
assertDialectFT("(({foo} AND {bar}) OR {baz})", "foo bar OR baz");
assertDialectFT("(({bar} NOT {foo}) OR {baz})", "-foo bar OR baz");
assertDialectFT("(({foo} NOT {bar}) OR {baz})", "foo -bar OR baz");
assertDialectFT("{foo} {bar}", "\"foo bar\"");
assertDialectFT("({foo} {bar} AND {baz})", "\"foo bar\" baz");
assertDialectFT("({foo} {bar} OR {baz})", "\"foo bar\" OR baz");
assertDialectFT("(({foo} {bar} AND {baz}) OR {gee} {man})", "\"foo bar\" baz OR \"gee man\"");
assertDialectFT("({foo} {bar} NOT {gee} {man})", "\"foo bar\" -\"gee man\"");
assertDialectFT("foo%", "foo*");
assertDialectFT("foo%", "foo%");
assertDialectFT("$foo", "$foo");
assertDialectFT("${foo}", "${foo}");
assertDialectFT("{fooBAR}", "{fooBAR}");
assertDialectFT("({foo} AND bar%)", "foo bar*");
assertDialectFT("{foo} bar%", "\"foo bar*\"");
// reserved words
assertDialectFT("({word} AND {and})", "word and");
assertDialectFT("{word} {and}", "\"word and\"");
}
@Test
public void testSQLServer() throws Exception {
dialect = new DialectSQLServer(metadata, repositoryDescriptor);
assertDialectFT("DONTMATCHANYTHINGFOREMPTYQUERY", "-foo");
assertDialectFT("\"foo\"", "foo");
assertDialectFT("\"FOO\"", "FOO");
assertDialectFT("\"foo\"", "foo :");
assertDialectFT("\"foo\"", "foo :)");
assertDialectFT("\"foo\"", "foo -+-");
assertDialectFT("(\"foo\" AND \"bar\")", "foo bar");
assertDialectFT("(\"foo\" AND NOT \"bar\")", "foo -bar");
assertDialectFT("(\"bar\" AND NOT \"foo\")", "-foo bar");
assertDialectFT("(\"foo\" OR \"bar\")", "foo OR bar");
assertDialectFT("\"foo\"", "foo OR -bar");
assertDialectFT("((\"foo\" AND \"bar\") OR \"baz\")", "foo bar OR baz");
assertDialectFT("((\"bar\" AND NOT \"foo\") OR \"baz\")", "-foo bar OR baz");
assertDialectFT("((\"foo\" AND NOT \"bar\") OR \"baz\")", "foo -bar OR baz");
assertDialectFT("\"foo bar\"", "\"foo bar\"");
assertDialectFT("(\"foo bar\" AND \"baz\")", "\"foo bar\" baz");
assertDialectFT("(\"foo bar\" OR \"baz\")", "\"foo bar\" OR baz");
assertDialectFT("((\"foo bar\" AND \"baz\") OR \"gee man\")", "\"foo bar\" \"baz\" OR \"gee man\"");
assertDialectFT("(\"foo bar\" AND NOT \"gee man\")", "\"foo bar\" -\"gee man\"");
assertDialectFT("\"foo*\"", "foo*");
assertDialectFT("(\"foo\" AND \"bar*\")", "foo bar*");
assertDialectFT("\"foo bar*\"", "\"foo bar*\"");
}
}