mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-15 16:51:03 +00:00
* Formatting adjustments made during code review.
While reviewing the BULK INSERT logic (in order to decide how best to
approach "ingest via sql" in the cloud), I made a few formatting and
comment changes. I'm just adding them here as a separate commit so they
don't muddy up my actual work.
* Parser modifications to support mulitple tuples in INSERT INTO
This commit doesn't include all of the changes required in the
planner. Fow now, the planner is simply modified to continue supporting
a single tuple (the first tuple in the list).
* Update the planner to handle multiple INSERT INTO tuples
This is part 1. It's still using the existing logic which builds an
ImportRequest for every record (and every field!).
The next step will involve using a client.Batch to handle the records.
* Introduce client.Importer interface (used by client.Batch)
Instead of the Batch having a pointer to a client, this puts an
interface there instead (which the client implements). It also allows us
to inject a different importer (i.e. other than a featurebase.client)
into the Batch.
* Decouple batch from client
This commit pulls batch-specific code out of the client package and into
a new batch package. It introduces the batch.Importer interface, the
methods of which replace all the calls that batch was previously making
directly to client methods.
Finally, it contains two implementations of the batch.Importer
interface: one is a wrapper around client, and the other is a wrapper
around featurebase.API.
* Use docker (instead of MustRunCluster) for internal batch tests
Because the `batch` package tests are internal, using
test.MustRunCluster() resulted in an import loop (because it eventually
imports `server`, and we can't have that). So this commit replaces the
use of `test.MustRunCluster()` with docker. The setup is basically the
same as that used in the idk docker tests.
Here we also remove all client-side references to `UseIngestAPI`, which
is an experimental (json) ingest api. It's still suppored on the server,
but here we remove the external usage of it.
* cherry-pick fix
* Use batch.Import() for sql3 INSERT INTO statements
* Thread logger into sql3
* fix batch test
* Fix some shadowing complaint by linter
* Address some test issues related to stringsets
* Exclude batch integration tests from CI
* Address PR feedback
- Added description to batch.README
- Consolidated grep commands in .gitlab-ci.yml
- Removed some debugging comments
- Replaces some inadvertantly removed license headers
* Add batch package to gitlab CI
* Updated CI for batch package
Updated CI include path
Update gitlab ci
Update CI
Update CI
Trying new include path for ci
Updated gitlab ci include path
Made idk race job optional for sonarcloud upload
add testdata directory
remove testenv from dockercompose file
use GIT_STRATEGY clone in batch CI
add testdata volume to dockercompose
Co-authored-by: Fletcher Haynes <fletcher.haynes@generalassemb.ly>
(cherry picked from commit 00ef2380e5)
664 lines
17 KiB
Go
664 lines
17 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
|
|
package planner
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
pilosa "github.com/featurebasedb/featurebase/v3"
|
|
"github.com/featurebasedb/featurebase/v3/sql3"
|
|
"github.com/featurebasedb/featurebase/v3/sql3/parser"
|
|
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
|
|
)
|
|
|
|
// bulkInsertMappedColumn specifies a mapping from the source
|
|
// data to a target column name
|
|
type bulkInsertMappedColumn struct {
|
|
// source expression
|
|
// using an interface for so we have flexibility in data types as format changes
|
|
columnSource interface{}
|
|
// name of the target column
|
|
columnName string
|
|
// data type of the target column
|
|
columnDataType parser.ExprDataType
|
|
}
|
|
|
|
// bulkInsertOptions contains options for bulk insert
|
|
type bulkInsertOptions struct {
|
|
// name of the file we're going to read
|
|
fileName string
|
|
// number of rows in a batch
|
|
batchSize int
|
|
// stop after this many rows
|
|
rowsLimit int
|
|
// format specifier (CSV is the only one right now)
|
|
format string
|
|
// the column map in the source data to use as the _id value
|
|
// if empty or nill auto increment
|
|
// using an interface so we have flexibility in data types as format changes
|
|
idColumnMap []interface{}
|
|
// column mappings
|
|
columnMap []*bulkInsertMappedColumn
|
|
}
|
|
|
|
// PlanOpBulkInsert plan operator to handle INSERT.
|
|
type PlanOpBulkInsert struct {
|
|
planner *ExecutionPlanner
|
|
tableName string
|
|
isKeyed bool
|
|
options *bulkInsertOptions
|
|
warnings []string
|
|
}
|
|
|
|
func NewPlanOpBulkInsert(p *ExecutionPlanner, tableName string, isKeyed bool, options *bulkInsertOptions) *PlanOpBulkInsert {
|
|
return &PlanOpBulkInsert{
|
|
planner: p,
|
|
tableName: tableName,
|
|
isKeyed: isKeyed,
|
|
options: options,
|
|
warnings: make([]string, 0),
|
|
}
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) Plan() map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
result["_op"] = fmt.Sprintf("%T", p)
|
|
sc := make([]string, 0)
|
|
for _, e := range p.Schema() {
|
|
sc = append(sc, fmt.Sprintf("'%s', '%s', '%s'", e.ColumnName, e.RelationName, e.Type.TypeName()))
|
|
}
|
|
result["_schema"] = sc
|
|
result["tableName"] = p.tableName
|
|
|
|
options := make(map[string]interface{})
|
|
options["batchsize"] = p.options.batchSize
|
|
options["rowslimit"] = p.options.rowsLimit
|
|
options["format"] = p.options.format
|
|
if len(p.options.idColumnMap) > 0 {
|
|
options["idColumnMap"] = p.options.idColumnMap
|
|
} else {
|
|
options["idColumnMap"] = "autoincrement"
|
|
}
|
|
colMap := make([]interface{}, 0)
|
|
for _, m := range p.options.columnMap {
|
|
cm := make(map[string]interface{})
|
|
cm["columnSource"] = m.columnSource
|
|
cm["columnName"] = m.columnName
|
|
colMap = append(colMap, cm)
|
|
}
|
|
options["columnMap"] = colMap
|
|
|
|
result["options"] = options
|
|
return result
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) String() string {
|
|
return ""
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) AddWarning(warning string) {
|
|
p.warnings = append(p.warnings, warning)
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) Warnings() []string {
|
|
return p.warnings
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) Schema() types.Schema {
|
|
return types.Schema{}
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) Children() []types.PlanOperator {
|
|
return []types.PlanOperator{}
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
|
return &bulkInsertCSVRowIter{
|
|
planner: p.planner,
|
|
tableName: p.tableName,
|
|
isKeyed: p.isKeyed,
|
|
options: p.options,
|
|
}, nil
|
|
}
|
|
|
|
func (p *PlanOpBulkInsert) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
|
return NewPlanOpBulkInsert(p.planner, p.tableName, p.isKeyed, p.options), nil
|
|
}
|
|
|
|
type bulkInsertCSVRowIter struct {
|
|
planner *ExecutionPlanner
|
|
tableName string
|
|
isKeyed bool
|
|
options *bulkInsertOptions
|
|
|
|
// latch is used to indicate if the CSV has been processed. It will
|
|
// be set to a non-nil value upon processing. After that, the file
|
|
// should not be processed again.
|
|
latch *struct{}
|
|
|
|
currentBatch []interface{}
|
|
lastKeyValue uint64
|
|
}
|
|
|
|
var _ types.RowIterator = (*bulkInsertCSVRowIter)(nil)
|
|
|
|
func (i *bulkInsertCSVRowIter) Next(ctx context.Context) (types.Row, error) {
|
|
// If Next has already been called, return early. We only want to process
|
|
// the file once.
|
|
if i.latch != nil {
|
|
return nil, types.ErrNoMoreRows
|
|
}
|
|
|
|
// Set latch to indicate that Next() has been called.
|
|
i.latch = &struct{}{}
|
|
|
|
i.lastKeyValue = 0
|
|
|
|
f, err := os.Open(i.options.fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
linesRead := 0
|
|
csvReader := csv.NewReader(f)
|
|
for {
|
|
rec, err := csvReader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// do something with read line
|
|
if err = i.processCSVLine(ctx, rec); err != nil {
|
|
return nil, err
|
|
}
|
|
linesRead += 1
|
|
|
|
// bail if we have a rows limit and we've hit it
|
|
if i.options.rowsLimit > 0 && linesRead >= i.options.rowsLimit {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil, types.ErrNoMoreRows
|
|
}
|
|
|
|
func (i *bulkInsertCSVRowIter) processCSVLine(ctx context.Context, line []string) error {
|
|
if i.currentBatch == nil {
|
|
i.currentBatch = make([]interface{}, 0)
|
|
}
|
|
i.currentBatch = append(i.currentBatch, line)
|
|
if len(i.currentBatch) >= i.options.batchSize {
|
|
log.Printf("BULK INSERT: processing batch (%d)", len(i.currentBatch))
|
|
err := i.processBatch(ctx)
|
|
log.Printf("BULK INSERT: batch processed")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *bulkInsertCSVRowIter) processBatch(ctx context.Context) error {
|
|
|
|
batchLen := len(i.currentBatch)
|
|
|
|
colIDs := make([]uint64, batchLen)
|
|
colKeys := make([]string, batchLen)
|
|
|
|
insertData := make([]interface{}, len(i.options.columnMap))
|
|
|
|
addColID := func(index int, v interface{}) error {
|
|
switch id := v.(type) {
|
|
case int64:
|
|
colIDs[index] = uint64(id)
|
|
case uint64:
|
|
colIDs[index] = id
|
|
case string:
|
|
colKeys[index] = id
|
|
default:
|
|
return sql3.NewErrInternalf("unhandled _id data type '%T'", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// make objects for each column depending on data type
|
|
for cidx, mc := range i.options.columnMap {
|
|
switch targetType := mc.columnDataType.(type) {
|
|
case *parser.DataTypeID:
|
|
vals := make([]uint64, batchLen)
|
|
insertData[cidx] = vals
|
|
|
|
case *parser.DataTypeInt:
|
|
vals := make([]int64, batchLen)
|
|
insertData[cidx] = vals
|
|
|
|
case *parser.DataTypeString:
|
|
vals := make([]string, batchLen)
|
|
insertData[cidx] = vals
|
|
|
|
case *parser.DataTypeTimestamp:
|
|
vals := make([]time.Time, batchLen)
|
|
insertData[cidx] = vals
|
|
|
|
default:
|
|
return sql3.NewErrInternalf("unhandled target type '%T'", targetType)
|
|
}
|
|
}
|
|
|
|
// for each row in the batch add value to each mapped column
|
|
log.Printf("BULK INSERT: building batch...")
|
|
for rowIdx, row := range i.currentBatch {
|
|
csvRow, ok := row.([]string)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected row type '%T'", row)
|
|
}
|
|
|
|
//handle each column
|
|
for colIdx, mc := range i.options.columnMap {
|
|
|
|
columnPosition, ok := mc.columnSource.(int64)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected columnPosition type '%T'", mc.columnSource)
|
|
}
|
|
switch targetType := mc.columnDataType.(type) {
|
|
case *parser.DataTypeID:
|
|
valueStr := csvRow[columnPosition]
|
|
insertValue, err := strconv.ParseUint(valueStr, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
columnData, ok := insertData[colIdx].([]uint64)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected columnData type '%T'", insertData[colIdx])
|
|
}
|
|
columnData[rowIdx] = insertValue
|
|
|
|
case *parser.DataTypeInt:
|
|
valueStr := csvRow[columnPosition]
|
|
insertValue, err := strconv.ParseInt(valueStr, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
columnData, ok := insertData[colIdx].([]int64)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected columnData type '%T'", insertData[colIdx])
|
|
}
|
|
columnData[rowIdx] = insertValue
|
|
|
|
case *parser.DataTypeString:
|
|
insertValue := csvRow[columnPosition]
|
|
columnData, ok := insertData[colIdx].([]string)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected columnData type '%T'", insertData[colIdx])
|
|
}
|
|
columnData[rowIdx] = insertValue
|
|
|
|
case *parser.DataTypeTimestamp:
|
|
valueStr := csvRow[columnPosition]
|
|
|
|
var insertValue time.Time
|
|
if tm, err := time.ParseInLocation(time.RFC3339Nano, valueStr, time.UTC); err == nil {
|
|
insertValue = tm
|
|
} else if tm, err := time.ParseInLocation(time.RFC3339, valueStr, time.UTC); err == nil {
|
|
insertValue = tm
|
|
} else if tm, err := time.ParseInLocation("2006-01-02 15:04:05", valueStr, time.UTC); err == nil {
|
|
insertValue = tm
|
|
} else if tm, err := time.ParseInLocation("2006-01-02", valueStr, time.UTC); err == nil {
|
|
insertValue = tm
|
|
} else {
|
|
return err
|
|
}
|
|
columnData, ok := insertData[colIdx].([]time.Time)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected columnData type '%T'", insertData[colIdx])
|
|
}
|
|
columnData[rowIdx] = insertValue
|
|
|
|
default:
|
|
return sql3.NewErrInternalf("unhandled target type '%T'", targetType)
|
|
}
|
|
|
|
}
|
|
|
|
// add _id
|
|
if len(i.options.idColumnMap) > 0 {
|
|
return sql3.NewErrInternalf("not yet implemented")
|
|
} else {
|
|
// if the table is keyed, use the string representation of an integer key value
|
|
if i.isKeyed {
|
|
//auto increment
|
|
err := addColID(rowIdx, fmt.Sprintf("%d", i.lastKeyValue))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
//auto increment
|
|
err := addColID(rowIdx, i.lastKeyValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
i.lastKeyValue += 1
|
|
}
|
|
}
|
|
log.Printf("BULK INSERT: building batch complete")
|
|
|
|
// now loop again and actually do the insert
|
|
|
|
log.Printf("BULK INSERT: inserting columns...")
|
|
qcx := i.planner.computeAPI.Txf().NewQcx()
|
|
|
|
//nil out colids if the table is keyed
|
|
for colIdx, mc := range i.options.columnMap {
|
|
log.Printf("BULK INSERT: inserting column '%s'...", mc.columnName)
|
|
if i.isKeyed {
|
|
colIDs = nil
|
|
}
|
|
switch targetType := mc.columnDataType.(type) {
|
|
case *parser.DataTypeID:
|
|
vals, ok := insertData[colIdx].([]uint64)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected insert data type '%T'", insertData[colIdx])
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: mc.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowIDs: vals,
|
|
}
|
|
|
|
err := i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case *parser.DataTypeInt:
|
|
vals, ok := insertData[colIdx].([]int64)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected insert data type '%T'", insertData[colIdx])
|
|
}
|
|
|
|
req := &pilosa.ImportValueRequest{
|
|
Index: i.tableName,
|
|
Field: mc.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
Values: vals,
|
|
}
|
|
|
|
err := i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case *parser.DataTypeString:
|
|
vals, ok := insertData[colIdx].([]string)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected insert data type '%T'", insertData[colIdx])
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: mc.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowKeys: vals,
|
|
}
|
|
err := i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case *parser.DataTypeTimestamp:
|
|
vals, ok := insertData[colIdx].([]time.Time)
|
|
if !ok {
|
|
return sql3.NewErrInternalf("unexpected insert data type '%T'", insertData[colIdx])
|
|
}
|
|
|
|
// TODO (pok) - getting and error for timestamp columns
|
|
// 'Error: local import after remote imports: number of columns (1) and number of values (0) do not match'
|
|
req := &pilosa.ImportValueRequest{
|
|
Index: i.tableName,
|
|
Field: mc.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
TimestampValues: vals,
|
|
}
|
|
|
|
err := i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
return sql3.NewErrInternalf("unhandled target type '%T'", targetType)
|
|
}
|
|
log.Printf("BULK INSERT: inserting column '%s' complete.", mc.columnName)
|
|
}
|
|
log.Printf("BULK INSERT: inserting columns complete.")
|
|
|
|
/*
|
|
|
|
|
|
//eval all the expressions and do the insert
|
|
for idx, iv := range i.insertValues {
|
|
|
|
sourceType := iv.Type()
|
|
switch targetType := i.targetColumns[idx].dataType.(type) {
|
|
|
|
case *parser.DataTypeBool:
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
val := eval.(bool)
|
|
vals := make([]uint64, 1)
|
|
if val {
|
|
vals[0] = 1
|
|
} else {
|
|
vals[0] = 0
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowIDs: vals,
|
|
}
|
|
|
|
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case *parser.DataTypeDecimal:
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vals := make([]float64, 1)
|
|
vals[0] = eval.(pql.Decimal).Float64()
|
|
|
|
req := &pilosa.ImportValueRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
FloatValues: vals,
|
|
}
|
|
|
|
err = i.planner.computeAPI.ImportValue(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
|
|
case *parser.DataTypeIDSet:
|
|
rowIDs := make([]uint64, 0)
|
|
rowSet := eval.([]int64)
|
|
for k := range rowSet {
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowIDs = append(rowIDs, uint64(rowSet[k]))
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowIDs: rowIDs,
|
|
}
|
|
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case *parser.DataTypeIDSetQuantum:
|
|
rowIDs := make([]uint64, 0)
|
|
timestamps := make([]int64, 0)
|
|
|
|
coercedVal, err := coerceValue(sourceType, targetType, eval, parser.Pos{Line: 0, Column: 0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
record := coercedVal.([]interface{})
|
|
rowSet := record[1].([]int64)
|
|
for k := range rowSet {
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowIDs = append(rowIDs, uint64(rowSet[k]))
|
|
}
|
|
|
|
if record[0] == nil {
|
|
timestamps = nil
|
|
} else {
|
|
timestamp, ok := record[0].(time.Time)
|
|
if !ok {
|
|
return nil, sql3.NewErrInternalf("unexpected type '%T'", record[0])
|
|
}
|
|
for _ = range rowSet {
|
|
timestamps = append(timestamps, timestamp.Unix())
|
|
}
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowIDs: rowIDs,
|
|
Timestamps: timestamps,
|
|
}
|
|
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case *parser.DataTypeStringSet:
|
|
rowKeys := make([]string, 0)
|
|
rowSet := eval.([]string)
|
|
for k := range rowSet {
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowKeys = append(rowKeys, rowSet[k])
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowKeys: rowKeys,
|
|
}
|
|
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case *parser.DataTypeStringSetQuantum:
|
|
rowKeys := make([]string, 0)
|
|
timestamps := make([]int64, 0)
|
|
|
|
coercedVal, err := coerceValue(sourceType, targetType, eval, parser.Pos{Line: 0, Column: 0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
record := coercedVal.([]interface{})
|
|
rowSet := record[1].([]string)
|
|
for k := range rowSet {
|
|
err = addColID(columnID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rowKeys = append(rowKeys, rowSet[k])
|
|
}
|
|
|
|
if record[0] == nil {
|
|
timestamps = nil
|
|
} else {
|
|
timestamp, ok := record[0].(time.Time)
|
|
if !ok {
|
|
return nil, sql3.NewErrInternalf("unexpected type '%T'", record[0])
|
|
}
|
|
for _ = range rowSet {
|
|
timestamps = append(timestamps, timestamp.Unix())
|
|
}
|
|
}
|
|
|
|
req := &pilosa.ImportRequest{
|
|
Index: i.tableName,
|
|
Field: targetColumn.columnName,
|
|
Shard: 0, //TODO: handle non-0 shards
|
|
ColumnIDs: colIDs,
|
|
ColumnKeys: colKeys,
|
|
RowKeys: rowKeys,
|
|
Timestamps: timestamps,
|
|
}
|
|
err = i.planner.computeAPI.Import(ctx, qcx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
default:
|
|
return nil, sql3.NewErrInternalf("unhandled data type '%T'", targetType)
|
|
}
|
|
}*/
|
|
|
|
// done with current batch
|
|
i.currentBatch = nil
|
|
return nil
|
|
}
|