improve string bindings API and functionality

This commit is contained in:
Jonathan Shook 2022-02-15 21:23:46 -06:00
parent dde2eab49b
commit 35871718d5
11 changed files with 179 additions and 161 deletions

View File

@ -25,10 +25,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/** /**
* Maps a set of parameters on an associated object of type T to specifiers for data mappers. * Maps a set of parameters on an associated object of type T to specifiers for data mappers.
@ -38,14 +35,11 @@ import java.util.Optional;
* bindings will be used in. * bindings will be used in.
*/ */
public class BindingsTemplate { public class BindingsTemplate {
private final static Logger logger = LogManager.getLogger(BindingsTemplate.class); private final static Logger logger = LogManager.getLogger(BindingsTemplate.class);
private final Map<String, Object> fconfig; private final Map<String, Object> fconfig;
private List<String> bindPointNames = new ArrayList<>(); private final List<String> bindPointNames = new ArrayList<>();
private List<String> specifiers = new ArrayList<>(); private final List<String> specifiers = new ArrayList<>();
// public BindingsTemplate(Map<String,String> specs) {
// specs.forEach(this::addFieldBinding);
// }
public BindingsTemplate(Map<String,Object> config, List<String> anchors, List<String> specs) { public BindingsTemplate(Map<String,Object> config, List<String> anchors, List<String> specs) {
this.fconfig = config; this.fconfig = config;
@ -61,10 +55,10 @@ public class BindingsTemplate {
this.fconfig = config; this.fconfig = config;
addFieldBindings(bindpoints); addFieldBindings(bindpoints);
} }
public BindingsTemplate(List<BindPoint> bindPoints) { public BindingsTemplate(List<BindPoint> bindPoints) {
this.fconfig = Map.of(); this.fconfig = Map.of();
addFieldBindings(bindPoints); addFieldBindings(bindPoints);
} }
public BindingsTemplate(Map<String,Object> config) { public BindingsTemplate(Map<String,Object> config) {
@ -114,7 +108,7 @@ public class BindingsTemplate {
diaglog.append(diagnostics); diaglog.append(diagnostics);
if (mapperDiagnostics.getResolvedFunction().isPresent()) { if (mapperDiagnostics.getResolvedFunction().isPresent()) {
diaglog.append("☑ RESOLVED:") diaglog.append("☑ RESOLVED:")
.append(mapperDiagnostics.getResolvedFunction().get().toString()).append("\n"); .append(mapperDiagnostics.getResolvedFunction().get()).append("\n");
} else { } else {
diaglog.append("☐ UNRESOLVED\n"); diaglog.append("☐ UNRESOLVED\n");
} }
@ -190,4 +184,11 @@ public class BindingsTemplate {
return sb.toString(); return sb.toString();
} }
public Map<String, String> getMap() {
LinkedHashMap<String, String> bindmap = new LinkedHashMap<>();
for (int i = 0; i < this.specifiers.size(); i++) {
bindmap.put(this.bindPointNames.get(i),this.specifiers.get(i));
}
return bindmap;
}
} }

View File

@ -33,6 +33,7 @@ public class BindPointParser implements BiFunction<String, Map<String, String>,
List<String> spans = new ArrayList<>(); List<String> spans = new ArrayList<>();
List<BindPoint> bindpoints = new ArrayList<>(); List<BindPoint> bindpoints = new ArrayList<>();
int genid=0;
while (m.find()) { while (m.find()) {
String pre = template.substring(lastMatch, m.start()); String pre = template.substring(lastMatch, m.start());
spans.add(pre); spans.add(pre);
@ -67,6 +68,9 @@ public class BindPointParser implements BiFunction<String, Map<String, String>,
this.bindpoints = bindpoints; this.bindpoints = bindpoints;
} }
/**
* @return the spans of literal values which are between the bind points
*/
public List<String> getSpans() { public List<String> getSpans() {
return spans; return spans;
} }

View File

@ -130,9 +130,6 @@ public class ParsedTemplate {
*/ */
private final Map<String, String> bindings = new LinkedHashMap<>(); private final Map<String, String> bindings = new LinkedHashMap<>();
private final BindPointParser bindPointParser = new BindPointParser();
private final CapturePointParser capturePointParser = new CapturePointParser();
/** /**
* Parse the given raw template, check the bind points against the provide bindings, and * Parse the given raw template, check the bind points against the provide bindings, and
* provide detailed template checks for validity. * provide detailed template checks for validity.
@ -144,8 +141,11 @@ public class ParsedTemplate {
this.bindings.putAll(availableBindings); this.bindings.putAll(availableBindings);
this.rawtemplate = rawtemplate; this.rawtemplate = rawtemplate;
CapturePointParser capturePointParser = new CapturePointParser();
CapturePointParser.Result captureData = capturePointParser.apply(rawtemplate); CapturePointParser.Result captureData = capturePointParser.apply(rawtemplate);
this.captures = captureData.getCaptures(); this.captures = captureData.getCaptures();
BindPointParser bindPointParser = new BindPointParser();
BindPointParser.Result bindPointsResult = bindPointParser.apply(captureData.getRawTemplate(), availableBindings); BindPointParser.Result bindPointsResult = bindPointParser.apply(captureData.getRawTemplate(), availableBindings);
this.spans = bindPointsResult.getSpans().toArray(new String[0]); this.spans = bindPointsResult.getSpans().toArray(new String[0]);
this.bindpoints = bindPointsResult.getBindpoints(); this.bindpoints = bindPointsResult.getBindpoints();

View File

@ -1,25 +1,40 @@
package io.nosqlbench.virtdata.core.templates; package io.nosqlbench.virtdata.core.templates;
import io.nosqlbench.virtdata.core.bindings.Binder; import io.nosqlbench.virtdata.core.bindings.Binder;
import io.nosqlbench.virtdata.core.bindings.Bindings;
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate; import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
import java.util.Map;
/** /**
* Allows the generation of strings from a string template and bindings template. * Allows the generation of strings from a string template and bindings template.
*/ */
public class StringBindings implements Binder<String> { public class StringBindings implements Binder<String> {
private final StringCompositor compositor; private final StringCompositor compositor;
private final Bindings bindings;
public StringBindings(StringCompositor compositor, Bindings bindings) { public StringBindings(String template) {
this.compositor = compositor; this(template,Map.of(),Map.of());
this.bindings = bindings;
} }
public StringBindings(ParsedTemplate pt) { public StringBindings(String template, Map<String, String> bindings) {
this.compositor = new StringCompositor(pt); this(template,bindings,Map.of());
this.bindings = new BindingsTemplate(pt.getBindPoints()).resolveBindings(); }
public StringBindings(String template, Map<String,String> bindings, Map<String,Object> fconfig) {
ParsedTemplate parsed = new ParsedTemplate(template,bindings);
this.compositor = new StringCompositor(parsed, fconfig);
}
public StringBindings(ParsedTemplate parsedTemplate) {
this(parsedTemplate, Map.of());
}
public StringBindings(ParsedTemplate pt, Map<String,Object> fconfig) {
this.compositor = new StringCompositor(pt,fconfig);
}
public StringBindings(String stringTemplate, BindingsTemplate bindingsTemplate) {
this(stringTemplate,bindingsTemplate.getMap());
} }
/** /**
@ -30,15 +45,13 @@ public class StringBindings implements Binder<String> {
*/ */
@Override @Override
public String bind(long value) { public String bind(long value) {
String s = compositor.bindValues(compositor, bindings, value); return compositor.apply(value);
return s;
} }
@Override @Override
public String toString() { public String toString() {
return "StringBindings{" + return "StringBindings{" +
"compositor=" + compositor + "compositor=" + compositor +
", bindings=" + bindings +
'}'; '}';
} }
} }

View File

@ -2,10 +2,8 @@ package io.nosqlbench.virtdata.core.templates;
//import io.nosqlbench.engine.api.activityconfig.ParsedStmt; //import io.nosqlbench.engine.api.activityconfig.ParsedStmt;
//import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef; //import io.nosqlbench.engine.api.activityconfig.yaml.StmtDef;
import io.nosqlbench.virtdata.core.bindings.Bindings;
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
import java.util.HashSet; import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
/** /**
* Uses a string template and a bindings template to create instances of {@link StringBindings}. * Uses a string template and a bindings template to create instances of {@link StringBindings}.
@ -20,55 +18,12 @@ public class StringBindingsTemplate {
this.bindingsTemplate = bindingsTemplate; this.bindingsTemplate = bindingsTemplate;
} }
// /**
// * Build a default string bindings template using the standard representation
// * for a string template in NoSQLBench, which is a literal string interspersed
// * with named anchors in {@code {{curlybraces}}} form.
// * @param stmtDef A stmtDef
// */
// public StringBindingsTemplate(StmtDef stmtDef) {
// this(stmtDef, s->"{{"+s+"}}");
// }
//
// /**
// * Build a string bindings template using a custom representation that maps
// * the named anchors to a different form than the default {@code {{curlybraces}}} form.
// * The mapping function provides the textual substitution which is used to composite
// * the normative representation of the statement.
// * @param stmtdef The {@link StmtDef} which provides the bindpoints
// * @param tokenMapper A custom named anchor formatting function
// */
// public StringBindingsTemplate(StmtDef stmtdef, Function<String,String> tokenMapper) {
// ParsedStmt parsedStmt = stmtdef.getParsed().orError();
// this.stringTemplate = parsedStmt.getPositionalStatement(tokenMapper);
// this.bindingsTemplate = new BindingsTemplate(parsedStmt.getBindPoints());
// }
/** /**
* Create a new instance of {@link StringBindings}, preferably in the thread context that will use it. * Create a new instance of {@link StringBindings}, preferably in the thread context that will use it.
* @return a new StringBindings * @return a new StringBindings
*/ */
public StringBindings resolve() { public StringBindings resolve() {
return new StringBindings(stringTemplate,bindingsTemplate);
StringCompositor compositor = new StringCompositor(stringTemplate);
HashSet<String> unqualifiedNames = new HashSet<>(compositor.getBindPointNames());
unqualifiedNames.removeAll(new HashSet<>(bindingsTemplate.getBindPointNames()));
if (unqualifiedNames.size()>0) {
throw new RuntimeException("Named anchors were specified in the template which were not provided in the bindings: " + unqualifiedNames);
}
Bindings bindings = bindingsTemplate.resolveBindings();
return new StringBindings(compositor,bindings);
}
public String getDiagnostics() {
StringCompositor compositor = new StringCompositor(stringTemplate);
HashSet<String> unqualifiedNames = new HashSet<>(compositor.getBindPointNames());
unqualifiedNames.removeAll(new HashSet<>(bindingsTemplate.getBindPointNames()));
if (unqualifiedNames.size()>0) {
throw new RuntimeException("Named anchors were specified in the template which were not provided in the bindings: " + unqualifiedNames);
}
return bindingsTemplate.getDiagnostics();
} }
@Override @Override

View File

@ -1,74 +1,73 @@
package io.nosqlbench.virtdata.core.templates; package io.nosqlbench.virtdata.core.templates;
import io.nosqlbench.virtdata.core.bindings.ValuesBinder; import io.nosqlbench.virtdata.core.bindings.DataMapper;
import io.nosqlbench.virtdata.core.bindings.Bindings; import io.nosqlbench.virtdata.core.bindings.VirtData;
import java.util.ArrayList; import java.util.HashMap;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.LongFunction;
/** /**
* StringCompositor provides a way to build strings from a string template and provided values. * This implementation of a string compositor takes a logically coherent
* * string template and bindings set. It employs a few simplistic optimizations
* <p> * to avoid re-generating duplicate values, as well as lower allocation
* The template is simply an array of string values, where odd indices represent token positions, and even indices represent * rate of buffer data.
* literals. This version of the StringCompositor fetches data from the bindings only for the named fields in the template.
* </p>
*/ */
public class StringCompositor implements ValuesBinder<StringCompositor, String> { public class StringCompositor implements LongFunction<String> {
private final String[] templateSegments; private final String[] spans;
private Function<Object, String> stringfunc = String::valueOf; private final DataMapper<?>[] mappers;
private final int[] LUT;
private final int bufsize;
/** private final Function<Object, String> stringfunc;
* Create a string template which has positional tokens, in "{}" form.
*
* @param template The string template
*/
public StringCompositor(String template) {
templateSegments = parseTemplate(template);
}
public StringCompositor(String template, Function<Object, String> stringfunc) { public StringCompositor(ParsedTemplate template, Map<String,Object> fconfig, Function<Object,String> stringfunc) {
this(template); Map<String,Integer> specs = new HashMap<>();
List<BindPoint> bindpoints = template.getBindPoints();
for (BindPoint bindPoint : bindpoints) {
String spec = bindPoint.getBindspec();
specs.compute(spec,(s,i) -> i==null ? specs.size() : i);
}
mappers = new DataMapper<?>[specs.size()];
specs.forEach((k,v) -> {
mappers[v]= VirtData.getOptionalMapper(k,fconfig).orElseThrow();
});
String[] even_odd_spans = template.getSpans();
this.spans = new String[bindpoints.size()+1];
LUT = new int[bindpoints.size()];
for (int i = 0; i < bindpoints.size(); i++) {
spans[i]=even_odd_spans[i<<1];
LUT[i]=specs.get(template.getBindPoints().get(i).getBindspec());
}
spans[spans.length-1]=even_odd_spans[even_odd_spans.length-1];
this.stringfunc = stringfunc; this.stringfunc = stringfunc;
int minsize = 0;
for (int i = 0; i < 100; i++) {
String result = apply(i);
minsize = Math.max(minsize,result.length());
}
bufsize = minsize*2;
} }
public StringCompositor(ParsedTemplate pt) { public StringCompositor(ParsedTemplate template, Map<String,Object> fconfig) {
templateSegments = pt.getSpans(); this(template,fconfig,Object::toString);
} }
// for testing
protected String[] parseTemplate(String template) {
ParsedTemplate parsed = new ParsedTemplate(template, Collections.emptyMap());
return parsed.getSpans();
}
@Override @Override
public String bindValues(StringCompositor context, Bindings bindings, long cycle) { public String apply(long value) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder(bufsize);
for (int i = 0; i < templateSegments.length; i++) { String[] ary = new String[mappers.length];
if (i % 2 == 0) { for (int i = 0; i < ary.length; i++) {
sb.append(templateSegments[i]); ary[i] = stringfunc.apply(mappers[i].apply(value));
} else {
String key = templateSegments[i];
Object value = bindings.get(key, cycle);
String valueString = stringfunc.apply(value);
sb.append(valueString);
}
} }
for (int i = 0; i < LUT.length; i++) {
sb.append(spans[i]).append(ary[LUT[i]]);
}
sb.append(spans[spans.length-1]);
return sb.toString(); return sb.toString();
} }
public List<String> getBindPointNames() {
List<String> tokens = new ArrayList<>();
for (int i = 0; i < templateSegments.length; i++) {
if (i % 2 == 1) {
tokens.add(templateSegments[i]);
}
}
return tokens;
}
} }

View File

@ -0,0 +1,20 @@
package io.nosqlbench.virtdata.core.templates;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Map;
public class FastStringCompositorTest {
@Test
@Disabled // Needs to have annotation processor run in test scope first
public void testFastStringCompositor() {
String rawTpl = "template {b1}, {{TestValue(5)}}";
Map<String, String> bindings = Map.of("b1", "TestIdentity()");
ParsedTemplate ptpl = new ParsedTemplate(rawTpl, bindings);
StringCompositor fsc = new StringCompositor(ptpl,Map.of());
System.out.println(fsc);
}
}

View File

@ -2,22 +2,22 @@ package io.nosqlbench.virtdata.core.templates;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
public class StringCompositorTest { public class StringCompositorTest {
@Test @Test
public void testShouldMatchSpanOnly() { public void testShouldMatchSpanOnly() {
StringCompositor c = new StringCompositor("A"); ParsedTemplate pt = new ParsedTemplate("A\\{ {one}two", Map.of());
String[] spans = c.parseTemplate("A\\{ {one}two"); assertThat(pt.getSpans()).containsExactly("A\\{ ", "one", "two");
assertThat(spans).containsExactly("A\\{ ", "one", "two");
} }
@Test @Test
public void testShouldNotMatchEscaped() { public void testShouldNotMatchEscaped() {
StringCompositor c = new StringCompositor("A"); ParsedTemplate pt = new ParsedTemplate("A\\{{B}C",Map.of());
String[] spans = c.parseTemplate("A\\{{B}C"); assertThat(pt.getSpans()).containsExactly("A\\{","B","C");
assertThat(spans).containsExactly("A\\{","B","C");
} }
// @Test // @Test

View File

@ -0,0 +1,14 @@
package io.nosqlbench.virtdata.core.templates;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.util.function.LongFunction;
@ThreadSafeMapper
public class TestIdentity implements LongFunction<Object> {
@Override
public Object apply(long value) {
return value;
}
}

View File

@ -0,0 +1,19 @@
package io.nosqlbench.virtdata.core.templates;
import io.nosqlbench.virtdata.api.annotations.ThreadSafeMapper;
import java.util.function.LongFunction;
@ThreadSafeMapper
public class TestValue implements LongFunction<Object> {
private final Object value;
public TestValue(Object value) {
this.value = value;
}
@Override
public Object apply(long value) {
return this.value;
}
}

View File

@ -2,15 +2,13 @@ package io.virtdata;
import io.nosqlbench.virtdata.core.bindings.Bindings; import io.nosqlbench.virtdata.core.bindings.Bindings;
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate; import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
import io.nosqlbench.virtdata.core.templates.StringCompositor; import io.nosqlbench.virtdata.core.templates.StringBindings;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
public class IntegratedStringCompositorTest { public class IntegratedStringBindingsTest {
private static BindingsTemplate template; private static BindingsTemplate template;
private static Bindings bindings; private static Bindings bindings;
@ -27,41 +25,36 @@ public class IntegratedStringCompositorTest {
bindings = bindingsTemplate.resolveBindings(); bindings = bindingsTemplate.resolveBindings();
} }
@Test
public void testEven() {
StringBindings c = new StringBindings("A{ident}B{ident}C{mod5}D{mod-5}",template);
String bind3 = c.bind(3);
assertThat(bind3).isEqualTo("A3B3C3D3");
}
@Test
public void testOdd() {
StringBindings c = new StringBindings("A{ident}B{ident}C{mod5}D{mod-5}E",template);
String bind3 = c.bind(7);
assertThat(bind3).isEqualTo("A7B7C2D2E");
}
@Test @Test
public void testBindValues() { public void testBindValues() {
StringCompositor c = new StringCompositor("A{ident}C"); StringBindings c = new StringBindings("A{ident}C", template);
String s = c.bindValues(c, bindings, 0L); String s = c.apply(0);
assertThat(s).isEqualTo("A0C"); assertThat(s).isEqualTo("A0C");
} }
@Test @Test
public void testBindValuesSpecialChars() { public void testBindValuesSpecialChars() {
StringCompositor c = new StringCompositor("A{mod-5}C"); StringBindings c = new StringBindings("A{mod-5}C", template);
String s = c.bindValues(c, bindings, 6L); String s = c.apply(6L);
assertThat(s).isEqualTo("A1C"); assertThat(s).isEqualTo("A1C");
c = new StringCompositor("A{5_mod_5}C"); c = new StringBindings("A{5_mod_5}C", template);
s = c.bindValues(c, bindings, 7L); s = c.apply(7L);
assertThat(s).isEqualTo("A2C"); assertThat(s).isEqualTo("A2C");
// c = new StringCompositor("A{.mod5}C");
// s = c.bindValues(c, bindings, 8L);
// assertThat(s).isEqualTo("A3C");
}
// @Test
// public void testBindEscapedAnchor() {
// StringCompositor c = new StringCompositor("A\\{{mod-5}C");
// String s = c.bindValues(c, bindings, 6L);
// assertThat(s).isEqualTo("A{1C");
// }
@Test
public void testBindCustomTransform() {
Function<Object,String> f = (o) -> "'" + o.toString() + "'";
StringCompositor c = new StringCompositor("A{mod5}C", f);
String s = c.bindValues(c, bindings, 13L);
assertThat(s).isEqualTo("A'3'C");
} }
} }