You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: utils.go
    path of file: ./repos/octosql/logical
    
    the code of the file until where you have to start completion: package logical

// func EqualNodes(node1, node2 Node) error {
// 	switch node1 := node1.(type) {
// 	case *With:
// 		if node2, ok := node2.(*With); ok {
// 			if len(node1.cteNames) != len(node2.cteNames) {
// 				return errors.Errorf("counts of cte's not equal: %d vs %d", len(node1.cteNames), len(node2.cteNames))
// 			}
// 			for i := range node1.cteNames {
// 				if node1.cteNames[i] != node2.cteNames[i] {
// 					return errors.Errorf("cte names with index %d not equal: %s vs %s", i, node1.cteNames[i], node2.cteNames[i])
// 				}
// 				if err := EqualNodes(node1.cteNodes[i], node2.cteNodes[i]); err != nil {
// 					return errors.Wrapf(err, "cte nodes with index %d not equal: %+v, %+v", i, node1.cteNodes[i], node2.cteNodes[i])
// 				}
// 			}
// 			if err := EqualNodes(node1.left, node2.left); err != nil {
// 				return errors.Wrapf(err, "left node not equal: %+v, %+v", node1.left, node2.left)
// 			}
// 			return nil
// 		}
//
// 	case *UnionAll:
// 		if node2, ok := node2.(*UnionAll); ok {
// 			if err := EqualNodes(node1.first, node2.first); err != nil {
// 				return errors.Wrapf(err, "first statements not equal: %+v, %+v", node1.first, node2.first)
// 			}
// 			if err := EqualNodes(node1.second, node2.second); err != n
    
func EqualExpressions(expr1, expr2 Expression) bool {
	switch expr1 := expr1.(type) {
	case *And:
		if expr2, ok := expr2.(*And); ok {
			return EqualExpressions(expr1.left, expr2.left) && EqualExpressions(expr1.right, expr2.right)
		}
	case *Or:
		if expr2, ok := expr2.(*Or); ok {
			return EqualExpressions(expr1.left, expr2.left) && EqualExpressions(expr1.right, expr2.right)
		}
	case *StarExpression:
		if expr2, ok := expr2.(*StarExpression); ok {
			return expr1.qualifier == expr2.qualifier
		}
	case *Constant:
		if expr2, ok := expr2.(*Constant); ok {
			return expr1.value.Compare(expr2.value) == 0
		}

	case *Variable:
		if expr2, ok := expr2.(*Variable); ok {
			return expr1.name == expr2.name
		}

	case *Tuple:
		if expr2, ok := expr2.(*Tuple); ok {
			if len(expr1.expressions) != len(expr2.expressions) {
				return false
			}
			for i := range expr1.expressions {
				if !EqualExpressions(expr1.expressions[i], expr2.expressions[i]) {
					return false
				}
			}
			return true
		}

	case *FunctionExpression:
		if expr2, ok := expr2.(*FunctionExpression); ok {
			if expr1.Name != expr2.Name {
				return false
			}
			if len(expr1.Arguments) != len(expr2.Arguments) {
				return false
			}
			for i := range expr1.Arguments {
				if !EqualExpressions(expr1.Arguments[i], expr2.Arguments[i]) {
					return false
				}
			}
			return true
		}

	case *TypeCast:
		if expr2, ok := expr2.(*TypeCast); ok {
			if expr1.targetTypeID != expr2.targetTypeID {
				return false
			}
			return EqualExpressions(expr1.arg, expr2.arg)
		}

	case *Coalesce:
		if expr2, ok := expr2.(*Coalesce); ok {
			if len(expr1.args) != len(expr2.args) {
				return false
			}
			for i := range expr1.args {
				if !EqualExpressions(expr1.args[i], expr2.args[i]) {
					return false
				}
			}
			return true
		}

	case *ObjectFieldAccess:
		if expr2, ok := expr2.(*ObjectFieldAccess); ok {
			if expr1.field != expr2.field {
				return false
			}
			return EqualExpressions(expr1.object, expr2.object)
		}

	}
	return false
}