github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/jsonpath_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package sql
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"testing"
    28  
    29  	"github.com/alecthomas/participle"
    30  	"github.com/bcicen/jstream"
    31  )
    32  
    33  func getJSONStructs(b []byte) ([]interface{}, error) {
    34  	dec := jstream.NewDecoder(bytes.NewBuffer(b), 0).ObjectAsKVS()
    35  	var result []interface{}
    36  	for parsedVal := range dec.Stream() {
    37  		result = append(result, parsedVal.Value)
    38  	}
    39  	if err := dec.Err(); err != nil {
    40  		return nil, err
    41  	}
    42  	return result, nil
    43  }
    44  
    45  func TestJsonpathEval(t *testing.T) {
    46  	f, err := os.Open(filepath.Join("jsondata", "books.json"))
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  
    51  	b, err := io.ReadAll(f)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	p := participle.MustBuild(
    57  		&JSONPath{},
    58  		participle.Lexer(sqlLexer),
    59  		participle.CaseInsensitive("Keyword"),
    60  	)
    61  	cases := []struct {
    62  		str string
    63  		res []interface{}
    64  	}{
    65  		{"s.title", []interface{}{"Murder on the Orient Express", "The Robots of Dawn", "Pigs Have Wings"}},
    66  		{"s.authorInfo.yearRange", []interface{}{[]interface{}{1890.0, 1976.0}, []interface{}{1920.0, 1992.0}, []interface{}{1881.0, 1975.0}}},
    67  		{"s.authorInfo.name", []interface{}{"Agatha Christie", "Isaac Asimov", "P. G. Wodehouse"}},
    68  		{"s.authorInfo.yearRange[0]", []interface{}{1890.0, 1920.0, 1881.0}},
    69  		{"s.publicationHistory[0].pages", []interface{}{256.0, 336.0, Missing{}}},
    70  	}
    71  	for i, tc := range cases {
    72  		t.Run(tc.str, func(t *testing.T) {
    73  			jp := JSONPath{}
    74  			err := p.ParseString(tc.str, &jp)
    75  			// fmt.Println(jp)
    76  			if err != nil {
    77  				t.Fatalf("parse failed!: %d %v %s", i, err, tc)
    78  			}
    79  
    80  			// Read only the first json object from the file
    81  			recs, err := getJSONStructs(b)
    82  			if err != nil || len(recs) != 3 {
    83  				t.Fatalf("%v or length was not 3", err)
    84  			}
    85  
    86  			for j, rec := range recs {
    87  				// fmt.Println(rec)
    88  				r, _, err := jsonpathEval(jp.PathExpr, rec)
    89  				if err != nil {
    90  					t.Errorf("Error: %d %d %v", i, j, err)
    91  				}
    92  				if !reflect.DeepEqual(r, tc.res[j]) {
    93  					fmt.Printf("%#v (%v) != %v (%v)\n", r, reflect.TypeOf(r), tc.res[j], reflect.TypeOf(tc.res[j]))
    94  					t.Errorf("case: %d %d failed", i, j)
    95  				}
    96  			}
    97  		})
    98  	}
    99  }