Repository: brimsec/zq
Pull request #1585 (2020-11-10 03:05:32)

add cut function
This commit adds a cut function that behaves like the cut proc
but in the function context.  Like the proc, the cut function
ignores records that do not contain all the fields being cut.



Patch:
diff --git a/expr/cut.go b/expr/cut.go
new file mode 100644
index 000000000..405d80992
--- /dev/null
+++ b/expr/cut.go
@@ -0,0 +1,43 @@
+package expr
+
+import (
+	"github.com/brimsec/zq/ast"
+	"github.com/brimsec/zq/field"
+	"github.com/brimsec/zq/zng"
+	"github.com/brimsec/zq/zng/resolver"
+)
+
+type cutFunc struct {
+	*Cutter
+}
+
+func compileCut(zctx *resolver.Context, node ast.FunctionCall) (Evaluator, error) {
+	var lhs []field.Static
+	var rhs []Evaluator
+	for _, expr := range node.Args {
+		// This is a bit of a hack and could be cleaed up by re-factoring
+		// CompileAssigment, but for now, we create an assigment expression
+		// where the LHS and RHS are the same, so that cut(id.orig_h,_path)
+		// gives a value of type record[id:record[orig_h:ip],_path:string]
+		// with field names that are the same as the cut names.
+		assignment := &ast.Assignment{LHS: expr, RHS: expr}
+		field, expression, err := CompileAssignment(zctx, assignment)
+		if err != nil {
+			return nil, err
+		}
+		lhs = append(lhs, field)
+		rhs = append(rhs, expression)
+	}
+	return &cutFunc{NewCutter(zctx, false, lhs, rhs)}, nil
+}
+
+func (c *cutFunc) Eval(rec *zng.Record) (zng.Value, error) {
+	out, err := c.Cut(rec)
+	if err != nil {
+		return zng.Value{}, err
+	}
+	if out == nil {
+		return zng.Value{}, ErrNoSuchField
+	}
+	return zng.Value{out.Type, out.Raw}, nil
+}
diff --git a/expr/expr.go b/expr/expr.go
index 5a7337c21..4cb1e5115 100644
--- a/expr/expr.go
+++ b/expr/expr.go
@@ -825,6 +825,13 @@ type Call struct {
 }
 
 func compileCall(zctx *resolver.Context, node ast.FunctionCall) (Evaluator, error) {
+	// For now, we special case cut here.  As we add more stateful functions
+	// this will make more sense.  We could also compile any reducer as a
+	// stateful function here, e.g., count(), would produce a new count() for
+	// each record encountered analogous to juttle stream functions.
+	if node.Function == "cut" {
+		return compileCut(zctx, node)
+	}
 	nargs := len(node.Args)
 	fn, err := function.New(node.Function, nargs)
 	if err != nil {


Test patch:
diff --git a/expr/ztests/cut-union.yaml b/expr/ztests/cut-union.yaml
new file mode 100644
index 000000000..8035ebfe4
--- /dev/null
+++ b/expr/ztests/cut-union.yaml
@@ -0,0 +1,17 @@
+zql: a=union(cut(x)),b=union(cut(x,s))
+
+input: |
+  #0:record[x:int32,s:string]
+  0:[1;a;]
+  0:[2;b;]
+  #1:record[s:string]
+  1:[x;]
+  1:[b;]
+  #2:record[none:string]
+  2:[bad;]
+  0:[1;a;]
+  0:[3;e;]
+
+output: |
+  #0:record[a:set[record[x:int32]],b:set[record[x:int32,s:string]]]
+  0:[[[1;][2;][3;]][[1;a;][2;b;][3;e;]]]
diff --git a/expr/ztests/cut.yaml b/expr/ztests/cut.yaml
new file mode 100644
index 000000000..c69dda532
--- /dev/null
+++ b/expr/ztests/cut.yaml
@@ -0,0 +1,28 @@
+zql: put v=cut(s,x)
+
+warnings: |
+  field is not present
+
+input: |
+  #0:record[x:int32,s:string]
+  0:[1;a;]
+  0:[2;b;]
+  #1:record[s:string]
+  1:[x;]
+  1:[b;]
+  #2:record[none:string]
+  2:[bad;]
+  0:[1;a;]
+  0:[3;e;]
+
+output: |
+  #0:record[x:int32,s:string,v:record[s:string,x:int32]]
+  0:[1;a;[a;1;]]
+  0:[2;b;[b;2;]]
+  #1:record[s:string,v:record[s:string]]
+  1:[x;[x;]]
+  1:[b;[b;]]
+  #2:record[none:string]
+  2:[bad;]
+  0:[1;a;[a;1;]]
+  0:[3;e;[e;3;]]