Skip to content

Commit

Permalink
⭐ introduce empty (#1884)
Browse files Browse the repository at this point in the history
Avoids having to double-check values, instead you can simply say:

```coffee
[] == empty
null == empty
'' == empty
{} == empty
```

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Sep 25, 2023
1 parent d0ef6ec commit cdb057a
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 50 deletions.
2 changes: 1 addition & 1 deletion cli/printer/mql.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ func (print *Printer) autoExpand(blockRef uint64, data interface{}, bundle *llx.
}

func (print *Printer) Data(typ types.Type, data interface{}, codeID string, bundle *llx.CodeBundle, indent string) string {
if typ.IsEmpty() {
if typ.NotSet() {
return "no data available"
}

Expand Down
97 changes: 61 additions & 36 deletions llx/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func init() {
string("!=" + types.Time): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Dict): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.Dict): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Empty): {f: chunkEqTrueV2, Label: "=="},
string("!=" + types.Empty): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.ArrayLike): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.ArrayLike): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.MapLike): {f: chunkEqFalseV2, Label: "=="},
Expand Down Expand Up @@ -69,6 +71,8 @@ func init() {
string("!=" + types.Time): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Dict): {f: boolCmpDictV2, Label: "=="},
string("!=" + types.Dict): {f: boolNotDictV2, Label: "!="},
string("==" + types.Empty): {f: boolCmpNilV2, Label: "=="},
string("!=" + types.Empty): {f: boolNotNilV2, Label: "!="},
string("==" + types.ArrayLike): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.ArrayLike): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Array(types.Bool)): {f: boolCmpBoolarrayV2, Label: "=="},
Expand Down Expand Up @@ -115,6 +119,8 @@ func init() {
string("!=" + types.Regex): {f: intNotRegexV2, Label: "!="},
string("==" + types.Dict): {f: intCmpDictV2, Label: "=="},
string("!=" + types.Dict): {f: intNotDictV2, Label: "!="},
string("==" + types.Empty): {f: intCmpNilV2, Label: "=="},
string("!=" + types.Empty): {f: intNotNilV2, Label: "!="},
string("==" + types.ArrayLike): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.ArrayLike): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Array(types.Int)): {f: intCmpIntarrayV2, Label: "=="},
Expand Down Expand Up @@ -183,6 +189,8 @@ func init() {
string("!=" + types.Regex): {f: floatNotRegexV2, Label: "!="},
string("==" + types.Dict): {f: floatCmpDictV2, Label: "=="},
string("!=" + types.Dict): {f: floatNotDictV2, Label: "!="},
string("==" + types.Empty): {f: floatCmpNilV2, Label: "=="},
string("!=" + types.Empty): {f: floatNotNilV2, Label: "!="},
string("==" + types.ArrayLike): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.ArrayLike): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Array(types.Int)): {f: floatCmpIntarrayV2, Label: "=="},
Expand Down Expand Up @@ -243,6 +251,8 @@ func init() {
// == / !=
string("==" + types.Nil): {f: stringCmpNilV2, Label: "=="},
string("!=" + types.Nil): {f: stringNotNilV2, Label: "!="},
string("==" + types.Empty): {f: stringCmpEmptyV2, Label: "=="},
string("!=" + types.Empty): {f: stringNotEmptyV2, Label: "!="},
string("==" + types.String): {f: stringCmpStringV2, Label: "=="},
string("!=" + types.String): {f: stringNotStringV2, Label: "!="},
string("==" + types.Regex): {f: stringCmpRegexV2, Label: "=="},
Expand Down Expand Up @@ -338,6 +348,8 @@ func init() {
// == / !=
string("==" + types.Nil): {f: stringCmpNilV2, Label: "=="},
string("!=" + types.Nil): {f: stringNotNilV2, Label: "!="},
string("==" + types.Empty): {f: stringCmpEmptyV2, Label: "=="},
string("!=" + types.Empty): {f: stringNotEmptyV2, Label: "!="},
string("==" + types.Regex): {f: stringCmpStringV2, Label: "=="},
string("!=" + types.Regex): {f: stringNotStringV2, Label: "!="},
string("==" + types.Bool): {f: chunkEqFalseV2, Label: "=="},
Expand Down Expand Up @@ -382,6 +394,8 @@ func init() {
types.Time: {
string("==" + types.Nil): {f: timeCmpNilV2, Label: "=="},
string("!=" + types.Nil): {f: timeNotNilV2, Label: "!="},
string("==" + types.Empty): {f: timeCmpNilV2, Label: "=="},
string("!=" + types.Empty): {f: timeNotNilV2, Label: "!="},
string("==" + types.Time): {f: timeCmpTimeV2, Label: "=="},
string("!=" + types.Time): {f: timeNotTimeV2, Label: "!="},
string("<" + types.Time): {f: timeLTTimeV2, Label: "<"},
Expand Down Expand Up @@ -421,6 +435,8 @@ func init() {
types.Dict: {
string("==" + types.Nil): {f: dictCmpNilV2, Label: "=="},
string("!=" + types.Nil): {f: dictNotNilV2, Label: "!="},
string("==" + types.Empty): {f: dictCmpEmpty, Label: "=="},
string("!=" + types.Empty): {f: dictNotEmpty, Label: "!="},
string("==" + types.Bool): {f: dictCmpBoolV2, Label: "=="},
string("!=" + types.Bool): {f: dictNotBoolV2, Label: "!="},
string("==" + types.Int): {f: dictCmpIntV2, Label: "=="},
Expand Down Expand Up @@ -523,33 +539,35 @@ func init() {
"notEmpty": {f: dictNotEmptyV2},
},
types.ArrayLike: {
"[]": {f: arrayGetIndexV2},
"first": {f: arrayGetFirstIndexV2},
"last": {f: arrayGetLastIndexV2},
"{}": {f: arrayBlockListV2},
"${}": {f: arrayBlockV2},
"length": {f: arrayLengthV2},
"where": {f: arrayWhereV2},
"$whereNot": {f: arrayWhereNotV2},
"$all": {f: arrayAllV2},
"$none": {f: arrayNoneV2},
"$any": {f: arrayAnyV2},
"$one": {f: arrayOneV2},
"map": {f: arrayMapV2},
"flat": {f: arrayFlat},
"duplicates": {f: arrayDuplicatesV2},
"fieldDuplicates": {f: arrayFieldDuplicatesV2},
"unique": {f: arrayUniqueV2},
"difference": {f: arrayDifferenceV2},
"containsNone": {f: arrayContainsNoneV2},
"==": {Compiler: compileArrayOpArray("=="), f: tarrayCmpTarrayV2, Label: "=="},
"!=": {Compiler: compileArrayOpArray("!="), f: tarrayNotTarrayV2, Label: "!="},
"==" + string(types.Nil): {f: arrayCmpNilV2},
"!=" + string(types.Nil): {f: arrayNotNilV2},
"&&": {Compiler: compileLogicalArrayOp(types.ArrayLike, "&&")},
"||": {Compiler: compileLogicalArrayOp(types.ArrayLike, "||")},
"+": {Compiler: compileArrayOpArray("+"), f: tarrayConcatTarrayV2, Label: "+"},
"-": {Compiler: compileArrayOpArray("-"), f: tarrayDeleteTarrayV2, Label: "-"},
"[]": {f: arrayGetIndexV2},
"first": {f: arrayGetFirstIndexV2},
"last": {f: arrayGetLastIndexV2},
"{}": {f: arrayBlockListV2},
"${}": {f: arrayBlockV2},
"length": {f: arrayLengthV2},
"where": {f: arrayWhereV2},
"$whereNot": {f: arrayWhereNotV2},
"$all": {f: arrayAllV2},
"$none": {f: arrayNoneV2},
"$any": {f: arrayAnyV2},
"$one": {f: arrayOneV2},
"map": {f: arrayMapV2},
"flat": {f: arrayFlat},
"duplicates": {f: arrayDuplicatesV2},
"fieldDuplicates": {f: arrayFieldDuplicatesV2},
"unique": {f: arrayUniqueV2},
"difference": {f: arrayDifferenceV2},
"containsNone": {f: arrayContainsNoneV2},
"==": {Compiler: compileArrayOpArray("=="), f: tarrayCmpTarrayV2, Label: "=="},
"!=": {Compiler: compileArrayOpArray("!="), f: tarrayNotTarrayV2, Label: "!="},
"==" + string(types.Nil): {f: arrayCmpNilV2},
"!=" + string(types.Nil): {f: arrayNotNilV2},
"==" + string(types.Empty): {f: arrayCmpEmpty},
"!=" + string(types.Empty): {f: arrayNotEmpty},
"&&": {Compiler: compileLogicalArrayOp(types.ArrayLike, "&&")},
"||": {Compiler: compileLogicalArrayOp(types.ArrayLike, "||")},
"+": {Compiler: compileArrayOpArray("+"), f: tarrayConcatTarrayV2, Label: "+"},
"-": {Compiler: compileArrayOpArray("-"), f: tarrayDeleteTarrayV2, Label: "-"},
// logical operations []<T> -- K
string(types.Any + "&&" + types.Bool): {f: arrayAndBoolV2, Label: "&&"},
string(types.Any + "||" + types.Bool): {f: arrayOrBoolV2, Label: "||"},
Expand Down Expand Up @@ -618,13 +636,17 @@ func init() {
"notEmpty": {f: arrayNotEmptyV2},
},
types.MapLike: {
"[]": {f: mapGetIndexV2},
"length": {f: mapLengthV2},
"where": {f: mapWhereV2},
"$whereNot": {f: mapWhereNotV2},
"{}": {f: mapBlockCallV2},
"keys": {f: mapKeysV2, Label: "keys"},
"values": {f: mapValuesV2, Label: "values"},
"[]": {f: mapGetIndexV2},
"length": {f: mapLengthV2},
"where": {f: mapWhereV2},
"$whereNot": {f: mapWhereNotV2},
"{}": {f: mapBlockCallV2},
"keys": {f: mapKeysV2, Label: "keys"},
"values": {f: mapValuesV2, Label: "values"},
"==" + string(types.Nil): {f: mapCmpNil},
"!=" + string(types.Nil): {f: mapNotNil},
"==" + string(types.Empty): {f: mapCmpEmpty},
"!=" + string(types.Empty): {f: mapNotEmpty},
// {}T -- T
string("&&" + types.Bool): {f: chunkEqFalseV2, Label: "&&"},
string("||" + types.Bool): {f: chunkNeqTrueV2, Label: "||"},
Expand All @@ -647,8 +669,11 @@ func init() {
},
types.ResourceLike: {
// == / !=
string("==" + types.Nil): {f: chunkEqFalseV2, Label: "=="},
string("!=" + types.Nil): {f: chunkNeqTrueV2, Label: "!="},
string("==" + types.Nil): {f: bindingEqNil, Label: "=="},
string("!=" + types.Nil): {f: bindingNeqNil, Label: "!="},
// TODO: correctly implement this for list type resources
string("==" + types.Empty): {f: bindingEqNil, Label: "=="},
string("!=" + types.Empty): {f: bindingNeqNil, Label: "!="},
// fields
"where": {f: resourceWhereV2},
"$whereNot": {f: resourceWhereNotV2},
Expand Down
22 changes: 22 additions & 0 deletions llx/builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,28 @@ func arrayNotNilV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return BoolTrue, 0, nil
}

func arrayCmpEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolTrue, 0, nil
}
v := bind.Value.([]interface{})
if v == nil {
return BoolTrue, 0, nil
}
return BoolData(len(v) == 0), 0, nil
}

func arrayNotEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
}
v := bind.Value.([]interface{})
if v == nil {
return BoolFalse, 0, nil
}
return BoolData(len(v) != 0), 0, nil
}

func boolarrayCmpBoolV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
return rawboolOpV2(e, bind, chunk, ref, func(left *RawData, right *RawData) bool {
return cmpArrayOne(left, right, opBoolCmpBool)
Expand Down
79 changes: 79 additions & 0 deletions llx/builtin_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,50 @@ func mapGetIndexV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
}, 0, nil
}

func mapCmpNil(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolTrue, 0, nil
}
v, ok := bind.Value.(map[string]interface{})
if !ok || v == nil {
return BoolTrue, 0, nil
}
return BoolFalse, 0, nil
}

func mapNotNil(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
}
v, ok := bind.Value.(map[string]interface{})
if !ok || v == nil {
return BoolFalse, 0, nil
}
return BoolTrue, 0, nil
}

func mapCmpEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolTrue, 0, nil
}
v, ok := bind.Value.(map[string]interface{})
if !ok || v == nil {
return BoolTrue, 0, nil
}
return BoolData(len(v) == 0), 0, nil
}

func mapNotEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
}
v, ok := bind.Value.(map[string]interface{})
if !ok || v == nil {
return BoolFalse, 0, nil
}
return BoolData(len(v) != 0), 0, nil
}

func mapLengthV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return &RawData{Type: types.Int}, 0, nil
Expand Down Expand Up @@ -357,6 +401,41 @@ func dictLengthV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*R
}
}

func dictCmpEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolTrue, 0, nil
}

switch x := bind.Value.(type) {
case string:
return BoolData(len(x) == 0), 0, nil
case []interface{}:
return BoolData(len(x) == 0), 0, nil
case map[string]interface{}:
return BoolData(len(x) == 0), 0, nil
default:
return BoolFalse, 0, nil
}
}

func dictNotEmpty(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
}

switch x := bind.Value.(type) {
case string:
return BoolData(len(x) != 0), 0, nil
case []interface{}:
return BoolData(len(x) != 0), 0, nil
case map[string]interface{}:
return BoolData(len(x) != 0), 0, nil
default:
return BoolTrue, 0, nil
}
}

// Deprecated: replace with calls to the empty type
func dictNotEmptyV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
Expand Down
22 changes: 22 additions & 0 deletions llx/builtin_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ func chunkNeqTrueV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (
})
}

func bindingEqNil(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
return BoolData(bind == nil), 0, nil
}

