package kallax import ( "database/sql" "errors" "io" "gopkg.in/src-d/go-kallax.v1/types" ) // ResultSet is the common interface all result sets need to implement. type ResultSet interface { // RawScan allows for raw scanning of fields in a result set. RawScan(...interface{}) error // Next moves the pointer to the next item in the result set and returns // if there was any. Next() bool // Get returns the next record of the given schema. Get(Schema) (Record, error) io.Closer } // ErrRawScan is an error returned when a the `Scan` method of `ResultSet` // is called with a `ResultSet` created as a result of a `RawQuery`, which is // not allowed. var ErrRawScan = errors.New("kallax: result set comes from raw query, use RawScan instead") // ErrRawScanBatching is an error returned when the `RawScan` method is used // with a batching result set. var ErrRawScanBatching = errors.New("kallax: cannot perform a raw scan on a batching result set") // BaseResultSet is a generic collection of rows. type BaseResultSet struct { relationships []Relationship columns []string readOnly bool *sql.Rows } // NewResultSet creates a new result set with the given rows and columns. // It is mandatory that all column names are in the same order and are exactly // equal to the ones in the query that produced the rows. func NewResultSet(rows *sql.Rows, readOnly bool, relationships []Relationship, columns ...string) *BaseResultSet { return &BaseResultSet{ relationships, columns, readOnly, rows, } } // Get returns the next record in the schema. func (rs *BaseResultSet) Get(schema Schema) (Record, error) { record := schema.New() if err := rs.Scan(record); err != nil { return nil, err } return record, nil } // Scan fills the column fields of the given value with the current row. func (rs *BaseResultSet) Scan(record Record) error { if len(rs.columns) == 0 { return ErrRawScan } var ( relationships = make([]Record, len(rs.relationships)) pointers = make([]interface{}, len(rs.columns)) ) for i, col := range rs.columns { ptr, err := record.ColumnAddress(col) if err != nil { return err } pointers[i] = ptr } for i, r := range rs.relationships { rec, err := record.NewRelationshipRecord(r.Field) if err != nil { return err } for _, col := range r.Schema.Columns() { ptr, err := rec.ColumnAddress(col.String()) if err != nil { return err } pointers = append(pointers, types.Nullable(ptr)) } relationships[i] = rec } if err := rs.Rows.Scan(pointers...); err != nil { return err } for i, r := range rs.relationships { relationships[i].setPersisted() relationships[i].setWritable(true) err := record.SetRelationship(r.Field, relationships[i]) if err != nil { return err } } record.setWritable(!rs.readOnly) record.setPersisted() return nil } // RowScan copies the columns in the current row into the values pointed at by // dest. The number of values in dest must be the same as the number of columns // selected in the query. func (rs *BaseResultSet) RawScan(dest ...interface{}) error { return rs.Rows.Scan(dest...) } // NewBatchingResultSet returns a new result set that performs batching // underneath. func NewBatchingResultSet(runner *batchQueryRunner) *BatchingResultSet { return &BatchingResultSet{runner: runner} } // BatchingResultSet is a result set that retrieves all the items up to the // batch size set in the query. // If there are 1:N relationships, it collects all the identifiers of // those records, retrieves all the rows matching them in the table of the // the N end, and assigns them to their correspondent to the record they belong // to. // It will continue doing this process until no more rows are returned by the // query. // This minimizes the number of queries and operations to perform in order to // retrieve a set of results and their relationships. type BatchingResultSet struct { runner *batchQueryRunner last Record lastErr error } // Next advances the internal index of the fetched records in one. // If there are no fetched records, will fetch the next batch. // It will return false when there are no more rows. func (rs *BatchingResultSet) Next() bool { rs.last, rs.lastErr = rs.runner.next() if rs.lastErr == errNoMoreRows { return false } return true } // Get returns the next processed record and the last error occurred. // Even though it accepts a schema, it is ignored, as the result set is // already aware of it. This is here just to be able to imeplement the // ResultSet interface. func (rs *BatchingResultSet) Get(_ Schema) (Record, error) { return rs.last, rs.lastErr } // Close will do nothing, as the internal result sets used by this are closed // when the rows at fetched. It will never throw an error. func (rs *BatchingResultSet) Close() error { return nil } // RawScan will always throw an error, as this is not a supported operation of // a batching result set. func (rs *BatchingResultSet) RawScan(_ ...interface{}) error { return ErrRawScanBatching }