Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import oap.testng.Fixtures;
import oap.testng.TestDirectoryFixture;
import oap.util.Dates;
import org.apache.commons.lang3.mutable.MutableObject;
import org.testng.annotations.Test;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -67,12 +68,12 @@ public void testLog() {
default = 1233
}
aaa {
path = a | default aa
path = a|aa
type = STRING
default = ""
}
list {
path = data1.list | default data2.list
path = data1.list|data2.list
type = STRING_ARRAY
default = []
}
Expand Down Expand Up @@ -129,7 +130,7 @@ public void testOptimization() {
default = ""
}
or {
path = "if a then a else aa end"
path = "a|aa"
type = STRING
default = ""
}
Expand Down Expand Up @@ -167,14 +168,21 @@ public void testOptimization() {

logger.log( testData, "prefix", Map.of(), "mylog" );

List<List<Object>> bytes = memoryLoggerBackend.asRowBinary( _ -> true );
MutableObject<String[]> headers = new MutableObject<>();

List<List<Object>> bytes = memoryLoggerBackend.asRowBinary( lid -> {
headers.setValue( lid.headers );
return true;
} );

assertThat( headers.get() ).isEqualTo( new String[] {"a", "or", "b", "a2", "aa2", "b2"} );

assertThat( bytes ).isEqualTo( List.of( List.of( "a1", 1, "a1", "a2", "aa2", 2 ) ) );
assertThat( bytes ).isEqualTo( List.of( List.of( "a1", "a1", 1, "a2", "aa2", 2 ) ) );

assertThat( listener.javaCode )
.isEqualTo( "{{ /* model MODEL1 id a path a type STRING defaultValue '' */<java.lang.String>a ?? \"\" }}"
+ "{{ /* model MODEL1 id b path b type INTEGER defaultValue '0' */<java.lang.Integer>b ?? 0 }}"
+ "{{ /* model MODEL1 id or path if a then a else aa end type STRING defaultValue '' */<java.lang.String>if a then a else aa end ?? \"\" }}"
+ "{{ /* model MODEL1 id b path b type INTEGER defaultValue '0' */<java.lang.Integer>b ?? 0 }}"
+ "{{% with subData }}"
+ "{{ /* model MODEL1 id a2 path subData.a type STRING defaultValue '' */<java.lang.String>a ?? \"\" }}"
+ "{{ /* model MODEL1 id aa2 path subData.aa type STRING defaultValue '' */<java.lang.String>aa ?? \"\" }}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package oap.logstream.formats.rowbinary;

import com.google.common.base.Preconditions;
import lombok.experimental.ExtensionMethod;
import oap.dictionary.Dictionary;
import oap.dictionary.DictionaryRoot;
import oap.logstream.AbstractLoggerBackend;
Expand All @@ -47,13 +48,15 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
import static oap.template.ErrorStrategy.ERROR;

/**
* Class for beans described in the datamodel via oap-logstream protocol.
*/
@ExtensionMethod( RowBinaryObjectLogger.DictionaryExtensions.class )
public class RowBinaryObjectLogger {
public static final String COLLECTION_SUFFIX = "_ARRAY";
public static final HashMap<String, TypeConfiguration> types = new HashMap<>();
Expand Down Expand Up @@ -105,49 +108,50 @@ public <D> TypedRowBinaryLogger<D> typed( TypeRef<D> typeRef, String id, boolean

if( sortByPath ) {
fields.sort( ( o1, o2 ) -> {
String path1 = o1.<String>getProperty( "path" ).get();
String path2 = o2.<String>getProperty( "path" ).get();

boolean or1 = path1.contains( "|" );
boolean or2 = path2.contains( "|" );

if( or1 || or2 ) {
return Boolean.compare( or1, or2 );
}
String path1 = o1.getPath();
String path2 = o2.getPath();

return path1.compareTo( path2 );
} );
}

List<Dictionary> rootFields = new ArrayList<>();
LinkedHashMap<String, Dictionary> rootFields = new LinkedHashMap<>();
Map<String, List<Dictionary>> groups = new LinkedHashMap<>();

for( Dictionary field : fields ) {
String path = field.<String>getProperty( "path" ).get();
boolean isOr = path.contains( "|" );
int dotIdx = isOr ? -1 : path.indexOf( '.' );
if( dotIdx < 0 ) {
rootFields.add( field );
String path = field.getPath();
int dotIdx = path.indexOf( '.' );
int orIndex = path.indexOf( '|' );
if( orIndex > 0 ) {
String left = path.substring( 0, orIndex );
String right = path.substring( orIndex + 1 );

int lastFieldIndex = left.lastIndexOf( '.' );

rootFields.put( "if " + ( lastFieldIndex > 0 ? left.substring( 0, lastFieldIndex ) : left ) + " then " + left + " else " + right + " end", field );
} else if( dotIdx < 0 ) {
rootFields.put( path, field );
} else {
groups.computeIfAbsent( path.substring( 0, dotIdx ), k -> new ArrayList<>() ).add( field );
}
}

for( Dictionary field : rootFields ) {
appendField( field, id, null, headers, rowTypes, expressions );
}
rootFields.forEach( ( path, field ) -> {
appendField( path, field, id, null, headers, rowTypes, expressions );
} );

for( Map.Entry<String, List<Dictionary>> entry : groups.entrySet() ) {
String prefix = entry.getKey();
List<Dictionary> group = entry.getValue();
if( group.size() >= 2 ) {
expressions.add( "{{% with " + prefix + " }}" );
for( Dictionary field : group ) {
appendField( field, id, prefix, headers, rowTypes, expressions );
appendField( field.getPath(), field, id, prefix, headers, rowTypes, expressions );
}
expressions.add( "{{% end }}" );
} else {
appendField( group.getFirst(), id, null, headers, rowTypes, expressions );
Dictionary first = group.getFirst();
appendField( first.getPath(), first, id, null, headers, rowTypes, expressions );
}
}

Expand All @@ -166,12 +170,11 @@ public <D> TypedRowBinaryLogger<D> typed( TypeRef<D> typeRef, String id, boolean
return new TypedRowBinaryLogger<>( renderer, headers.toArray( new String[0] ), rowTypes.toArray( new byte[0][] ) );
}

private void appendField( Dictionary field, String id, @Nullable String stripPrefix,
private void appendField( String path, Dictionary field, String id, @Nullable String stripPrefix,
List<String> headers, List<byte[]> rowTypes, List<String> expressions ) {
String name = field.getId();
String path = checkStringAndGet( field, "path" );
String fieldType = checkStringAndGet( field, "type" );
Object format = field.getProperty( "format" ).orElse( null );
Object format = field.getFormat();

boolean collection = false;
String idType = fieldType;
Expand All @@ -183,7 +186,7 @@ private void appendField( Dictionary field, String id, @Nullable String stripPre
TypeConfiguration rowType = types.get( idType );
Preconditions.checkNotNull( rowType, "unknown type " + idType );

Object defaultValue = field.getProperty( "default" )
Object defaultValue = field.getDefault()
.orElseThrow( () -> new IllegalStateException( "default not found for " + id + "/" + name ) );

String templateFunction = format != null ? "; format(\"" + format + "\")" : "";
Expand Down Expand Up @@ -234,6 +237,21 @@ public TypeConfiguration( String javaType, Types templateType ) {
}
}

public static class DictionaryExtensions {
public static String getPath( Dictionary dictionary ) {
return dictionary.<String>getProperty( "path" ).get();
}

@Nullable
public static String getFormat( Dictionary dictionary ) {
return dictionary.<String>getProperty( "format" ).orElse( null );
}

public static Optional<Object> getDefault( Dictionary dictionary ) {
return dictionary.getProperty( "default" );
}
}

public class TypedRowBinaryLogger<D> {
public final String[] headers;
public final byte[][] types;
Expand Down
24 changes: 3 additions & 21 deletions oap-formats/oap-template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ A compile-time template engine for the OAP framework. Each unique template strin
- [Field access](#field-access)
- [Null safety](#null-safety)
- [Default values (`??`)](#default-values-)
- [Fallback chains (`| default`)](#fallback-chains--default)
- [Concatenation](#concatenation)
- [Math](#math)
- [If / then / else (inline)](#if--then--else-inline)
Expand Down Expand Up @@ -193,15 +192,6 @@ Supported literal types:
{{ tags ?? [] }}
```

### Fallback chains (`| default`)

`{{ expr1 | default expr2 | default expr3 }}` — evaluates each expression in order and uses the first non-null, non-empty result. All expressions must resolve to the same type.

```
{{ primaryUrl | default fallbackUrl }}
{{ list | default list2 }}
```

### Concatenation

`+` is the string concatenation operator. All items are rendered as strings and joined without any separator character between them. Items can be: field names, double-quoted strings, single-quoted strings, decimal integers, floats.
Expand Down Expand Up @@ -425,21 +415,13 @@ If `scope` resolves to null, the body expression renders its default value, or e

Renders `n/a` when `child` is null or `child.field` is null.

**Fallback chain in body:**

```
{{ child{field | default field2} }}
```

Both alternatives are evaluated against `child` (expanded to `child.field | default child.field2`); the first non-null result is used.

**Root scope (`$`):** prefix any body expression with `$.` to resolve it from the root object instead of the scope:

```
{{ child{field | default $.rootField} }}
{{ child{$.rootField} }}
```

When `child.field` is null, `$.rootField` is resolved from the root object.
Resolves `rootField` from the root object, not from `child`.

**Concatenation inside scope:** when `+` separates two or more items inside `{}`, the result is a string concatenation of all items rendered in the context of `scope`:

Expand Down Expand Up @@ -476,7 +458,7 @@ Renders `AnoneB` when `child` is null. Renders `A` + field value + `B` when `chi

- `scopePath` is a field path (e.g. `child`, `a.b`). All `{{ expr }}` expressions inside the body are resolved against the type at that path.
- `$.fieldName` inside the body always resolves from the original root object regardless of nesting depth.
- Use `??` for literal defaults inside body expressions (`{{ field ?? 'default' }}`). Or-chain fallbacks between two fields (`{{ field | default field2 }}`) are not supported inside block-with bodies.
- Use `??` for literal defaults inside body expressions (`{{ field ?? 'default' }}`).
- Blocks may be nested inside other block constructs (`{{% if … }}`, other `{{% with … }}`).
- The `{{% end }}` tag closes the nearest open block.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ exprsCode returns [ArrayList<Exprs> ret = new ArrayList<>()]
: topLevelConcat {
$ret.add( $topLevelConcat.ret );
}
| exprs orExprs {
| exprs {
$ret.add( $exprs.ret );
$ret.addAll( $orExprs.ret );
}
;

Expand Down Expand Up @@ -169,11 +168,6 @@ functionArg returns [String ret]
| DSTRING { $ret = $DSTRING.text; }
;

orExprs returns [ArrayList<Exprs> ret = new ArrayList<Exprs>() ]
: (DEFAULT exprs { $ret.add( $exprs.ret ); } ( DEFAULT exprs { $ret.add( $exprs.ret ); })*)
|
;

exprs returns [Exprs ret = new Exprs()]
: ROOT DOT expr { $ret.rootScoped = true; $ret.exprs.add( $expr.ret ); }
(DOT expr { $ret.exprs.add( $expr.ret ); })*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ fragment DQuote : '"' ;
fragment Underscore : '_' ;
fragment Comma : ',' ;
fragment Semi : ';' ;
fragment Pipe : '|' ;
fragment Dot : '.' ;
fragment LParen : '(' ;
fragment RParen : ')' ;
Expand All @@ -29,8 +28,6 @@ fragment Minus : '-' ;
fragment DQuestion : '??' ;
fragment LT : '<' ;
fragment GT : '>' ;
fragment Default : 'default' ;

fragment NameChar
: [A-Z]
| [a-z]
Expand Down Expand Up @@ -79,8 +76,6 @@ fragment True : 'true' ;
fragment False : 'false' ;


DEFAULT : Pipe Hws* Default ;

IF : 'if' ;
THEN : 'then' ;
ELSE : 'else' ;
Expand Down Expand Up @@ -145,7 +140,6 @@ C_VERT_WS : Vws+ -> skip ;
CRBRACE : RBrace -> popMode, type(RBRACE) ;
CPLUS : Plus -> type(PLUS) ;
CDOT : Dot -> type(DOT) ;
CDEFAULT : Pipe Hws* Default -> type(DEFAULT) ;
CVAR_ID : '$' NameChar (NameChar|DecDigit)* -> type(VAR_ID) ;
CROOT : '$' -> type(ROOT) ;

Expand Down
Loading
Loading