/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb;
import java.io.IOException;
import junit.framework.TestCase;
import org.voltdb.VoltDB.Configuration;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcCallException;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.utils.MiscUtils;
public class TestLikeQueries extends TestCase {
static class LikeTest
{
String pattern;
int matches;
boolean crashes;
boolean addNot = false;
String escape = null;
public LikeTest(String pattern, int matches) {
this.pattern = pattern;
this.matches = matches;
this.crashes = false;
}
public LikeTest(String pattern, int matches, boolean crashes, boolean addNot, String escape) {
this.pattern = pattern;
this.matches = matches;
this.crashes = crashes;
this.addNot = addNot;
this.escape = escape;
}
public String getClause() {
String not = (this.addNot ? "NOT " : "");
String escape = (this.escape != null ? String.format(" ESCAPE '%s'", this.escape) : "");
String clause = String.format("%sLIKE '%s'%s", not, this.pattern, escape);
return clause;
}
}
static class NotLikeTest extends LikeTest {
public NotLikeTest(String pattern, int matches) {
super(pattern, matches, false, true, null);
}
}
static class EscapeLikeTest extends LikeTest {
public EscapeLikeTest(String pattern, int matches, String escape) {
super(pattern, matches, false, false, escape);
}
}
static class UnsupportedLikeTest extends LikeTest {
public UnsupportedLikeTest(String pattern, int matches) {
super(pattern, matches, true, false, null);
}
}
static class UnsupportedEscapeLikeTest extends LikeTest {
public UnsupportedEscapeLikeTest(String pattern, int matches, String escape) {
super(pattern, matches, true, false, escape);
}
}
static class LikeTestData {
public final String val;
public final String pat;
public LikeTestData(String val, String pat) {
this.val = val;
this.pat = pat;
}
}
public static final String schema =
"create table STRINGS (" +
"ID int default 0 not null, " +
"VAL varchar(32) default null," +
"PAT varchar(32) default null," +
"PRIMARY KEY(ID));";
static final LikeTestData[] rowData = {
new LikeTestData("aaaaaaa", "aaa%"),
new LikeTestData("abcccc%", "abc%"),
new LikeTestData("abcdefg", "abcdefg"),
new LikeTestData("âxxxéyy", "âxxx%"),
new LikeTestData("â🀲x一xxéyyÔ±", "â🀲x一%"),
new LikeTestData("â🀲x", "â🀲%"),
};
static final LikeTest[] tests = new LikeTest[] {
// Patterns that pass (currently supported)
new LikeTest("aaa%", 1),
new LikeTest("abc%", 2),
new LikeTest("AbC%", 0),
new LikeTest("zzz%", 0),
new LikeTest("%", rowData.length),
new LikeTest("_%", rowData.length),
new LikeTest("a%", 3),
new LikeTest("âxxx%", 1),
new LikeTest("aaaaaaa", 1),
new LikeTest("aaa", 0),
new LikeTest("abcdef_", 1),
new LikeTest("ab_d_fg", 1),
new LikeTest("%defg", 1),
new LikeTest("%de%", 1),
new LikeTest("â🀲x", 1),
new LikeTest("â🀲x一xxéyyÔ±", 1),
new LikeTest("â🀲x_xxéyyÔ±", 1),
new LikeTest("â🀲x一xxéyy_", 1),
new LikeTest("â🀲x一xéyyÔ±", 0),
new NotLikeTest("aaa%", rowData.length - 1),
new EscapeLikeTest("ââ🀲x一xxéyyÔ±", 1, "â"),
new EscapeLikeTest("abccccâ%", 1, "â"),
new EscapeLikeTest("abcccc|%", 1, "|"),
new EscapeLikeTest("abc%", 2, "|"),
new EscapeLikeTest("aaa", 0, "|")
};
static final LikeTest[] hsqlDiscrepencies = new LikeTest[] {
// Patterns that fail on hsql (unsupported until someone fixes unicode handling).
// We don't bother to run these in the head-to-head regression suite
new LikeTest("â_x一xxéyyÔ±", 1),
// Patterns that fail (unsupported until we fix the parser)
new UnsupportedEscapeLikeTest("abcd!%%", 0, "!"),
};
public static class LikeSuite {
public void doTests(Client client, boolean forHSQLcomparison) throws IOException, NoConnectionsException, ProcCallException {
loadForTests(client);
if (forHSQLcomparison == false) {
doViaStoredProc(client);
}
doViaAdHoc(client, forHSQLcomparison);
}
private void loadForTests(Client client) throws IOException, NoConnectionsException, ProcCallException {
int id = 0;
for (LikeTestData data : rowData) {
id++;
String query = String.format("insert into strings values (%d,'%s','%s');", id, data.val, data.pat);
VoltTable modCount = client.callProcedure("@AdHoc", query).getResults()[0];
assertEquals("Bad insert row count:", 1, modCount.getRowCount());
assertEquals("Bad insert modification count:", 1, modCount.asScalarLong());
}
}
protected void doViaStoredProc(Client client) throws IOException, NoConnectionsException {
// Tests based on LikeTest list
for (LikeTest test : tests) {
doTestViaStoredProc(client, test);
}
for (LikeTest nonHsqlTest : hsqlDiscrepencies) {
doTestViaStoredProc(client, nonHsqlTest);
}
}
private void doViaAdHoc(Client client, boolean forHSQLcomparison) throws IOException, NoConnectionsException, ProcCallException {
// Test parameter values used as like expression
for (LikeTest test : tests) {
doTestViaAdHoc(client, test);
}
if ( ! forHSQLcomparison) {
for (LikeTest otherTest : hsqlDiscrepencies) {
doTestViaAdHoc(client, otherTest);
}
}
// Tests using PAT column for pattern data
{
//Without an escape this works
String query = "select * from strings where val like pat";
VoltTable result = client.callProcedure("@AdHoc", query).getResults()[0];
assertEquals(String.format("LIKE column test: bad row count:"),
rowData.length, result.getRowCount());
//With an escape there is no way for the escape to propagate to the expression in EE
//So HSQL should reject it due to our modifications
query = "select * from strings where val like pat ESCAPE '?'";
try {
result = client.callProcedure("@AdHoc", query).getResults()[0];
assertEquals(String.format("LIKE column test: bad row count:"),
rowData.length, result.getRowCount());
} catch (ProcCallException e) {
System.out.println("LIKE column test failed (expected for now)");
}
}
}
private void doTestViaStoredProc(Client client, LikeTest test) throws IOException, NoConnectionsException {
String procName = null;
if (test.getClass() == LikeTest.class) {
procName = "SelectLike";
} else if (test instanceof NotLikeTest) {
procName = "NotLike";
} else if (test instanceof EscapeLikeTest) {
return;
}
if (test.getClass() == LikeTest.class) {
System.out.printf("SelectLike pattern \"%s\"\n", test.pattern);
try {
VoltTable result = client.callProcedure(procName, test.pattern).getResults()[0];
assertEquals(String.format("\"%s\": bad row count:", test.pattern),
test.matches, result.getRowCount());
System.out.println(result.toString());
assertFalse(String.format("Expected to crash on \"%s\", but didn't", test.pattern), test.crashes);
} catch (ProcCallException e) {
System.out.printf("LIKE pattern \"%s\" failed\n", test.pattern);
System.out.println(e.toString());
assertTrue("This failure was unexpected", test.crashes);
System.out.println("(This failure was expected)");
}
}
}
private void doTestViaAdHoc(Client client, LikeTest test) throws IOException, NoConnectionsException {
String clause = test.getClause();
String query = String.format("select * from strings where val %s", clause);
System.out.printf("LIKE clause \"%s\"\n", clause);
try {
VoltTable result = client.callProcedure("@AdHoc", query).getResults()[0];
assertEquals(String.format("\"%s\": bad row count:", clause),
test.matches, result.getRowCount());
System.out.println(result.toString());
assertFalse(String.format("Expected to crash on \"%s\", but didn't", clause), test.crashes);
} catch (ProcCallException e) {
System.out.printf("LIKE clause \"%s\" failed\n", clause);
System.out.println(e.toString());
assertTrue("This failure was unexpected", test.crashes);
System.out.println("(This failure was expected)");
}
}
}
public void testLikeClause() throws Exception {
String pathToCatalog = Configuration.getPathToCatalogForTest("adhoc_like.jar");
String pathToDeployment = Configuration.getPathToCatalogForTest("adhoc_like.xml");
VoltProjectBuilder builder = new VoltProjectBuilder();
builder.addLiteralSchema(schema);
builder.addPartitionInfo("STRINGS", "ID");
builder.addStmtProcedure("Insert", "insert into strings values (?, ?, ?);", null);
builder.addStmtProcedure("SelectLike", "select * from strings where val like ?;");
builder.addStmtProcedure("SelectNotLike", "select * from strings where val not like ?;");
boolean success = builder.compile(pathToCatalog, 2, 1, 0);
assertTrue("Insert compilation failed", success);
MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment);
VoltDB.Configuration config = new VoltDB.Configuration();
config.m_pathToCatalog = pathToCatalog;
config.m_pathToDeployment = pathToDeployment;
ServerThread localServer = new ServerThread(config);
Client client = null;
try {
localServer.start();
localServer.waitForInitialization();
client = ClientFactory.createClient();
client.createConnection("localhost");
LikeSuite tests = new LikeSuite();
tests.doTests(client, false);
}
finally {
if (client != null) client.close();
client = null;
if (localServer != null) {
localServer.shutdown();
localServer.join();
}
localServer = null;
// no clue how helpful this is
System.gc();
}
}
}