func bindingNeqNil(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
return BoolData(bind != nil), 0, nil
}

// raw operator handling
// == !=

Expand Down Expand Up @@ -320,6 +328,20 @@ func timeNotTimeV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*
return boolNotOpV2(e, bind, chunk, ref, opTimeCmpTime)
}

func stringCmpEmptyV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolTrue, 0, nil
}
return BoolData(bind.Value.(string) == ""), 0, nil
}

func stringNotEmptyV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
if bind.Value == nil {
return BoolFalse, 0, nil
}
return BoolData(bind.Value.(string) != ""), 0, nil
}

// int arithmetic

func intPlusIntV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) {
Expand Down
12 changes: 11 additions & 1 deletion llx/data_conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func init() {
types.Time: time2result,
types.Dict: dict2result,
types.Score: score2result,
types.Empty: empty2result,
types.Block: block2result,
types.ArrayLike: array2result,
types.MapLike: map2result,
Expand All @@ -55,6 +56,7 @@ func init() {
types.Time: ptime2raw,
types.Dict: pdict2raw,
types.Score: pscore2raw,
types.Empty: pempty2raw,
types.Block: pblock2rawV2,
types.ArrayLike: parray2raw,
types.MapLike: pmap2raw,
Expand Down Expand Up @@ -226,6 +228,10 @@ func score2result(value interface{}, typ types.Type) (*Primitive, error) {
}, nil
}

func empty2result(value interface{}, typ types.Type) (*Primitive, error) {
return EmptyPrimitive, nil
}

func block2result(value interface{}, typ types.Type) (*Primitive, error) {
m, ok := value.(map[string]interface{})
if !ok {
Expand Down Expand Up @@ -465,7 +471,7 @@ func (r *Result) RawData() *RawData {
data := &RawData{}
if r.Data != nil {
// The type can be empty, when we do not have data
if r.Data.IsNil() || types.Type(r.Data.Type).IsEmpty() {
if r.Data.IsNil() || types.Type(r.Data.Type).NotSet() {
data.Type = types.Nil
} else {
data = r.Data.RawData()
Expand Down Expand Up @@ -562,6 +568,10 @@ func pscore2raw(p *Primitive) *RawData {
return &RawData{Value: p.Value, Type: types.Score}
}

func pempty2raw(p *Primitive) *RawData {
return &RawData{Type: types.Type(p.Type)}
}

func pblock2rawV2(p *Primitive) *RawData {
d, err := primitive2rawdataMapV2(p.Map)
return &RawData{Value: d, Error: err, Type: types.Type(p.Type)}
Expand Down
Loading

0 comments on commit cdb057a

Please sign in to comment.