/***************************************************************** * 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.cayenne.query; import org.apache.cayenne.ObjectId; import org.apache.cayenne.Persistent; import org.apache.cayenne.access.types.ValueObjectType; import org.apache.cayenne.access.types.ValueObjectTypeRegistry; import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.exp.TraversalHandler; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * This class is testing converting Expressions to cache key part. * * @since 4.0 */ public class SelectQueryMetadataCacheKeyTest { private ValueObjectTypeRegistry registry; private StringBuilder cacheKey; @SuppressWarnings("unchecked") @Before public void createObjects() { registry = mock(ValueObjectTypeRegistry.class); // mock value type for Double class ValueObjectType mockType = mock(ValueObjectType.class); when(mockType.getValueType()).thenReturn(Double.class); when(mockType.toCacheKey(any())).thenReturn("<value placeholder>"); when(registry.getValueType(eq(Double.class))).thenReturn(mockType); // value type for TestValue class ValueObjectType testType = new TestValueType(); when(registry.getValueType(eq(TestValue.class))).thenReturn(testType); } /** * Simple expressions */ @Test public void cacheKeySimple() { ExpressionFactory.exp("field = 1").traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.exp("field = 1").traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.exp("field = 2").traverse(newHandler()); String s3 = cacheKey.toString(); assertEquals(s1, s2); assertNotEquals(s2, s3); } /** * Expressions with list of simple values */ @Test public void cacheKeyWithList() { ExpressionFactory.exp("field in (1,2,3)").traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.exp("field in (1,2,3)").traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.exp("field in (2,3,4)").traverse(newHandler()); String s3 = cacheKey.toString(); assertEquals(s1, s2); assertNotEquals(s2, s3); } /** * Simple test for custom value object, Double.class is marked as a custom value object. */ @Test public void cacheKeyWithValueObjectSimple() { ExpressionFactory.exp("field = 1.0").traverse(newHandler()); String s1 = cacheKey.toString(); assertTrue(s1.contains("<value placeholder>")); } /** * List of value objects, Double.class is marked as a custom value object. */ @Test public void cacheKeyWithValueObjectList() { ExpressionFactory.exp("field in (1.0,2.0,3.0)").traverse(newHandler()); String s1 = cacheKey.toString(); assertTrue(s1.contains("<value placeholder>")); } @Test public void cacheKeyWithEnumValue() { ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_1).traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_1).traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", TestEnum.VALUE_2).traverse(newHandler()); String s3 = cacheKey.toString(); assertEquals(s1, s2); assertNotEquals(s2, s3); } @Test public void cacheKeyWithValueObject() { ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(1)).traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(1)).traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", new TestValue(2)).traverse(newHandler()); String s3 = cacheKey.toString(); assertEquals(s1, s2); assertNotEquals(s2, s3); } /** * Persistent objects should be converted to their ObjectIds. */ @Test public void cacheKeyWithPersistentObject() { Persistent persistent1 = mock(Persistent.class); ObjectId objectId1 = mock(ObjectId.class); when(objectId1.toString()).thenReturn("objId1"); when(persistent1.getObjectId()).thenReturn(objectId1); Persistent persistent2 = mock(Persistent.class); ObjectId objectId2 = mock(ObjectId.class); when(objectId2.toString()).thenReturn("objId2"); when(persistent2.getObjectId()).thenReturn(objectId2); ExpressionFactory.greaterOrEqualExp("testPath", persistent1).traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", persistent1).traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.greaterOrEqualExp("testPath", persistent2).traverse(newHandler()); String s3 = cacheKey.toString(); assertTrue(s1.contains("objId1")); assertTrue(s3.contains("objId2")); assertEquals(s1, s2); assertNotEquals(s2, s3); } @Test public void cacheKeyWithFunctionCall() { ExpressionFactory.exp("length(testPath)").traverse(newHandler()); String s1 = cacheKey.toString(); ExpressionFactory.exp("length(testPath)").traverse(newHandler()); String s2 = cacheKey.toString(); ExpressionFactory.exp("count(testPath)").traverse(newHandler()); String s3 = cacheKey.toString(); assertEquals(s1, s2); assertNotEquals(s2, s3); ExpressionFactory.exp("substring(path, testPath)").traverse(newHandler()); String s4 = cacheKey.toString(); ExpressionFactory.exp("substring(path2, testPath)").traverse(newHandler()); String s5 = cacheKey.toString(); assertNotEquals(s4, s5); ExpressionFactory.exp("year(path)").traverse(newHandler()); String s6 = cacheKey.toString(); ExpressionFactory.exp("hour(path)").traverse(newHandler()); String s7 = cacheKey.toString(); assertNotEquals(s6, s7); } private TraversalHandler newHandler() { return new SelectQueryMetadata.ToCacheKeyTraversalHandler(registry, cacheKey = new StringBuilder()); } /* ************* Test types *************** */ /** * Test enum */ enum TestEnum { VALUE_1, VALUE_2 } /** * Test value object */ static class TestValue { int v = 0; TestValue(int v) { this.v = v; } } /** * Test value object descriptor, we need only toCacheKey() method */ static class TestValueType implements ValueObjectType<TestValue, Integer> { @Override public Class<Integer> getTargetType() { throw new UnsupportedOperationException(); } @Override public Class<TestValue> getValueType() { throw new UnsupportedOperationException(); } @Override public TestValue toJavaObject(Integer value) { throw new UnsupportedOperationException(); } @Override public Integer fromJavaObject(TestValue object) { throw new UnsupportedOperationException(); } @Override public String toCacheKey(TestValue object) { return Integer.toString(object.v); } } }