/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.regression.rowrecog; import com.espertech.esper.client.*; import com.espertech.esper.client.scopetest.EPAssertionUtil; import com.espertech.esper.client.scopetest.SupportUpdateListener; import com.espertech.esper.client.soda.EPStatementObjectModel; import com.espertech.esper.core.service.EPServiceProviderSPI; import com.espertech.esper.core.service.EPStatementSPI; import com.espertech.esper.epl.spec.StatementSpecCompiled; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import com.espertech.esper.rowregex.RegexPatternExpandUtil; import com.espertech.esper.rowregex.RowRegexExprNode; import com.espertech.esper.rowregex.RowRegexExprNodePrecedenceEnum; import com.espertech.esper.supportregression.bean.SupportBean; import com.espertech.esper.supportregression.client.SupportConfigFactory; import com.espertech.esper.supportregression.util.SupportMessageAssertUtil; import com.espertech.esper.supportregression.util.SupportModelHelper; import junit.framework.TestCase; import java.io.StringWriter; import java.util.*; public class TestRowPatternRecognitionRepetition extends TestCase { private EPServiceProvider epService; private SupportUpdateListener listener; public void setUp() { Configuration config = SupportConfigFactory.getConfiguration(); config.addEventType(SupportBean.class); epService = EPServiceProviderManager.getDefaultProvider(config); epService.initialize(); listener = new SupportUpdateListener(); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.startTest(epService, this.getClass(), getName());} } public void tearDown() { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.endTest();} listener = null; } public void testRepeat() throws Exception { runAssertionRepeat(false); runAssertionRepeat(true); runAssertionPrev(); runInvalid(); runDocSamples(); } private void runDocSamples() { epService.getEPAdministrator().createEPL("create objectarray schema TemperatureSensorEvent(id string, device string, temp int)"); runDocSampleExactlyN(); runDocSampleNOrMore_and_BetweenNandM("A{2,} B"); runDocSampleNOrMore_and_BetweenNandM("A{2,3} B"); runDocSampleUpToN(); } private void runDocSampleUpToN() { String[] fields = "a0_id,a1_id,b_id".split(","); String epl = "select * from TemperatureSensorEvent\n" + "match_recognize (\n" + " partition by device\n" + " measures A[0].id as a0_id, A[1].id as a1_id, B.id as b_id\n" + " pattern (A{,2} B)\n" + " define \n" + "\tA as A.temp >= 100,\n" + "\tB as B.temp >= 102)"; EPStatement stmt = epService.getEPAdministrator().createEPL(epl); stmt.addListener(listener); epService.getEPRuntime().sendEvent(new Object[] {"E1", "1", 99}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E2", "1", 100}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E3", "1", 100}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E4", "1", 101}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E5", "1", 102}, "TemperatureSensorEvent"); EPAssertionUtil.assertProps(listener.assertOneGetNewAndReset(), fields, new Object[]{"E3", "E4", "E5"}); stmt.destroy(); } private void runDocSampleNOrMore_and_BetweenNandM(String pattern) { String[] fields = "a0_id,a1_id,a2_id,b_id".split(","); String epl = "select * from TemperatureSensorEvent\n" + "match_recognize (\n" + " partition by device\n" + " measures A[0].id as a0_id, A[1].id as a1_id, A[2].id as a2_id, B.id as b_id\n" + " pattern (" + pattern + ")\n" + " define \n" + "\tA as A.temp >= 100,\n" + "\tB as B.temp >= 102)"; EPStatement stmt = epService.getEPAdministrator().createEPL(epl); stmt.addListener(listener); epService.getEPRuntime().sendEvent(new Object[] {"E1", "1", 99}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E2", "1", 100}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E3", "1", 100}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E4", "1", 101}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E5", "1", 102}, "TemperatureSensorEvent"); EPAssertionUtil.assertProps(listener.assertOneGetNewAndReset(), fields, new Object[]{"E2", "E3", "E4", "E5"}); stmt.destroy(); } private void runDocSampleExactlyN() { String[] fields = "a0_id,a1_id".split(","); String epl = "select * from TemperatureSensorEvent\n" + "match_recognize (\n" + " partition by device\n" + " measures A[0].id as a0_id, A[1].id as a1_id\n" + " pattern (A{2})\n" + " define \n" + "\tA as A.temp >= 100)"; EPStatement stmt = epService.getEPAdministrator().createEPL(epl); stmt.addListener(listener); epService.getEPRuntime().sendEvent(new Object[] {"E1", "1", 99}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E2", "1", 100}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E3", "1", 100}, "TemperatureSensorEvent"); EPAssertionUtil.assertProps(listener.assertOneGetNewAndReset(), fields, new Object[] {"E2", "E3"}); epService.getEPRuntime().sendEvent(new Object[]{"E4", "1", 101}, "TemperatureSensorEvent"); epService.getEPRuntime().sendEvent(new Object[] {"E5", "1", 102}, "TemperatureSensorEvent"); EPAssertionUtil.assertProps(listener.assertOneGetNewAndReset(), fields, new Object[]{"E4", "E5"}); stmt.destroy(); } private void runInvalid() { String template = "select * from SupportBean " + "match_recognize (" + " measures A as a" + " pattern (REPLACE) " + ")"; epService.getEPAdministrator().createEPL("create variable int myvariable = 0"); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{}"), "Invalid match-recognize quantifier '{}', expecting an expression"); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{null}"), "Error starting statement: pattern quantifier 'null' must return an integer-type value"); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{myvariable}"), "Error starting statement: pattern quantifier 'myvariable' must return a constant value"); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{prev(A)}"), "Error starting statement: Invalid match-recognize pattern expression 'prev(A)': Aggregation, sub-select, previous or prior functions are not supported in this context"); String expected = "Error starting statement: Invalid pattern quantifier value -1, expecting a minimum of 1"; SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{-1}"), expected); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{,-1}"), expected); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{-1,10}"), expected); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{-1,}"), expected); SupportMessageAssertUtil.tryInvalid(epService, template.replaceAll("REPLACE", "A{5,3}"), "Error starting statement: Invalid pattern quantifier value 5, expecting a minimum of 1 and maximum of 3"); } private void runAssertionPrev() { String text = "select * from SupportBean " + "match_recognize (" + " measures A as a" + " pattern (A{3}) " + " define " + " A as A.intPrimitive > prev(A.intPrimitive)" + ")"; EPStatement stmt = epService.getEPAdministrator().createEPL(text); SupportUpdateListener listener = new SupportUpdateListener(); stmt.addListener(listener); sendEvent("A1", 1); sendEvent("A2", 4); sendEvent("A3", 2); sendEvent("A4", 6); sendEvent("A5", 5); SupportBean b6 = sendEvent("A6", 6); SupportBean b7 = sendEvent("A7", 7); SupportBean b8 = sendEvent("A9", 8); EPAssertionUtil.assertProps(listener.assertOneGetNewAndReset(), "a".split(","), new Object[] {new Object[] {b6,b7,b8}}); } private void runAssertionRepeat(boolean soda) { // Atom Assertions // // // single-bound assertions runAssertionRepeatSingleBound(soda); // defined-range assertions runAssertionsRepeatRange(soda); // lower-bounds assertions runAssertionsUpTo(soda); // upper-bounds assertions runAssertionsAtLeast(soda); // Nested Assertions // // // single-bound nested assertions runAssertionNestedRepeatSingle(soda); // defined-range nested assertions runAssertionNestedRepeatRange(soda); // lower-bounds nested assertions runAssertionsNestedUpTo(soda); // upper-bounds nested assertions runAssertionsNestedAtLeast(soda); } public void testEquivalent() throws Exception { // // Single-bounds Repeat. // runEquivalent(epService, "A{1}", "A"); runEquivalent(epService, "A{2}", "A A"); runEquivalent(epService, "A{3}", "A A A"); runEquivalent(epService, "A{1} B{2}", "A B B"); runEquivalent(epService, "A{1} B{2} C{3}", "A B B C C C"); runEquivalent(epService, "(A{2})", "(A A)"); runEquivalent(epService, "A?{2}", "A? A?"); runEquivalent(epService, "A*{2}", "A* A*"); runEquivalent(epService, "A+{2}", "A+ A+"); runEquivalent(epService, "A??{2}", "A?? A??"); runEquivalent(epService, "A*?{2}", "A*? A*?"); runEquivalent(epService, "A+?{2}", "A+? A+?"); runEquivalent(epService, "(A B){1}", "(A B)"); runEquivalent(epService, "(A B){2}", "(A B) (A B)"); runEquivalent(epService, "(A B)?{2}", "(A B)? (A B)?"); runEquivalent(epService, "(A B)*{2}", "(A B)* (A B)*"); runEquivalent(epService, "(A B)+{2}", "(A B)+ (A B)+"); runEquivalent(epService, "A B{2} C", "A B B C"); runEquivalent(epService, "A (B{2}) C", "A (B B) C"); runEquivalent(epService, "(A{2}) C", "(A A) C"); runEquivalent(epService, "A (B{2}|C{2})", "A (B B|C C)"); runEquivalent(epService, "A{2} B{2} C{2}", "A A B B C C"); runEquivalent(epService, "A{2} B C{2}", "A A B C C"); runEquivalent(epService, "A B{2} C{2}", "A B B C C"); // range bounds runEquivalent(epService, "A{1, 3}", "A A? A?"); runEquivalent(epService, "A{2, 4}", "A A A? A?"); runEquivalent(epService, "A?{1, 3}", "A? A? A?"); runEquivalent(epService, "A*{1, 3}", "A* A* A*"); runEquivalent(epService, "A+{1, 3}", "A+ A* A*"); runEquivalent(epService, "A??{1, 3}", "A?? A?? A??"); runEquivalent(epService, "A*?{1, 3}", "A*? A*? A*?"); runEquivalent(epService, "A+?{1, 3}", "A+? A*? A*?"); runEquivalent(epService, "(A B)?{1, 3}", "(A B)? (A B)? (A B)?"); runEquivalent(epService, "(A B)*{1, 3}", "(A B)* (A B)* (A B)*"); runEquivalent(epService, "(A B)+{1, 3}", "(A B)+ (A B)* (A B)*"); // lower-only bounds runEquivalent(epService, "A{2,}", "A A A*"); runEquivalent(epService, "A?{2,}", "A? A? A*"); runEquivalent(epService, "A*{2,}", "A* A* A*"); runEquivalent(epService, "A+{2,}", "A+ A+ A*"); runEquivalent(epService, "A??{2,}", "A?? A?? A*?"); runEquivalent(epService, "A*?{2,}", "A*? A*? A*?"); runEquivalent(epService, "A+?{2,}", "A+? A+? A*?"); runEquivalent(epService, "(A B)?{2,}", "(A B)? (A B)? (A B)*"); runEquivalent(epService, "(A B)*{2,}", "(A B)* (A B)* (A B)*"); runEquivalent(epService, "(A B)+{2,}", "(A B)+ (A B)+ (A B)*"); // upper-only bounds runEquivalent(epService, "A{,2}", "A? A?"); runEquivalent(epService, "A?{,2}", "A? A?"); runEquivalent(epService, "A*{,2}", "A* A*"); runEquivalent(epService, "A+{,2}", "A* A*"); runEquivalent(epService, "A??{,2}", "A?? A??"); runEquivalent(epService, "A*?{,2}", "A*? A*?"); runEquivalent(epService, "A+?{,2}", "A*? A*?"); runEquivalent(epService, "(A B){,2}", "(A B)? (A B)?"); runEquivalent(epService, "(A B)?{,2}", "(A B)? (A B)?"); runEquivalent(epService, "(A B)*{,2}", "(A B)* (A B)*"); runEquivalent(epService, "(A B)+{,2}", "(A B)* (A B)*"); // // Nested Repeat. // runEquivalent(epService, "(A B){2}", "(A B) (A B)"); runEquivalent(epService, "(A){2}", "A A"); runEquivalent(epService, "(A B C){3}", "(A B C) (A B C) (A B C)"); runEquivalent(epService, "(A B){2} (C D){2}", "(A B) (A B) (C D) (C D)"); runEquivalent(epService, "((A B){2} C){2}", "((A B) (A B) C) ((A B) (A B) C)"); runEquivalent(epService, "((A|B){2} (C|D){2}){2}", "((A|B) (A|B) (C|D) (C|D)) ((A|B) (A|B) (C|D) (C|D))"); } private void runAssertionNestedRepeatSingle(boolean soda) { runTwiceAB(soda, "(A B) (A B)"); runTwiceAB(soda, "(A B){2}"); runAThenTwiceBC(soda, "A (B C) (B C)"); runAThenTwiceBC(soda, "A (B C){2}"); } private void runAssertionNestedRepeatRange(boolean soda) { runOnceOrTwiceABThenC(soda, "(A B) (A B)? C"); runOnceOrTwiceABThenC(soda, "(A B){1,2} C"); } private void runAssertionsAtLeast(boolean soda) { runAtLeast2AThenB(soda, "A A A* B"); runAtLeast2AThenB(soda, "A{2,} B"); runAtLeast2AThenB(soda, "A{2,4} B"); } private void runAssertionsUpTo(boolean soda) { runUpTo2AThenB(soda, "A? A? B"); runUpTo2AThenB(soda, "A{,2} B"); } private void runAssertionsRepeatRange(boolean soda) { run2To3AThenB(soda, "A A A? B"); run2To3AThenB(soda, "A{2,3} B"); } private void runAssertionsNestedUpTo(boolean soda) { runUpTo2ABThenC(soda, "(A B)? (A B)? C"); runUpTo2ABThenC(soda, "(A B){,2} C"); } private void runAssertionsNestedAtLeast(boolean soda) { runAtLeast2ABThenC(soda, "(A B) (A B) (A B)* C"); runAtLeast2ABThenC(soda, "(A B){2,} C"); } private void runAssertionRepeatSingleBound(boolean soda) { runExactly2A(soda, "A A"); runExactly2A(soda, "A{2}"); runExactly2A(soda, "(A{2})"); // concatenation runAThen2BThenC(soda, "A B B C"); runAThen2BThenC(soda, "A B{2} C"); // nested runAThen2BThenC(false, "A (B B) C"); runAThen2BThenC(false, "A (B{2}) C"); // alteration runAThen2BOr2C(soda, "A (B B|C C)"); runAThen2BOr2C(soda, "A (B{2}|C{2})"); // multiple run2AThen2B(soda, "A A B B"); run2AThen2B(soda, "A{2} B{2}"); } private void runAtLeast2ABThenC(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {true, true, false}, new String[] { "A1,B1,A2,B2,C1", "A1,B1,A2,B2,A3,B3,C1" }, new String[] {"A1,B1,C1", "A1,B1,A2,C1", "B1,A1,B2,C1"}); } private void runOnceOrTwiceABThenC(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {true, true, false}, new String[] { "A1,B1,C1", "A1,B1,A2,B2,C1" }, new String[] {"C1", "A1,A2,C2", "B1,A1,C1"}); } private void runAtLeast2AThenB(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b", new boolean[] {true, false}, new String[] { "A1,A2,B1", "A1,A2,A3,B1" }, new String[] {"A1,B1", "B1"}); } private void runUpTo2AThenB(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b", new boolean[] {true, false}, new String[] { "B1", "A1,B1", "A1,A2,B1" }, new String[] {"A1"}); } private void run2AThen2B(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b", new boolean[] {true, true}, new String[] { "A1,A2,B1,B2", }, new String[] {"A1,A2,B1", "B1,B2,A1,A2", "A1,B1,A2,B2"}); } private void runUpTo2ABThenC(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {true, true, false}, new String[] { "C1", "A1,B1,C1", "A1,B1,A2,B2,C1", }, new String[] {"A1,B1,A2,B2", "A1,A2"}); } private void run2To3AThenB(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b", new boolean[] {true, false}, new String[] { "A1,A2,A3,B1", "A1,A2,B1", }, new String[] {"A1,B1", "A1,A2", "B1"}); } private void runAThen2BOr2C(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {false, true, true}, new String[] { "A1,C1,C2", "A2,B1,B2", }, new String[] {"B1,B2", "C1,C2", "A1,B1,C1", "A1,C1,B1"}); } private void runTwiceAB(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b", new boolean[] {true, true}, new String[] { "A1,B1,A2,B2", }, new String[] {"A1,A2,B1", "A1,A2,B1,B2", "A1,B1,B2,A2"}); } private void runAThenTwiceBC(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {false, true, true}, new String[] { "A1,B1,C1,B2,C2", }, new String[] {"A1,B1,C1,B2", "A1,B1,C1,C2", "A1,B1,B2,C1,C2"}); } private void runAThen2BThenC(boolean soda, String pattern) { runAssertion(soda, pattern, "a,b,c", new boolean[] {false, true, false}, new String[] { "A1,B1,B2,C1", }, new String[] {"B1,B2,C1", "A1,B1,C1", "A1,B1,B2"}); } private void runExactly2A(boolean soda, String pattern) { runAssertion(soda, pattern, "a", new boolean[] {true}, new String[]{ "A1,A2", "A3,A4", }, new String[] {"A5"}); } private void runAssertion(boolean soda, String pattern, String propertyNames, boolean[] arrayProp, String[] sequencesWithMatch, String[] sequencesNoMatch) { String[] props = propertyNames.split(","); String measures = makeMeasures(props); String defines = makeDefines(props); String text = "select * from SupportBean " + "match_recognize (" + " partition by intPrimitive" + " measures " + measures + " pattern (" + pattern + ")" + " define " + defines + ")"; SupportModelHelper.createByCompileOrParse(epService, soda, text).addListener(listener); int sequenceNum = 0; for (int i = 0; i < sequencesWithMatch.length; i++) { runAssertionSequence(true, props, arrayProp, sequenceNum, sequencesWithMatch[i]); sequenceNum++; } for (int i = 0; i < sequencesNoMatch.length; i++) { runAssertionSequence(false, props, arrayProp, sequenceNum, sequencesNoMatch[i]); sequenceNum++; } epService.getEPAdministrator().destroyAllStatements(); } private void runAssertionSequence(boolean match, String[] propertyNames, boolean[] arrayProp, int sequenceNum, String sequence) { // send events String[] events = sequence.split(","); Map<String, List<SupportBean>> sent = new HashMap<String, List<SupportBean>>(); for (String anEvent : events) { String type = new String(new char[]{anEvent.charAt(0)}); SupportBean bean = sendEvent(anEvent, sequenceNum); String propName = type.toLowerCase(Locale.ENGLISH); if (!sent.containsKey(propName)) { sent.put(propName, new ArrayList<SupportBean>()); } sent.get(propName).add(bean); } // prepare expected Object[] expected = new Object[propertyNames.length]; for (int i = 0; i < propertyNames.length; i++) { List<SupportBean> sentForType = sent.get(propertyNames[i]); if (arrayProp[i]) { expected[i] = sentForType == null ? null : sentForType.toArray(new SupportBean[0]); } else { if (match) { assertTrue(sentForType.size() == 1); expected[i] = sentForType.get(0); } } } if (match) { EventBean event = listener.assertOneGetNewAndReset(); EPAssertionUtil.assertProps(event, propertyNames, expected); } else { assertFalse("Failed at " + sequence, listener.isInvoked()); } } private String makeDefines(String[] props) { String delimiter = ""; StringWriter buf = new StringWriter(); for (String prop : props) { buf.append(delimiter); delimiter = ", "; buf.append(prop.toUpperCase(Locale.ENGLISH)); buf.append(" as "); buf.append(prop.toUpperCase(Locale.ENGLISH)); buf.append(".theString like \""); buf.append(prop.toUpperCase(Locale.ENGLISH)); buf.append("%\""); } return buf.toString(); } private String makeMeasures(String[] props) { String delimiter = ""; StringWriter buf = new StringWriter(); for (String prop : props) { buf.append(delimiter); delimiter = ", "; buf.append(prop.toUpperCase(Locale.ENGLISH)); buf.append(" as "); buf.append(prop); } return buf.toString(); } private SupportBean sendEvent(String theString, int intPrimitive) { SupportBean sb = new SupportBean(theString, intPrimitive); epService.getEPRuntime().sendEvent(sb); return sb; } protected static void runEquivalent(EPServiceProvider epService, String before, String after) throws Exception { String epl = "select * from SupportBean#keepall " + "match_recognize (" + " measures A as a" + " pattern (" + before + ")" + " define" + " A as A.theString like \"A%\"" + ")"; EPStatementObjectModel model = epService.getEPAdministrator().compileEPL(epl); EPStatementSPI spi = (EPStatementSPI) epService.getEPAdministrator().create(model); StatementSpecCompiled spec = ((EPServiceProviderSPI) (epService)).getStatementLifecycleSvc().getStatementSpec(spi.getStatementId()); RowRegexExprNode expanded = RegexPatternExpandUtil.expand(spec.getMatchRecognizeSpec().getPattern()); StringWriter writer = new StringWriter(); expanded.toEPL(writer, RowRegexExprNodePrecedenceEnum.MINIMUM); assertEquals(after, writer.toString()); epService.getEPAdministrator().destroyAllStatements(); } }