blob: ab549b01897c099d19d573749afc6c1d1a92b9e7 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.metamodel.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.metamodel.DataContext;
import org.apache.metamodel.MetaModelException;
import org.apache.metamodel.QueryPostprocessDataContext;
import org.apache.metamodel.data.CachingDataSetHeader;
import org.apache.metamodel.data.DataSet;
import org.apache.metamodel.data.DataSetHeader;
import org.apache.metamodel.data.DefaultRow;
import org.apache.metamodel.data.InMemoryDataSet;
import org.apache.metamodel.data.Row;
import org.apache.metamodel.data.SimpleDataSetHeader;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.MutableColumn;
import org.apache.metamodel.schema.MutableSchema;
import org.apache.metamodel.schema.MutableTable;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.schema.TableType;
import com.google.common.collect.Lists;
import junit.framework.TestCase;
public class FilterItemTest extends TestCase {
public void testExpressionBasedFilter() throws Exception {
FilterItem filterItem = new FilterItem("foobar");
assertEquals("foobar", filterItem.getExpression());
try {
filterItem.evaluate(null);
fail("Exception should have been thrown");
} catch (Exception e) {
assertEquals("Expression-based filters cannot be manually evaluated", e.getMessage());
}
Column col1 = new MutableColumn("Col1", ColumnType.VARCHAR);
assertEquals("SELECT Col1 WHERE foobar", new Query().select(col1).where(filterItem).toString());
assertEquals("SELECT Col1 WHERE YEAR(Col1) = 2008", new Query().select(col1).where("YEAR(Col1) = 2008")
.toString());
}
public void testToSqlWhereItem() throws Exception {
MutableColumn col1 = new MutableColumn("Col1", ColumnType.VARCHAR);
SelectItem selectItem = new SelectItem(col1);
FilterItem c = new FilterItem(selectItem, OperatorType.DIFFERENT_FROM, null);
assertEquals("Col1 IS NOT NULL", c.toString());
try {
c = new FilterItem(selectItem, OperatorType.GREATER_THAN, null);
fail("Exception should have been thrown");
} catch (IllegalArgumentException e) {
assertEquals("Can only use EQUALS or DIFFERENT_FROM operator with null-operand", e.getMessage());
}
c = new FilterItem(selectItem, OperatorType.DIFFERENT_FROM, "foo");
assertEquals("Col1 <> 'foo'", c.toString());
c = new FilterItem(selectItem, OperatorType.DIFFERENT_FROM, "'bar'");
// this will be rewritten so it's not an issue even though it look like
// it needs an escape-char
assertEquals("Col1 <> ''bar''", c.toSql());
c = new FilterItem(selectItem, OperatorType.DIFFERENT_FROM, "foo's bar");
// the same applies here
assertEquals("Col1 <> 'foo's bar'", c.toSql());
col1.setType(ColumnType.FLOAT);
c = new FilterItem(selectItem, OperatorType.EQUALS_TO, 423);
assertEquals("Col1 = 423", c.toString());
c = new FilterItem(selectItem, OperatorType.EQUALS_TO, 423426235423.42);
assertEquals("Col1 = 423426235423.42", c.toString());
c = new FilterItem(selectItem, OperatorType.EQUALS_TO, true);
assertEquals("Col1 = 1", c.toString());
c = new FilterItem(selectItem, OperatorType.GREATER_THAN_OR_EQUAL, 123);
assertEquals("Col1 >= 123", c.toString());
c = new FilterItem(selectItem, OperatorType.LESS_THAN_OR_EQUAL, 123);
assertEquals("Col1 <= 123", c.toString());
Column timeColumn = new MutableColumn("TimeCol", ColumnType.TIME);
selectItem = new SelectItem(timeColumn);
c = new FilterItem(selectItem, OperatorType.GREATER_THAN, "02:30:05.000");
assertEquals("TimeCol > TIME '02:30:05'", c.toString());
Column dateColumn = new MutableColumn("DateCol", ColumnType.DATE);
c = new FilterItem(new SelectItem(dateColumn), OperatorType.GREATER_THAN, "2000-12-31");
assertEquals("DateCol > DATE '2000-12-31'", c.toString());
}
public void testToStringTimeStamp() throws Exception {
Column timestampColumn = new MutableColumn("TimestampCol", ColumnType.TIMESTAMP);
FilterItem c = new FilterItem(new SelectItem(timestampColumn), OperatorType.LESS_THAN,
"2000-12-31 02:30:05.007");
assertEquals("TimestampCol < TIMESTAMP '2000-12-31 02:30:05'", c.toString());
c = new FilterItem(new SelectItem(timestampColumn), OperatorType.LESS_THAN, "2000-12-31 02:30:05");
assertEquals("TimestampCol < TIMESTAMP '2000-12-31 02:30:05'", c.toString());
Column dateColumn = new MutableColumn("DateCol", ColumnType.DATE);
c = new FilterItem(new SelectItem(timestampColumn), OperatorType.GREATER_THAN, new SelectItem(dateColumn));
assertEquals("TimestampCol > DateCol", c.toString());
}
public void testEvaluateStrings() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.VARCHAR);
Column col2 = new MutableColumn("Col2", ColumnType.VARCHAR);
SelectItem s1 = new SelectItem(col1);
SelectItem s2 = new SelectItem(col2);
SelectItem[] selectItems = new SelectItem[] { s1, s2 };
SimpleDataSetHeader header = new SimpleDataSetHeader(selectItems);
Row row;
FilterItem c;
row = new DefaultRow(header, new Object[] { "foo", "bar" });
c = new FilterItem(s1, OperatorType.DIFFERENT_FROM, s2);
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { "aaa", "bbb" });
c = new FilterItem(s1, OperatorType.GREATER_THAN, s2);
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.LESS_THAN, s2);
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { "aaa", "aaa" });
c = new FilterItem(s1, OperatorType.EQUALS_TO, s2);
assertTrue(c.evaluate(row));
c = new FilterItem(s1, OperatorType.LIKE, s2);
row = new DefaultRow(header, new Object[] { "foobar", "fo%b%r" });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { "foobbdbafsdfr", "fo%b%r" });
assertTrue(c.evaluate(row));
}
public void testEvaluateNull() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.INTEGER);
Column col2 = new MutableColumn("Col2", ColumnType.DECIMAL);
SelectItem s1 = new SelectItem(col1);
SelectItem s2 = new SelectItem(col2);
List<SelectItem> selectItems = Lists.newArrayList( s1, s2 );
CachingDataSetHeader header = new CachingDataSetHeader(selectItems);
FilterItem c = new FilterItem(s1, OperatorType.EQUALS_TO, null);
Row row = new DefaultRow(header, new Object[] { 1, 1 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertTrue(c.evaluate(row));
c = new FilterItem(s1, OperatorType.EQUALS_TO, 1);
row = new DefaultRow(header, new Object[] { 1, 1 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.DIFFERENT_FROM, 5);
row = new DefaultRow(header, new Object[] { 1, 1 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertTrue(c.evaluate(row));
c = new FilterItem(s1, OperatorType.GREATER_THAN, s2);
row = new DefaultRow(header, new Object[] { 5, 1 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 1, null });
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.GREATER_THAN_OR_EQUAL, s2);
row = new DefaultRow(header, new Object[] { 5, 1 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 1, 5 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 5, 5 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 1, null });
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.LESS_THAN_OR_EQUAL, s2);
row = new DefaultRow(header, new Object[] { 1, 5 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 5, 1 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 1, 1 });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, 1 });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { 1, null });
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.EQUALS_TO, s2);
row = new DefaultRow(header, new Object[] { 1, null });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { null, null });
assertTrue(c.evaluate(row));
}
public void testEvaluateDates() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.DATE);
SelectItem s1 = new SelectItem(col1);
CachingDataSetHeader header = new CachingDataSetHeader(Lists.newArrayList(s1));
long currentTimeMillis = System.currentTimeMillis();
FilterItem c = new FilterItem(s1, OperatorType.LESS_THAN, new java.sql.Date(currentTimeMillis));
Row row = new DefaultRow(header, new Object[] { new java.sql.Date(currentTimeMillis) });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { new java.sql.Date(currentTimeMillis + 10000000) });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { new java.sql.Date(currentTimeMillis - 10000000) });
assertTrue(c.evaluate(row));
}
public void testEvaluateBooleans() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.BIT);
SelectItem s1 = new SelectItem(col1);
SelectItem[] selectItems = new SelectItem[] { s1 };
DataSetHeader header = new SimpleDataSetHeader(selectItems);
FilterItem c = new FilterItem(s1, OperatorType.EQUALS_TO, true);
Row row = new DefaultRow(header, new Object[] { true });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { false });
assertFalse(c.evaluate(row));
c = new FilterItem(s1, OperatorType.EQUALS_TO, false);
row = new DefaultRow(header, new Object[] { true });
assertFalse(c.evaluate(row));
row = new DefaultRow(header, new Object[] { false });
assertTrue(c.evaluate(row));
c = new FilterItem(s1, OperatorType.GREATER_THAN, false);
row = new DefaultRow(header, new Object[] { true });
assertTrue(c.evaluate(row));
row = new DefaultRow(header, new Object[] { false });
assertFalse(c.evaluate(row));
}
/**
* Tests that the following (general) rules apply to the object:
* <p/>
* <li>the hashcode is the same when run twice on an unaltered object</li>
* <li>if o1.equals(o2) then this condition must be true: o1.hashCode() ==
* 02.hashCode()
*/
public void testEqualsAndHashCode() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.BIT);
FilterItem c1 = new FilterItem(new SelectItem(col1), OperatorType.EQUALS_TO, true);
FilterItem c2 = new FilterItem(new SelectItem(col1), OperatorType.EQUALS_TO, true);
assertEquals(c1, c2);
assertEquals(c1.hashCode(), c2.hashCode());
c2 = new FilterItem(new SelectItem(col1), OperatorType.GREATER_THAN, true);
assertFalse(c1.equals(c2));
assertFalse(c1.hashCode() == c2.hashCode());
Column col2 = new MutableColumn("Col2", ColumnType.VARBINARY);
c2 = new FilterItem(new SelectItem(col2), OperatorType.EQUALS_TO, true);
assertFalse(c1.equals(c2));
assertFalse(c1.hashCode() == c2.hashCode());
}
public void testOrFilterItem() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.VARCHAR);
SelectItem s1 = new SelectItem(col1);
FilterItem c1 = new FilterItem(s1, OperatorType.EQUALS_TO, "foo");
FilterItem c2 = new FilterItem(s1, OperatorType.EQUALS_TO, "bar");
FilterItem c3 = new FilterItem(s1, OperatorType.EQUALS_TO, "foobar");
FilterItem filter = new FilterItem(c1, c2, c3);
assertEquals("(Col1 = 'foo' OR Col1 = 'bar' OR Col1 = 'foobar')", filter.toString());
DataSetHeader header = new SimpleDataSetHeader(new SelectItem[] { s1 });
assertTrue(filter.evaluate(new DefaultRow(header, new Object[] { "foo" })));
assertTrue(filter.evaluate(new DefaultRow(header, new Object[] { "bar" })));
assertTrue(filter.evaluate(new DefaultRow(header, new Object[] { "foobar" })));
assertFalse(filter.evaluate(new DefaultRow(header, new Object[] { "foob" })));
}
public void testAndFilterItem() throws Exception {
Column col1 = new MutableColumn("Col1", ColumnType.VARCHAR);
SelectItem s1 = new SelectItem(col1);
FilterItem c1 = new FilterItem(s1, OperatorType.LIKE, "foo%");
FilterItem c2 = new FilterItem(s1, OperatorType.LIKE, "%bar");
FilterItem c3 = new FilterItem(s1, OperatorType.DIFFERENT_FROM, "foobar");
FilterItem filter = new FilterItem(LogicalOperator.AND, c1, c2, c3);
assertEquals("(Col1 LIKE 'foo%' AND Col1 LIKE '%bar' AND Col1 <> 'foobar')", filter.toString());
CachingDataSetHeader header = new CachingDataSetHeader(Lists.newArrayList(s1));
assertTrue(filter.evaluate(new DefaultRow(header, new Object[] { "foo bar" })));
assertTrue(filter.evaluate(new DefaultRow(header, new Object[] { "foosenbar" })));
assertFalse(filter.evaluate(new DefaultRow(header, new Object[] { "foo" })));
assertFalse(filter.evaluate(new DefaultRow(header, new Object[] { "hello world" })));
assertFalse(filter.evaluate(new DefaultRow(header, new Object[] { "foobar" })));
}
// Ticket #410
public void testOrFilterItemWithoutSelectingActualItmes() throws Exception {
// define the schema
final MutableSchema schema = new MutableSchema("s");
MutableTable table = new MutableTable("persons", TableType.TABLE, schema);
schema.addTable(table);
final Column col1 = new MutableColumn("name", ColumnType.VARCHAR, table, 1, true);
final Column col2 = new MutableColumn("role", ColumnType.VARCHAR, table, 2, true);
final Column col3 = new MutableColumn("column_number", ColumnType.INTEGER, table, 3, true);
table.addColumn(col1);
table.addColumn(col2);
table.addColumn(col3);
Query q = new Query();
q.select(col3);
q.from(col1.getTable());
SelectItem selectItem1 = new SelectItem(col1);
SelectItem selectItem2 = new SelectItem(col2);
FilterItem item1 = new FilterItem(selectItem1, OperatorType.EQUALS_TO, "kasper");
FilterItem item2 = new FilterItem(selectItem2, OperatorType.EQUALS_TO, "user");
q.where(new FilterItem(item1, item2));
assertEquals(
"SELECT persons.column_number FROM s.persons WHERE (persons.name = 'kasper' OR persons.role = 'user')",
q.toString());
DataContext dc = new QueryPostprocessDataContext() {
@Override
public DataSet materializeMainSchemaTable(Table table, List<Column> columns, int maxRows) {
// we expect 3 columns to be materialized because the query has column references in both SELECT and WHERE clause
assertEquals(3, columns.size());
assertEquals("column_number", columns.get(0).getName());
assertEquals("name", columns.get(1).getName());
assertEquals("role", columns.get(2).getName());
DataSetHeader header = new CachingDataSetHeader(Lists.newArrayList(col1, col2, col3).stream()
.map(SelectItem::new)
.collect(Collectors.toList()));
List<Row> rows = new LinkedList<Row>();
rows.add(new DefaultRow(header, new Object[] { "foo", "bar", 1 }));
rows.add(new DefaultRow(header, new Object[] { "kasper", "developer", 2 }));
rows.add(new DefaultRow(header, new Object[] { "admin", "admin", 3 }));
rows.add(new DefaultRow(header, new Object[] { "elikeon", "user", 4 }));
rows.add(new DefaultRow(header, new Object[] { "someuser", "user", 5 }));
rows.add(new DefaultRow(header, new Object[] { "hmm", "what-the", 6 }));
return new InMemoryDataSet(header, rows);
}
@Override
protected String getMainSchemaName() throws MetaModelException {
return "s";
}
@Override
protected MutableSchema getMainSchema() throws MetaModelException {
return schema;
}
};
DataSet result = dc.executeQuery(q);
List<Object[]> objectArrays = result.toObjectArrays();
assertEquals(3, objectArrays.size());
assertEquals(2, objectArrays.get(0)[0]);
assertEquals(4, objectArrays.get(1)[0]);
assertEquals(5, objectArrays.get(2)[0]);
}
public void testInOperandSql() throws Exception {
SelectItem selectItem = new SelectItem(new MutableColumn("foo", ColumnType.VARCHAR, null, 1, null, null, true,
null, false, null));
Object operand = new String[] { "foo", "bar" };
assertEquals("foo IN ('foo' , 'bar')", new FilterItem(selectItem, OperatorType.IN, operand).toSql());
operand = Arrays.asList("foo", "bar", "baz");
assertEquals("foo IN ('foo' , 'bar' , 'baz')", new FilterItem(selectItem, OperatorType.IN, operand).toSql());
operand = "foo";
assertEquals("foo IN ('foo')", new FilterItem(selectItem, OperatorType.IN, operand).toSql());
operand = new ArrayList<Object>();
assertEquals("foo IN ()", new FilterItem(selectItem, OperatorType.IN, operand).toSql());
}
public void testNotInOperandSql() throws Exception {
SelectItem selectItem = new SelectItem("foo", "foo");
Object operand = new String[] { "foo", "bar" };
assertEquals("foo NOT IN ('foo' , 'bar')", new FilterItem(selectItem, OperatorType.NOT_IN, operand).toSql());
operand = Arrays.asList("foo", "bar", "baz");
assertEquals("foo NOT IN ('foo' , 'bar' , 'baz')", new FilterItem(selectItem, OperatorType.NOT_IN, operand).toSql());
operand = "foo";
assertEquals("foo NOT IN ('foo')", new FilterItem(selectItem, OperatorType.NOT_IN, operand).toSql());
operand = new ArrayList<Object>();
assertEquals("foo NOT IN ()", new FilterItem(selectItem, OperatorType.NOT_IN, operand).toSql());
}
public void testNotLikeOperandSql() throws Exception {
Column column = new MutableColumn("foo");
SelectItem selectItem = new SelectItem(column);
String operand = "%foo";
assertEquals("foo NOT LIKE '%foo'", new FilterItem(selectItem, OperatorType.NOT_LIKE, operand).toSql());
operand = "foo%";
assertEquals("foo NOT LIKE 'foo%'", new FilterItem(selectItem, OperatorType.NOT_LIKE, operand).toSql());
operand = "%foo%foo%";
assertEquals("foo NOT LIKE '%foo%foo%'", new FilterItem(selectItem, OperatorType.NOT_LIKE, operand).toSql());
}
public void testInOperandEvaluate() throws Exception {
SelectItem selectItem = new SelectItem(new MutableColumn("foo", ColumnType.VARCHAR, null, 1, null, null, true,
null, false, null));
Object operand = new String[] { "foo", "bar" };
FilterItem filterItem = new FilterItem(selectItem, OperatorType.IN, operand);
DataSetHeader header = new CachingDataSetHeader(Lists.newArrayList(selectItem));
assertTrue(filterItem.evaluate(new DefaultRow(header, new Object[] { "foo" })));
assertTrue(filterItem.evaluate(new DefaultRow(header, new Object[] { "bar" })));
assertFalse(filterItem.evaluate(new DefaultRow(header, new Object[] { "foobar" })));
}
}