Repository: metron Updated Branches: refs/heads/feature/METRON-1090-stellar-assignment 0e037edad -> 3df949877
http://git-wip-us.apache.org/repos/asf/metron/blob/3df94987/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/DefaultVariableResolver.java ---------------------------------------------------------------------- diff --git a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/DefaultVariableResolver.java b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/DefaultVariableResolver.java index fc2c2b7..1ac34d5 100644 --- a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/DefaultVariableResolver.java +++ b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/DefaultVariableResolver.java @@ -1,44 +1,75 @@ /** - * 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 + * 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 + * 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. + * 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.metron.stellar.dsl; +import java.util.function.BiConsumer; import java.util.function.Function; -public class DefaultVariableResolver implements VariableResolver{ - Function<String,Object> resolveFunc; - Function<String,Boolean> existsFunc; +/** + * Simple VariableResolver implemenation using passed Functions + * for implementation. + * + * Support for updates is optional + */ +public class DefaultVariableResolver implements VariableResolver { - public DefaultVariableResolver(Function<String,Object> resolveFunc, Function<String,Boolean> existsFunc){ + private Function<String, Object> resolveFunc; + private Function<String, Boolean> existsFunc; + private BiConsumer<String, Object> updateFunc; + + /** + * DefaultVariableResolver without support for updates + * @param resolveFunc + * @param existsFunc + */ + public DefaultVariableResolver(Function<String, Object> resolveFunc, + Function<String, Boolean> existsFunc) { + this(resolveFunc, existsFunc, null); + } + + /** + * DefaultVariableResolver with full support for updates + * @param resolveFunc + * @param existsFunc + * @param updateFunc + */ + public DefaultVariableResolver(Function<String, Object> resolveFunc, + Function<String, Boolean> existsFunc, BiConsumer<String, Object> updateFunc) { this.resolveFunc = resolveFunc; this.existsFunc = existsFunc; + this.updateFunc = updateFunc; } + @Override public Object resolve(String variable) { - return resolveFunc.apply(variable); + return resolveFunc == null? null : resolveFunc.apply(variable); } @Override public boolean exists(String variable) { - return existsFunc.apply(variable); + return existsFunc == null? false : existsFunc.apply(variable); } - public static DefaultVariableResolver NULL_RESOLVER() { - return new DefaultVariableResolver(x -> null, x -> false); + @Override + public void update(String variable, Object value) { + if (updateFunc != null) { + updateFunc.accept(variable, value); + } } + + public static DefaultVariableResolver NULL_RESOLVER = new DefaultVariableResolver(x -> null, + x -> false, null); } http://git-wip-us.apache.org/repos/asf/metron/blob/3df94987/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/MapVariableResolver.java ---------------------------------------------------------------------- diff --git a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/MapVariableResolver.java b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/MapVariableResolver.java index 872211d..e057975 100644 --- a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/MapVariableResolver.java +++ b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/MapVariableResolver.java @@ -66,4 +66,9 @@ public class MapVariableResolver implements VariableResolver { public boolean exists(String variable) { return true; } + + @Override + public void update(String variable, Object value) { + // not supported, but could be useful for counters? + } } http://git-wip-us.apache.org/repos/asf/metron/blob/3df94987/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/VariableResolver.java ---------------------------------------------------------------------- diff --git a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/VariableResolver.java b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/VariableResolver.java index fb95d27..d5edf3a 100644 --- a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/VariableResolver.java +++ b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/VariableResolver.java @@ -19,8 +19,35 @@ package org.apache.metron.stellar.dsl; +/** + * VariableResolver implementors provide support variable operations. + * <ul> + * <li>Verifying the exists of a variable by name</li> + * <li>Returning the value of a variable by name</li> + * <li>Updating the value of a variable by name</li> + * </ul> + */ public interface VariableResolver { public static final String ALL_FIELDS = "_"; + + /** + * Returns the value of a variable. + * @param variable the variable name + * @return the value Object + */ Object resolve(String variable); + + /** + * Returns the existance of the variable. + * @param variable the variable name + * @return true if the variable exists, false otherwise + */ boolean exists(String variable); + + /** + * Updates the value of a variable. + * @param variable the variable name + * @param value the value to update with + */ + void update(String variable, Object value); } http://git-wip-us.apache.org/repos/asf/metron/blob/3df94987/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java ---------------------------------------------------------------------- diff --git a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java index dec05a8..28f1ae4 100644 --- a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java +++ b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/BasicStellarTest.java @@ -19,8 +19,10 @@ package org.apache.metron.stellar.dsl.functions; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import java.text.DecimalFormat; import org.apache.commons.lang3.StringUtils; import org.apache.metron.stellar.common.StellarProcessor; import org.apache.metron.stellar.dsl.Context; @@ -115,6 +117,642 @@ public class BasicStellarTest { } @Test + public void testAssign(){ + String query = "foo = 1"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + query = "if foo == 1 then foo = foo + 1 else foo = foo - 1"; + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count = count + 1 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 0); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(15), (Integer)map.get("count")); + } + + // can we assign one variable to another? + { + String expr = "foo = bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(999,run(expr,map)); + Assert.assertEquals(999,map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo = bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + Assert.assertEquals("message",run(expr,map)); + Assert.assertEquals("message",map.get("foo")); + } + + } + + @Test + public void testColonAssign(){ + String query = "foo := 1"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + query = "if foo == 1 then foo := foo + 1 else foo := foo - 1"; + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count := count + 1 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 0); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(15), (Integer)map.get("count")); + } + + // can we assign one variable to another? + { + String expr = "foo := bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(999,run(expr,map)); + Assert.assertEquals(999,map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo := bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + Assert.assertEquals("message",run(expr,map)); + Assert.assertEquals("message",map.get("foo")); + } + + } + + @Test + public void testMixedAssign(){ + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",1); + }}; + + // test more complex, until we get += + // the else is required.... + String query = "if foo == 1 then foo := foo + 1 else foo = foo - 1"; + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + } + + + @Test + public void testPlusAssign(){ + String query = "foo += 1"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + query = "if foo == 2 then foo += 1 else foo -= 1"; + Assert.assertEquals(3, run(query,variables)); + Assert.assertEquals(3, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count += 1 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 0); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(15), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo += bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(999,run(expr,map)); + Assert.assertEquals(999,map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo += bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric assignment value")); + } + Assert.assertTrue(thrown); + } + + + } + + @Test + public void testMinusAssign(){ + String query = "foo -= 1"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(-1, run(query,variables)); + Assert.assertEquals(-1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(-2, run(query,variables)); + Assert.assertEquals(-2, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + variables.put("foo",2); + query = "if foo == 2 then foo -= 1 else foo += 1"; + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count -= 1 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 15); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(0), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo -= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(-999,run(expr,map)); + Assert.assertEquals(-999,map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo -= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric assignment value")); + } + Assert.assertTrue(thrown); + } + } + + @Test + public void testMultiplyAssign(){ + String query = "foo *= 2"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(0, run(query,variables)); + Assert.assertEquals(0, variables.get("foo")); + variables.put("foo",2); + // and if it already exists + Assert.assertEquals(4, run(query,variables)); + Assert.assertEquals(4, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + query = "if foo == 4 then foo *= 2 else foo"; + Assert.assertEquals(8, run(query,variables)); + Assert.assertEquals(8, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count *= 2 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 1); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new DecimalFormat("#").format(Math.pow(2,15)), map.get("count").toString()); + } + + // can we assign one variable to another? + { + String expr = "foo *= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",2); + put("bar",999); + }}; + Assert.assertEquals((2*999),run(expr,map)); + Assert.assertEquals((2*999),map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo *= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo", null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric assignment value")); + } + Assert.assertTrue(thrown); + } + } + + @Test + public void testDivideAssign(){ + String query = "foo /= 2"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(0, run(query,variables)); + Assert.assertEquals(0, variables.get("foo")); + variables.put("foo",2); + // and if it already exists + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + + // test more complex, until we get += + // the else is required.... + variables.put("foo",4); + query = "if foo == 4 then foo /= 2 else foo"; + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count /= 2 )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", Math.pow(2,15)); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(1.0, map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo /= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",(999*2)); + put("bar",999); + }}; + Assert.assertEquals(2,run(expr,map)); + Assert.assertEquals(2,map.get("foo")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo /= bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",10); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric assignment value")); + } + Assert.assertTrue(thrown); + } + } + + @Test + public void testPreIncrement(){ + String query = "++foo"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + query = "if foo == 2 then ++foo else foo -= 1"; + Assert.assertEquals(3, run(query,variables)); + Assert.assertEquals(3, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> ++count )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 0); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(15), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo = ++bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(1000,run(expr,map)); + Assert.assertEquals(1000,map.get("foo")); + Assert.assertEquals(1000,map.get("bar")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo = ++bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric pre-increment")); + } + Assert.assertTrue(thrown); + } + } + + @Test + public void testPreDecrement(){ + String query = "--foo"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(-1, run(query,variables)); + Assert.assertEquals(-1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(-2, run(query,variables)); + Assert.assertEquals(-2, variables.get("foo")); + + query = "if foo == -2 then --foo else foo -= 1"; + Assert.assertEquals(-3, run(query,variables)); + Assert.assertEquals(-3, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> --count )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 15); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(0), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo = --bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",1001); + }}; + Assert.assertEquals(1000,run(expr,map)); + Assert.assertEquals(1000,map.get("foo")); + Assert.assertEquals(1000,map.get("bar")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo = --bar"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric pre-decrement")); + } + Assert.assertTrue(thrown); + } + } + + + + @Test + public void testPostIncrement(){ + String query = "foo++"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(0, run(query,variables)); + Assert.assertEquals(1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(1, run(query,variables)); + Assert.assertEquals(2, variables.get("foo")); + + query = "if foo == 2 then foo++ else foo -= 1"; + Assert.assertEquals(2, run(query,variables)); + Assert.assertEquals(3, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count++ )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 0); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(15), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo = bar++"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",999); + }}; + Assert.assertEquals(999,run(expr,map)); + Assert.assertEquals(999,map.get("foo")); + Assert.assertEquals(1000,map.get("bar")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo = bar++"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric post-increment")); + } + Assert.assertTrue(thrown); + } + } + + @Test + public void testPostDecrement(){ + String query = "foo--"; + Map<String,Object> variables = new HashMap<String,Object>(){{ + put("foo",null); + }}; + + // basics, return the assign, set the var with default resolver + Assert.assertEquals(0, run(query,variables)); + Assert.assertEquals(-1, variables.get("foo")); + // and if it already exists + Assert.assertEquals(-1, run(query,variables)); + Assert.assertEquals(-2, variables.get("foo")); + + query = "if foo == -2 then foo-- else foo -= 1"; + Assert.assertEquals(-2, run(query,variables)); + Assert.assertEquals(-3, variables.get("foo")); + + // does it work in a lambda if we explicitly use var name? + { + String expr = "MAP([ foo, bar, baz ], (item) -> count-- )"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",1); + put("bar",1); + put("baz",1); + put("count", 15); + }}; + for(int i = 0 ; i < 5; i++){ + run(expr, map); + } + Assert.assertEquals(new Integer(0), (Integer)map.get("count")); + } + // can we assign one variable to another? + { + String expr = "foo = bar--"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar",1001); + }}; + Assert.assertEquals(1001,run(expr,map)); + Assert.assertEquals(1001,map.get("foo")); + Assert.assertEquals(1000,map.get("bar")); + } + + // can we assign one variable to another as a string? + { + String expr = "foo = bar--"; + Map<String,Object> map = new HashMap<String,Object>(){{ + put("foo",null); + put("bar","message"); + }}; + boolean thrown = false; + try { + run(expr, map); + } catch (ParseException pe) { + thrown = true; + Assert.assertTrue(pe.getMessage().contains("Invalid operation, Number type required for numeric post-decrement")); + } + Assert.assertTrue(thrown); + } + } + + @Test public void testEscapedLiterals() { Assert.assertEquals("'bar'", run("\"'bar'\"", new HashMap<>())); Assert.assertEquals("'BAR'", run("TO_UPPER('\\'bar\\'')", new HashMap<>()));