...

Source file src/github.com/go-kivik/kivik/v4/resultset_test.go

Documentation: github.com/go-kivik/kivik/v4

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package kivik
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"errors"
    19  	"io"
    20  	"net/http"
    21  	"strconv"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"gitlab.com/flimzy/testy"
    28  
    29  	"github.com/go-kivik/kivik/v4/driver"
    30  	"github.com/go-kivik/kivik/v4/int/mock"
    31  )
    32  
    33  func TestRowsIteratorNext(t *testing.T) {
    34  	const expected = "foo error"
    35  	r := &rowsIterator{
    36  		Rows: &mock.Rows{
    37  			NextFunc: func(_ *driver.Row) error { return errors.New(expected) },
    38  		},
    39  	}
    40  	var i driver.Row
    41  	err := r.Next(&i)
    42  	if !testy.ErrorMatches(expected, err) {
    43  		t.Errorf("Unexpected error: %s", err)
    44  	}
    45  }
    46  
    47  func TestNextResultSet(t *testing.T) {
    48  	t.Run("two resultsets", func(t *testing.T) {
    49  		r := multiResultSet()
    50  
    51  		ids := []string{}
    52  		for r.NextResultSet() {
    53  			for r.Next() {
    54  				id, _ := r.ID()
    55  				ids = append(ids, id)
    56  			}
    57  		}
    58  		if err := r.Err(); err != nil {
    59  			t.Error(err)
    60  		}
    61  		want := []string{"1", "2", "3", "x", "y"}
    62  		if d := cmp.Diff(want, ids); d != "" {
    63  			t.Error(d)
    64  		}
    65  	})
    66  	t.Run("called out of order", func(t *testing.T) {
    67  		r := multiResultSet()
    68  
    69  		if !r.Next() {
    70  			t.Fatal("expected next to return true")
    71  		}
    72  		if r.NextResultSet() {
    73  			t.Fatal("expected NextResultSet to return false")
    74  		}
    75  
    76  		wantErr := "must call NextResultSet before Next"
    77  		err := r.Err()
    78  		if !testy.ErrorMatches(wantErr, err) {
    79  			t.Errorf("Unexpected error: %s", err)
    80  		}
    81  	})
    82  	t.Run("next only", func(t *testing.T) {
    83  		r := multiResultSet()
    84  
    85  		ids := []string{}
    86  		for r.Next() {
    87  			id, _ := r.ID()
    88  			ids = append(ids, id)
    89  		}
    90  		if err := r.Err(); err != nil {
    91  			t.Error(err)
    92  		}
    93  		want := []string{"1", "2", "3", "x", "y"}
    94  		if d := testy.DiffInterface(want, ids); d != nil {
    95  			t.Error(d)
    96  		}
    97  	})
    98  	t.Run("don't call NextResultSet in loop", func(t *testing.T) {
    99  		r := multiResultSet()
   100  
   101  		ids := []string{}
   102  		r.NextResultSet()
   103  		for r.Next() {
   104  			id, _ := r.ID()
   105  			ids = append(ids, id)
   106  		}
   107  		_ = r.Next() // once more to ensure it doesn't error past the end of the first RS
   108  		if err := r.Err(); err != nil {
   109  			t.Error(err)
   110  		}
   111  
   112  		// Only the first result set is processed, since NextResultSet is never
   113  		// called a second time.
   114  		want := []string{"1", "2", "3"}
   115  		if d := testy.DiffInterface(want, ids); d != nil {
   116  			t.Error(d)
   117  		}
   118  	})
   119  }
   120  
   121  func multiResultSet() *ResultSet {
   122  	rows := []interface{}{
   123  		&driver.Row{ID: "1", Doc: strings.NewReader(`{"foo":"bar"}`)},
   124  		&driver.Row{ID: "2", Doc: strings.NewReader(`{"foo":"bar"}`)},
   125  		&driver.Row{ID: "3", Doc: strings.NewReader(`{"foo":"bar"}`)},
   126  		int64(5),
   127  		&driver.Row{ID: "x", Doc: strings.NewReader(`{"foo":"bar"}`)},
   128  		&driver.Row{ID: "y", Doc: strings.NewReader(`{"foo":"bar"}`)},
   129  		int64(2),
   130  	}
   131  	var offset int64
   132  
   133  	return newResultSet(context.Background(), nil, &mock.Rows{
   134  		NextFunc: func(r *driver.Row) error {
   135  			if len(rows) == 0 {
   136  				return io.EOF
   137  			}
   138  			row := rows[0]
   139  			rows = rows[1:]
   140  			switch t := row.(type) {
   141  			case *driver.Row:
   142  				*r = *t
   143  				return nil
   144  			case int64:
   145  				offset = t
   146  				return driver.EOQ
   147  			default:
   148  				panic("unknown type")
   149  			}
   150  		},
   151  		OffsetFunc: func() int64 {
   152  			return offset
   153  		},
   154  	})
   155  }
   156  
   157  func TestScanAllDocs(t *testing.T) {
   158  	type tt struct {
   159  		rows *ResultSet
   160  		dest interface{}
   161  		err  string
   162  	}
   163  
   164  	tests := testy.NewTable()
   165  	tests.Add("non-pointer dest", tt{
   166  		dest: "string",
   167  		err:  "must pass a pointer to ScanAllDocs",
   168  	})
   169  	tests.Add("nil pointer dest", tt{
   170  		dest: (*string)(nil),
   171  		err:  "nil pointer passed to ScanAllDocs",
   172  	})
   173  	tests.Add("not a slice or array", tt{
   174  		dest: &ResultSet{},
   175  		err:  "dest must be a pointer to a slice or array",
   176  	})
   177  	tests.Add("0-length array", tt{
   178  		dest: func() *[0]string { var x [0]string; return &x }(),
   179  		err:  "0-length array passed to ScanAllDocs",
   180  	})
   181  	tests.Add("No docs to read", tt{
   182  		rows: newResultSet(context.Background(), nil, &mock.Rows{}),
   183  		dest: func() *[]string { return &[]string{} }(),
   184  	})
   185  	tests.Add("Success", func() interface{} {
   186  		rows := []*driver.Row{
   187  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   188  		}
   189  		return tt{
   190  			rows: newResultSet(context.Background(), nil, &mock.Rows{
   191  				NextFunc: func(r *driver.Row) error {
   192  					if len(rows) == 0 {
   193  						return io.EOF
   194  					}
   195  					*r = *rows[0]
   196  					rows = rows[1:]
   197  					return nil
   198  				},
   199  			}),
   200  			dest: func() *[]json.RawMessage { return &[]json.RawMessage{} }(),
   201  		}
   202  	})
   203  	tests.Add("Success, slice of pointers", func() interface{} {
   204  		rows := []*driver.Row{
   205  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   206  		}
   207  		return tt{
   208  			rows: newResultSet(context.Background(), nil, &mock.Rows{
   209  				NextFunc: func(r *driver.Row) error {
   210  					if len(rows) == 0 {
   211  						return io.EOF
   212  					}
   213  					*r = *rows[0]
   214  					rows = rows[1:]
   215  					return nil
   216  				},
   217  			}),
   218  			dest: func() *[]*json.RawMessage { return &[]*json.RawMessage{} }(),
   219  		}
   220  	})
   221  	tests.Add("Success, long array", func() interface{} {
   222  		rows := []*driver.Row{
   223  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   224  		}
   225  		return tt{
   226  			rows: newResultSet(context.Background(), nil, &mock.Rows{
   227  				NextFunc: func(r *driver.Row) error {
   228  					if len(rows) == 0 {
   229  						return io.EOF
   230  					}
   231  					*r = *rows[0]
   232  					rows = rows[1:]
   233  					return nil
   234  				},
   235  			}),
   236  			dest: func() *[5]*json.RawMessage { return &[5]*json.RawMessage{} }(),
   237  		}
   238  	})
   239  	tests.Add("Success, short array", func() interface{} {
   240  		rows := []*driver.Row{
   241  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   242  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   243  			{Doc: strings.NewReader(`{"foo":"bar"}`)},
   244  		}
   245  		return tt{
   246  			rows: newResultSet(context.Background(), nil, &mock.Rows{
   247  				NextFunc: func(r *driver.Row) error {
   248  					if len(rows) == 0 {
   249  						return io.EOF
   250  					}
   251  					*r = *rows[0]
   252  					rows = rows[1:]
   253  					return nil
   254  				},
   255  			}),
   256  			dest: func() *[1]*json.RawMessage { return &[1]*json.RawMessage{} }(),
   257  		}
   258  	})
   259  	tests.Run(t, func(t *testing.T, tt tt) {
   260  		if tt.rows == nil {
   261  			tt.rows = newResultSet(context.Background(), nil, &mock.Rows{})
   262  		}
   263  		err := ScanAllDocs(tt.rows, tt.dest)
   264  		if !testy.ErrorMatches(tt.err, err) {
   265  			t.Errorf("Unexpected error: %s", err)
   266  		}
   267  		if d := testy.DiffAsJSON(testy.Snapshot(t), tt.dest); d != nil {
   268  			t.Error(d)
   269  		}
   270  	})
   271  }
   272  
   273  func TestResultSet_Next_resets_iterator_value(t *testing.T) {
   274  	idx := 0
   275  	rows := newResultSet(context.Background(), nil, &mock.Rows{
   276  		NextFunc: func(r *driver.Row) error {
   277  			idx++
   278  			switch idx {
   279  			case 1:
   280  				r.ID = strconv.Itoa(idx)
   281  				return nil
   282  			case 2:
   283  				return nil
   284  			}
   285  			return io.EOF
   286  		},
   287  	})
   288  
   289  	wantIDs := []string{"1", ""}
   290  	gotIDs := []string{}
   291  	for rows.Next() {
   292  		id, err := rows.ID()
   293  		if err != nil {
   294  			t.Fatal(err)
   295  		}
   296  		gotIDs = append(gotIDs, id)
   297  	}
   298  	if d := cmp.Diff(wantIDs, gotIDs); d != "" {
   299  		t.Error(d)
   300  	}
   301  }
   302  
   303  func TestResultSet_Getters(t *testing.T) {
   304  	const id = "foo"
   305  	key := []byte("[1234]")
   306  	const offset = int64(2)
   307  	const totalrows = int64(3)
   308  	const updateseq = "asdfasdf"
   309  	r := &ResultSet{
   310  		iter: &iter{
   311  			state: stateRowReady,
   312  			curVal: &driver.Row{
   313  				ID:  id,
   314  				Key: key,
   315  			},
   316  		},
   317  		rowsi: &mock.Rows{
   318  			OffsetFunc:    func() int64 { return offset },
   319  			TotalRowsFunc: func() int64 { return totalrows },
   320  			UpdateSeqFunc: func() string { return updateseq },
   321  		},
   322  	}
   323  
   324  	t.Run("ID", func(t *testing.T) {
   325  		result, _ := r.ID()
   326  		if id != result {
   327  			t.Errorf("Unexpected result: %v", result)
   328  		}
   329  	})
   330  
   331  	t.Run("Key", func(t *testing.T) {
   332  		result, _ := r.Key()
   333  		if string(key) != result {
   334  			t.Errorf("Unexpected result: %v", result)
   335  		}
   336  	})
   337  
   338  	t.Run("Not Ready", func(t *testing.T) {
   339  		t.Run("ID", func(t *testing.T) {
   340  			rowsi := &mock.Rows{
   341  				NextFunc: func(r *driver.Row) error {
   342  					r.ID = id
   343  					return nil
   344  				},
   345  			}
   346  			r := newResultSet(context.Background(), nil, rowsi)
   347  
   348  			_, err := r.ID()
   349  			if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) {
   350  				t.Errorf("Unexpected error: %v", err)
   351  			}
   352  		})
   353  
   354  		t.Run("Key", func(t *testing.T) {
   355  			rowsi := &mock.Rows{
   356  				NextFunc: func(r *driver.Row) error {
   357  					r.Key = key
   358  					return nil
   359  				},
   360  			}
   361  			r := newResultSet(context.Background(), nil, rowsi)
   362  
   363  			_, err := r.Key()
   364  			if !testy.ErrorMatches("kivik: Iterator access before calling Next", err) {
   365  				t.Errorf("Unexpected error: %v", err)
   366  			}
   367  		})
   368  	})
   369  }
   370  
   371  func TestResultSet_Metadata(t *testing.T) {
   372  	t.Run("iteration incomplete", func(t *testing.T) {
   373  		r := newResultSet(context.Background(), nil, &mock.Rows{
   374  			OffsetFunc:    func() int64 { return 123 },
   375  			TotalRowsFunc: func() int64 { return 234 },
   376  			UpdateSeqFunc: func() string { return "seq" },
   377  		})
   378  		_, err := r.Metadata()
   379  		wantErr := "Metadata must not be called until result set iteration is complete"
   380  		if !testy.ErrorMatches(wantErr, err) {
   381  			t.Errorf("Unexpected error: %s", err)
   382  		}
   383  	})
   384  
   385  	check := func(t *testing.T, r *ResultSet) {
   386  		t.Helper()
   387  		for r.Next() { //nolint:revive // Consume all rows
   388  		}
   389  		meta, err := r.Metadata()
   390  		if err != nil {
   391  			t.Fatal(err)
   392  		}
   393  		if d := testy.DiffInterface(testy.Snapshot(t), meta); d != nil {
   394  			t.Error(d)
   395  		}
   396  	}
   397  
   398  	t.Run("Standard", func(t *testing.T) {
   399  		r := newResultSet(context.Background(), nil, &mock.Rows{
   400  			OffsetFunc:    func() int64 { return 123 },
   401  			TotalRowsFunc: func() int64 { return 234 },
   402  			UpdateSeqFunc: func() string { return "seq" },
   403  		})
   404  		check(t, r)
   405  	})
   406  	t.Run("Bookmarker", func(t *testing.T) {
   407  		expected := "test bookmark"
   408  		r := newResultSet(context.Background(), nil, &mock.Bookmarker{
   409  			BookmarkFunc: func() string { return expected },
   410  		})
   411  		check(t, r)
   412  	})
   413  	t.Run("Warner", func(t *testing.T) {
   414  		const expected = "test warning"
   415  		r := newResultSet(context.Background(), nil, &mock.RowsWarner{
   416  			WarningFunc: func() string { return expected },
   417  		})
   418  		check(t, r)
   419  	})
   420  	t.Run("query in progress", func(t *testing.T) {
   421  		rows := []interface{}{
   422  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   423  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   424  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   425  		}
   426  
   427  		r := newResultSet(context.Background(), nil, &mock.Rows{
   428  			NextFunc: func(r *driver.Row) error {
   429  				if len(rows) == 0 {
   430  					return io.EOF
   431  				}
   432  				if dr, ok := rows[0].(*driver.Row); ok {
   433  					rows = rows[1:]
   434  					*r = *dr
   435  					return nil
   436  				}
   437  				return driver.EOQ
   438  			},
   439  			OffsetFunc: func() int64 {
   440  				return 5
   441  			},
   442  		})
   443  		var i int
   444  		for r.Next() {
   445  			i++
   446  			if i > 10 {
   447  				panic(i)
   448  			}
   449  		}
   450  		check(t, r)
   451  	})
   452  	t.Run("no query in progress", func(t *testing.T) {
   453  		rows := []interface{}{
   454  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   455  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   456  			&driver.Row{Doc: strings.NewReader(`{"foo":"bar"}`)},
   457  		}
   458  
   459  		r := newResultSet(context.Background(), nil, &mock.Rows{
   460  			NextFunc: func(r *driver.Row) error {
   461  				if len(rows) == 0 {
   462  					return io.EOF
   463  				}
   464  				if dr, ok := rows[0].(*driver.Row); ok {
   465  					rows = rows[1:]
   466  					*r = *dr
   467  					return nil
   468  				}
   469  				return driver.EOQ
   470  			},
   471  			OffsetFunc: func() int64 {
   472  				return 5
   473  			},
   474  		})
   475  		check(t, r)
   476  	})
   477  	t.Run("followed by other query in resultset mode", func(t *testing.T) {
   478  		r := multiResultSet()
   479  
   480  		_ = r.NextResultSet()
   481  		check(t, r)
   482  		ids := []string{}
   483  		for r.Next() {
   484  			id, _ := r.ID()
   485  			ids = append(ids, id)
   486  		}
   487  		want := []string{"x", "y"}
   488  		if d := testy.DiffInterface(want, ids); d != nil {
   489  			t.Error(d)
   490  		}
   491  		t.Run("second query", func(t *testing.T) {
   492  			check(t, r)
   493  		})
   494  	})
   495  	t.Run("followed by other query in row mode", func(t *testing.T) {
   496  		r := multiResultSet()
   497  
   498  		check(t, r)
   499  		ids := []string{}
   500  		for r.Next() {
   501  			id, _ := r.ID()
   502  			ids = append(ids, id)
   503  		}
   504  		want := []string{}
   505  		if d := testy.DiffInterface(want, ids); d != nil {
   506  			t.Error(d)
   507  		}
   508  		t.Run("second query", func(t *testing.T) {
   509  			check(t, r)
   510  		})
   511  	})
   512  }
   513  
   514  func Test_bug576(t *testing.T) {
   515  	rows := newResultSet(context.Background(), nil, &mock.Rows{
   516  		NextFunc: func(*driver.Row) error {
   517  			return io.EOF
   518  		},
   519  	})
   520  
   521  	var result interface{}
   522  	err := rows.ScanDoc(&result)
   523  	const wantErr = "kivik: Iterator access before calling Next"
   524  	wantStatus := http.StatusBadRequest
   525  	if !testy.ErrorMatches(wantErr, err) {
   526  		t.Errorf("unexpected error: %s", err)
   527  	}
   528  	if status := HTTPStatus(err); status != wantStatus {
   529  		t.Errorf("Unexpected error status: %v", status)
   530  	}
   531  }
   532  
   533  func TestResultSet_Close_blocks(t *testing.T) {
   534  	t.Parallel()
   535  
   536  	const delay = 100 * time.Millisecond
   537  
   538  	type tt struct {
   539  		rows driver.Rows
   540  		work func(*ResultSet)
   541  	}
   542  
   543  	tests := testy.NewTable()
   544  	tests.Add("ScanDoc", tt{
   545  		rows: &mock.Rows{
   546  			NextFunc: func(row *driver.Row) error {
   547  				row.Doc = io.MultiReader(
   548  					testy.DelayReader(delay),
   549  					strings.NewReader(`{}`),
   550  				)
   551  				return nil
   552  			},
   553  		},
   554  		work: func(rs *ResultSet) {
   555  			var i interface{}
   556  			rs.Next()
   557  			_ = rs.ScanDoc(&i)
   558  		},
   559  	})
   560  	tests.Add("ScanValue", tt{
   561  		rows: &mock.Rows{
   562  			NextFunc: func(row *driver.Row) error {
   563  				row.Value = io.MultiReader(
   564  					testy.DelayReader(delay),
   565  					strings.NewReader(`{}`),
   566  				)
   567  				return nil
   568  			},
   569  		},
   570  		work: func(rs *ResultSet) {
   571  			var i interface{}
   572  			rs.Next()
   573  			_ = rs.ScanValue(&i)
   574  		},
   575  	})
   576  	tests.Add("Attachments", tt{
   577  		rows: &mock.Rows{
   578  			NextFunc: func(row *driver.Row) error {
   579  				row.Attachments = &mock.Attachments{
   580  					NextFunc: func(*driver.Attachment) error {
   581  						time.Sleep(delay)
   582  						return io.EOF
   583  					},
   584  				}
   585  				return nil
   586  			},
   587  		},
   588  		work: func(rs *ResultSet) {
   589  			rs.Next()
   590  			atts, err := rs.Attachments()
   591  			if err != nil {
   592  				t.Fatal(err)
   593  			}
   594  			for {
   595  				_, _ = atts.Next()
   596  			}
   597  		},
   598  	})
   599  
   600  	tests.Run(t, func(t *testing.T, tt tt) {
   601  		t.Parallel()
   602  
   603  		rs := newResultSet(context.Background(), nil, tt.rows)
   604  
   605  		start := time.Now()
   606  		go tt.work(rs)
   607  		time.Sleep(delay / 2)
   608  		_ = rs.Close()
   609  		if elapsed := time.Since(start); elapsed < delay {
   610  			t.Errorf("rs.Close() didn't block long enough (%v < %v)", elapsed, delay)
   611  		}
   612  	})
   613  }
   614  

View as plain text