// populateExplain invokes explain() with a makeRow method // which populates a valuesNode. func (e *explainer) populateExplain(v *valuesNode, plan planNode) error { e.makeRow = func(level int, name, field, description string, plan planNode) { if e.err != nil { return } row := parser.DTuple{ parser.NewDInt(parser.DInt(level)), parser.NewDString(name), parser.NewDString(field), parser.NewDString(description), } if e.showMetadata { if plan != nil { row = append(row, parser.NewDString(formatColumns(plan.Columns(), e.showTypes))) row = append(row, parser.NewDString(plan.Ordering().AsString(plan.Columns()))) } else { row = append(row, emptyString, emptyString) } } if _, err := v.rows.AddRow(row); err != nil { e.err = err } } e.err = nil e.explain(plan) return e.err }
// SetUIData is an endpoint that stores the given key/value pairs in the // system.ui table. See GetUIData for more details on semantics. func (s *adminServer) SetUIData( ctx context.Context, req *serverpb.SetUIDataRequest, ) (*serverpb.SetUIDataResponse, error) { if len(req.KeyValues) == 0 { return nil, grpc.Errorf(codes.InvalidArgument, "KeyValues cannot be empty") } args := sql.SessionArgs{User: s.getUser(req)} session := s.NewSessionForRPC(ctx, args) defer session.Finish(s.server.sqlExecutor) for key, val := range req.KeyValues { // Do an upsert of the key. We update each key in a separate transaction to // avoid long-running transactions and possible deadlocks. br := s.server.sqlExecutor.ExecuteStatements(session, "BEGIN;", nil) defer br.Close() if err := s.checkQueryResults(br.ResultList, 1); err != nil { return nil, s.serverError(err) } // See if the key already exists. resp, err := s.getUIData(session, s.getUser(req), []string{key}) if err != nil { return nil, s.serverError(err) } _, alreadyExists := resp.KeyValues[key] // INSERT or UPDATE as appropriate. if alreadyExists { query := "UPDATE system.ui SET value = $1, lastUpdated = NOW() WHERE key = $2; COMMIT;" qargs := parser.NewPlaceholderInfo() qargs.SetValue(`1`, parser.NewDString(string(val))) qargs.SetValue(`2`, parser.NewDString(key)) r := s.server.sqlExecutor.ExecuteStatements(session, query, qargs) defer r.Close() if err := s.checkQueryResults(r.ResultList, 2); err != nil { return nil, s.serverError(err) } if a, e := r.ResultList[0].RowsAffected, 1; a != e { return nil, s.serverErrorf("rows affected %d != expected %d", a, e) } } else { query := "INSERT INTO system.ui (key, value, lastUpdated) VALUES ($1, $2, NOW()); COMMIT;" qargs := parser.NewPlaceholderInfo() qargs.SetValue(`1`, parser.NewDString(key)) qargs.SetValue(`2`, parser.NewDBytes(parser.DBytes(val))) r := s.server.sqlExecutor.ExecuteStatements(session, query, qargs) defer r.Close() if err := s.checkQueryResults(r.ResultList, 2); err != nil { return nil, s.serverError(err) } if a, e := r.ResultList[0].RowsAffected, 1; a != e { return nil, s.serverErrorf("rows affected %d != expected %d", a, e) } } } return &serverpb.SetUIDataResponse{}, nil }
// ShowGrants returns grant details for the specified objects and users. // TODO(marc): implement no targets (meaning full scan). // Privileges: None. // Notes: postgres does not have a SHOW GRANTS statement. // mysql only returns the user's privileges. func (p *planner) ShowGrants(n *parser.ShowGrants) (planNode, error) { if n.Targets == nil { return nil, errors.Errorf("TODO(marc): implement SHOW GRANT with no targets") } descriptors, err := p.getDescriptorsFromTargetList(*n.Targets) if err != nil { return nil, err } objectType := "Database" if n.Targets.Tables != nil { objectType = "Table" } columns := ResultColumns{ {Name: objectType, Typ: parser.TypeString}, {Name: "User", Typ: parser.TypeString}, {Name: "Privileges", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "SHOW GRANTS", columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) var wantedUsers map[string]struct{} if len(n.Grantees) != 0 { wantedUsers = make(map[string]struct{}) } for _, u := range n.Grantees { wantedUsers[string(u)] = struct{}{} } for _, descriptor := range descriptors { userPrivileges := descriptor.GetPrivileges().Show() for _, userPriv := range userPrivileges { if wantedUsers != nil { if _, ok := wantedUsers[userPriv.User]; !ok { continue } } newRow := parser.DTuple{ parser.NewDString(descriptor.GetName()), parser.NewDString(userPriv.User), parser.NewDString(userPriv.PrivilegeString()), } if err := v.rows.AddRow(newRow); err != nil { v.rows.Close() return nil, err } } } return v, nil }, }, nil }
// Show a session-local variable name. func (p *planner) Show(n *parser.Show) (planNode, error) { name := strings.ToUpper(n.Name) var columns ResultColumns switch name { case `ALL`: columns = ResultColumns{ {Name: "Variable", Typ: parser.TypeString}, {Name: "Value", Typ: parser.TypeString}, } default: if _, ok := varGen[name]; !ok { return nil, fmt.Errorf("unknown variable: %q", name) } columns = ResultColumns{{Name: name, Typ: parser.TypeString}} } return &delayedNode{ p: p, name: "SHOW " + name, columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) switch name { case `ALL`: for _, vName := range varNames { gen := varGen[vName] value := gen(p) if _, err := v.rows.AddRow( parser.DTuple{parser.NewDString(vName), parser.NewDString(value)}, ); err != nil { v.rows.Close() return nil, err } } default: // The key in varGen is guaranteed to exist thanks to the // check above. gen := varGen[name] value := gen(p) if _, err := v.rows.AddRow(parser.DTuple{parser.NewDString(value)}); err != nil { v.rows.Close() return nil, err } } return v, nil }, }, nil }
func benchmarkWriteTuple(b *testing.B, format formatCode) { i := parser.NewDInt(1234) f := parser.NewDFloat(12.34) s := parser.NewDString("testing") t := &parser.DTuple{i, f, s} benchmarkWriteType(b, t, format) }
// queryNamespaceID queries for the ID of the namespace with the given name and // parent ID. func (s *adminServer) queryNamespaceID( session *sql.Session, parentID sqlbase.ID, name string, ) (sqlbase.ID, error) { const query = `SELECT id FROM system.namespace WHERE parentID = $1 AND name = $2` params := parser.NewPlaceholderInfo() params.SetValue(`1`, parser.NewDInt(parser.DInt(parentID))) params.SetValue(`2`, parser.NewDString(name)) r := s.server.sqlExecutor.ExecuteStatements(session, query, params) defer r.Close() if err := s.checkQueryResults(r.ResultList, 1); err != nil { return 0, err } result := r.ResultList[0] if result.Rows.Len() == 0 { return 0, errors.Errorf("namespace %s with ParentID %d not found", name, parentID) } var id int64 scanner := resultScanner{} err := scanner.ScanIndex(result.Rows.At(0), 0, &id) if err != nil { return 0, err } return sqlbase.ID(id), nil }
// Events is an endpoint that returns the latest event log entries, with the following // optional URL parameters: // // type=STRING returns events with this type (e.g. "create_table") // targetID=INT returns events for that have this targetID func (s *adminServer) Events( ctx context.Context, req *serverpb.EventsRequest, ) (*serverpb.EventsResponse, error) { args := sql.SessionArgs{User: s.getUser(req)} session := s.NewSessionForRPC(ctx, args) defer session.Finish(s.server.sqlExecutor) // Execute the query. q := makeSQLQuery() q.Append("SELECT timestamp, eventType, targetID, reportingID, info, uniqueID ") q.Append("FROM system.eventlog ") q.Append("WHERE true ") // This simplifies the WHERE clause logic below. if len(req.Type) > 0 { q.Append("AND eventType = $ ", parser.NewDString(req.Type)) } if req.TargetId > 0 { q.Append("AND targetID = $ ", parser.NewDInt(parser.DInt(req.TargetId))) } q.Append("ORDER BY timestamp DESC ") q.Append("LIMIT $", parser.NewDInt(parser.DInt(apiEventLimit))) if len(q.Errors()) > 0 { return nil, s.serverErrors(q.Errors()) } r := s.server.sqlExecutor.ExecuteStatements(session, q.String(), q.QueryArguments()) defer r.Close() if err := s.checkQueryResults(r.ResultList, 1); err != nil { return nil, s.serverError(err) } // Marshal response. var resp serverpb.EventsResponse scanner := makeResultScanner(r.ResultList[0].Columns) for i, nRows := 0, r.ResultList[0].Rows.Len(); i < nRows; i++ { row := r.ResultList[0].Rows.At(i) var event serverpb.EventsResponse_Event var ts time.Time if err := scanner.ScanIndex(row, 0, &ts); err != nil { return nil, err } event.Timestamp = serverpb.EventsResponse_Event_Timestamp{Sec: ts.Unix(), Nsec: uint32(ts.Nanosecond())} if err := scanner.ScanIndex(row, 1, &event.EventType); err != nil { return nil, err } if err := scanner.ScanIndex(row, 2, &event.TargetID); err != nil { return nil, err } if err := scanner.ScanIndex(row, 3, &event.ReportingID); err != nil { return nil, err } if err := scanner.ScanIndex(row, 4, &event.Info); err != nil { return nil, err } if err := scanner.ScanIndex(row, 5, &event.UniqueID); err != nil { return nil, err } resp.Events = append(resp.Events, event) } return &resp, nil }
// SetUIData is an endpoint that stores the given key/value pairs in the // system.ui table. See GetUIData for more details on semantics. func (s *adminServer) SetUIData( ctx context.Context, req *serverpb.SetUIDataRequest, ) (*serverpb.SetUIDataResponse, error) { if len(req.KeyValues) == 0 { return nil, grpc.Errorf(codes.InvalidArgument, "KeyValues cannot be empty") } args := sql.SessionArgs{User: s.getUser(req)} session := s.NewSessionForRPC(ctx, args) defer session.Finish(s.server.sqlExecutor) for key, val := range req.KeyValues { // Do an upsert of the key. We update each key in a separate transaction to // avoid long-running transactions and possible deadlocks. query := "UPSERT INTO system.ui (key, value, lastUpdated) VALUES ($1, $2, NOW())" qargs := parser.NewPlaceholderInfo() qargs.SetValue(`1`, parser.NewDString(key)) qargs.SetValue(`2`, parser.NewDBytes(parser.DBytes(val))) r := s.server.sqlExecutor.ExecuteStatements(session, query, qargs) defer r.Close() if err := s.checkQueryResults(r.ResultList, 1); err != nil { return nil, s.serverError(err) } if a, e := r.ResultList[0].RowsAffected, 1; a != e { return nil, s.serverErrorf("rows affected %d != expected %d", a, e) } } return &serverpb.SetUIDataResponse{}, nil }
// ShowColumns of a table. // Privileges: Any privilege on table. // Notes: postgres does not have a SHOW COLUMNS statement. // mysql only returns columns you have privileges on. func (p *planner) ShowColumns(n *parser.ShowColumns) (planNode, error) { tn, err := n.Table.NormalizeWithDatabaseName(p.session.Database) if err != nil { return nil, err } desc, err := p.mustGetTableDesc(tn) if err != nil { return nil, err } if err := p.anyPrivilege(desc); err != nil { return nil, err } columns := ResultColumns{ {Name: "Field", Typ: parser.TypeString}, {Name: "Type", Typ: parser.TypeString}, {Name: "Null", Typ: parser.TypeBool}, {Name: "Default", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "SHOW COLUMNS FROM " + tn.String(), columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) for i, col := range desc.Columns { defaultExpr := parser.DNull if e := desc.Columns[i].DefaultExpr; e != nil { defaultExpr = parser.NewDString(*e) } newRow := parser.DTuple{ parser.NewDString(desc.Columns[i].Name), parser.NewDString(col.Type.SQLString()), parser.MakeDBool(parser.DBool(desc.Columns[i].Nullable)), defaultExpr, } if err := v.rows.AddRow(newRow); err != nil { v.rows.Close() return nil, err } } return v, nil }, }, nil }
// golangFillQueryArguments populates the placeholder map with // types and values from an array of Go values. // TODO: This does not support arguments of the SQL 'Date' type, as there is not // an equivalent type in Go's standard library. It's not currently needed by any // of our internal tables. func golangFillQueryArguments(pinfo *parser.PlaceholderInfo, args []interface{}) { pinfo.Clear() for i, arg := range args { k := fmt.Sprint(i + 1) if arg == nil { pinfo.SetValue(k, parser.DNull) continue } // A type switch to handle a few explicit types with special semantics: // - Datums are passed along as is. // - Time datatypes get special representation in the database. var d parser.Datum switch t := arg.(type) { case parser.Datum: d = t case time.Time: d = parser.MakeDTimestamp(t, time.Microsecond) case time.Duration: d = &parser.DInterval{Duration: duration.Duration{Nanos: t.Nanoseconds()}} case *inf.Dec: dd := &parser.DDecimal{} dd.Set(t) d = dd } if d == nil { // Handle all types which have an underlying type that can be stored in the // database. // Note: if this reflection becomes a performance concern in the future, // commonly used types could be added explicitly into the type switch above // for a performance gain. val := reflect.ValueOf(arg) switch val.Kind() { case reflect.Bool: d = parser.MakeDBool(parser.DBool(val.Bool())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: d = parser.NewDInt(parser.DInt(val.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: d = parser.NewDInt(parser.DInt(val.Uint())) case reflect.Float32, reflect.Float64: d = parser.NewDFloat(parser.DFloat(val.Float())) case reflect.String: d = parser.NewDString(val.String()) case reflect.Slice: // Handle byte slices. if val.Type().Elem().Kind() == reflect.Uint8 { d = parser.NewDBytes(parser.DBytes(val.Bytes())) } } if d == nil { panic(fmt.Sprintf("unexpected type %T", arg)) } } pinfo.SetValue(k, d) } }
// ShowDatabases returns all the databases. // Privileges: None. // Notes: postgres does not have a "show databases" // mysql has a "SHOW DATABASES" permission, but we have no system-level permissions. func (p *planner) ShowDatabases(n *parser.ShowDatabases) (planNode, error) { // TODO(pmattis): This could be implemented as: // // SELECT id FROM system.namespace WHERE parentID = 0 columns := ResultColumns{{Name: "Database", Typ: parser.TypeString}} return &delayedNode{ p: p, name: "SHOW DATABASES", columns: columns, constructor: func(p *planner) (planNode, error) { prefix := sqlbase.MakeNameMetadataKey(keys.RootNamespaceID, "") sr, err := p.txn.Scan(prefix, prefix.PrefixEnd(), 0) if err != nil { return nil, err } v := p.newContainerValuesNode(columns, 0) for _, db := range p.session.virtualSchemas.orderedNames { if err := v.rows.AddRow(parser.DTuple{parser.NewDString(db)}); err != nil { v.rows.Close() return nil, err } } for _, row := range sr { _, name, err := encoding.DecodeUnsafeStringAscending( bytes.TrimPrefix(row.Key, prefix), nil) if err != nil { v.rows.Close() return nil, err } if err := v.rows.AddRow(parser.DTuple{parser.NewDString(name)}); err != nil { v.rows.Close() return nil, err } } return v, nil }, }, nil }
// ShowTables returns all the tables. // Privileges: None. // Notes: postgres does not have a SHOW TABLES statement. // mysql only returns tables you have privileges on. func (p *planner) ShowTables(n *parser.ShowTables) (planNode, error) { // TODO(pmattis): This could be implemented as: // // SELECT name FROM system.namespace // WHERE parentID = (SELECT id FROM system.namespace // WHERE parentID = 0 AND name = <database>) name := p.session.Database if n.Database != "" { name = string(n.Database) } if name == "" { return nil, errNoDatabase } columns := ResultColumns{{Name: "Table", Typ: parser.TypeString}} return &delayedNode{ p: p, name: "SHOW TABLES FROM " + name, columns: columns, constructor: func(p *planner) (planNode, error) { dbDesc, err := p.mustGetDatabaseDesc(name) if err != nil { return nil, err } tableNames, err := p.getTableNames(dbDesc) if err != nil { return nil, err } v := p.newContainerValuesNode(columns, len(tableNames)) for _, name := range tableNames { tableName := name.Table() // Check to see if the table has been dropped. if _, err := p.mustGetTableOrViewDesc(&name); err != nil { if err == errTableDropped { tableName += " (dropped)" } else { return nil, err } } if err := v.rows.AddRow(parser.DTuple{parser.NewDString(tableName)}); err != nil { v.rows.Close() return nil, err } } return v, nil }, }, nil }
// getUIData returns the values and timestamps for the given UI keys. Keys // that are not found will not be returned. func (s *adminServer) getUIData( session *sql.Session, user string, keys []string, ) (*serverpb.GetUIDataResponse, error) { if len(keys) == 0 { return &serverpb.GetUIDataResponse{}, nil } // Query database. query := makeSQLQuery() query.Append("SELECT key, value, lastUpdated FROM system.ui WHERE key IN (") for i, key := range keys { if i != 0 { query.Append(",") } query.Append("$", parser.NewDString(key)) } query.Append(");") if err := query.Errors(); err != nil { return nil, s.serverErrorf("error constructing query: %v", err) } r := s.server.sqlExecutor.ExecuteStatements(session, query.String(), query.QueryArguments()) defer r.Close() if err := s.checkQueryResults(r.ResultList, 1); err != nil { return nil, s.serverError(err) } // Marshal results. resp := serverpb.GetUIDataResponse{KeyValues: make(map[string]serverpb.GetUIDataResponse_Value)} for i, nRows := 0, r.ResultList[0].Rows.Len(); i < nRows; i++ { row := r.ResultList[0].Rows.At(i) dKey, ok := row[0].(*parser.DString) if !ok { return nil, s.serverErrorf("unexpected type for UI key: %T", row[0]) } dValue, ok := row[1].(*parser.DBytes) if !ok { return nil, s.serverErrorf("unexpected type for UI value: %T", row[1]) } dLastUpdated, ok := row[2].(*parser.DTimestamp) if !ok { return nil, s.serverErrorf("unexpected type for UI lastUpdated: %T", row[2]) } resp.KeyValues[string(*dKey)] = serverpb.GetUIDataResponse_Value{ Value: []byte(*dValue), LastUpdated: serverpb.GetUIDataResponse_Timestamp{Sec: dLastUpdated.Unix(), Nsec: uint32(dLastUpdated.Nanosecond())}, } } return &resp, nil }
// Help returns usage information for the builtin functions // Privileges: None func (p *planner) Help(n *parser.Help) (planNode, error) { name := strings.ToLower(n.Name.String()) columns := ResultColumns{ {Name: "Function", Typ: parser.TypeString}, {Name: "Signature", Typ: parser.TypeString}, {Name: "Category", Typ: parser.TypeString}, {Name: "Details", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "HELP " + name, columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) matches, ok := parser.Builtins[name] // TODO(dt): support fuzzy matching. if !ok { return v, nil } for _, f := range matches { row := parser.DTuple{ parser.NewDString(name), parser.NewDString(f.Signature()), parser.NewDString(f.Category()), parser.NewDString(f.Info), } if _, err := v.rows.AddRow(row); err != nil { v.Close() return nil, err } } return v, nil }, }, nil }
// colIDArrayToDatum returns a mock int[] as a DString for a slice of ColumnIDs. // TODO(nvanbenschoten) use real int arrays when they are supported. func colIDArrayToDatum(arr []sqlbase.ColumnID) parser.Datum { if len(arr) == 0 { return parser.DNull } var buf bytes.Buffer buf.WriteByte('{') for i, val := range arr { if i > 0 { buf.WriteByte(',') } buf.WriteString(strconv.Itoa(int(val))) } buf.WriteByte('}') return parser.NewDString(buf.String()) }
// runShowTransactionState returns the state of current transaction. func (p *planner) runShowTransactionState(txnState *txnState, implicitTxn bool) (Result, error) { var result Result result.PGTag = (*parser.Show)(nil).StatementTag() result.Type = (*parser.Show)(nil).StatementType() result.Columns = ResultColumns{{Name: "TRANSACTION STATUS", Typ: parser.TypeString}} result.Rows = NewRowContainer(p.session.makeBoundAccount(), result.Columns, 0) state := txnState.State if implicitTxn { state = NoTxn } if _, err := result.Rows.AddRow(parser.DTuple{parser.NewDString(state.String())}); err != nil { result.Rows.Close() result.Err = err return result, err } return result, nil }
// RandDatum generates a random Datum of the given type. // If null is true, the datum can be DNull. func RandDatum(rng *rand.Rand, typ ColumnType_Kind, null bool) parser.Datum { if null && rng.Intn(10) == 0 { return parser.DNull } switch typ { case ColumnType_BOOL: return parser.MakeDBool(rng.Intn(2) == 1) case ColumnType_INT: return parser.NewDInt(parser.DInt(rng.Int63())) case ColumnType_FLOAT: return parser.NewDFloat(parser.DFloat(rng.NormFloat64())) case ColumnType_DECIMAL: d := &parser.DDecimal{} d.Dec.SetScale(inf.Scale(rng.Intn(40) - 20)) d.Dec.SetUnscaled(rng.Int63()) return d case ColumnType_DATE: return parser.NewDDate(parser.DDate(rng.Intn(10000))) case ColumnType_TIMESTAMP: return &parser.DTimestamp{Time: time.Unix(rng.Int63n(1000000), rng.Int63n(1000000))} case ColumnType_INTERVAL: return &parser.DInterval{Duration: duration.Duration{Months: rng.Int63n(1000), Days: rng.Int63n(1000), Nanos: rng.Int63n(1000000), }} case ColumnType_STRING: // Generate a random ASCII string. p := make([]byte, rng.Intn(10)) for i := range p { p[i] = byte(1 + rng.Intn(127)) } return parser.NewDString(string(p)) case ColumnType_BYTES: p := make([]byte, rng.Intn(10)) _, _ = rng.Read(p) return parser.NewDBytes(parser.DBytes(p)) case ColumnType_TIMESTAMPTZ: return &parser.DTimestampTZ{Time: time.Unix(rng.Int63n(1000000), rng.Int63n(1000000))} case ColumnType_INT_ARRAY: // TODO(cuongdo): we don't support for persistence of arrays yet return parser.DNull default: panic(fmt.Sprintf("invalid type %s", typ)) } }
// MakePrimaryIndexKey creates a key prefix that corresponds to a table row // (in the primary index); it is intended for tests. // // The value types must match the primary key columns (or a prefix of them); // supported types are: - Datum // - bool (converts to DBool) // - int (converts to DInt) // - string (converts to DString) func MakePrimaryIndexKey(desc *TableDescriptor, vals ...interface{}) (roachpb.Key, error) { index := &desc.PrimaryIndex if len(vals) > len(index.ColumnIDs) { return nil, errors.Errorf("got %d values, PK has %d columns", len(vals), len(index.ColumnIDs)) } datums := make([]parser.Datum, len(vals)) for i, v := range vals { switch v := v.(type) { case bool: datums[i] = parser.MakeDBool(parser.DBool(v)) case int: datums[i] = parser.NewDInt(parser.DInt(v)) case string: datums[i] = parser.NewDString(v) case parser.Datum: datums[i] = v default: return nil, errors.Errorf("unexpected value type %T", v) } // Check that the value type matches. colID := index.ColumnIDs[i] for _, c := range desc.Columns { if c.ID == colID { if t := DatumTypeToColumnKind(datums[i].ResolvedType()); t != c.Type.Kind { return nil, errors.Errorf("column %d of type %s, got value of type %s", i, c.Type.Kind, t) } break } } } // Create the ColumnID to index in datums slice map needed by // MakeIndexKeyPrefix. colIDToRowIndex := make(map[ColumnID]int) for i := range vals { colIDToRowIndex[index.ColumnIDs[i]] = i } keyPrefix := MakeIndexKeyPrefix(desc, index.ID) key, _, err := EncodeIndexKey(desc, index, colIDToRowIndex, datums, keyPrefix) if err != nil { return nil, err } return roachpb.Key(key), nil }
// Add a placeholder implementation to test the plan hook. It accepts statements // of the form `SHOW planhook` and returns a single row with the string value // 'planhook'. func init() { testingPlanHook := func( ctx context.Context, stmt parser.Statement, cfg *sql.ExecutorConfig, ) (func() ([]parser.DTuple, error), sql.ResultColumns, error) { show, ok := stmt.(*parser.Show) if !ok || show.Name != "planhook" { return nil, nil, nil } header := sql.ResultColumns{ {Name: "value", Typ: parser.TypeString}, } return func() ([]parser.DTuple, error) { return []parser.DTuple{ {parser.NewDString(show.Name)}, }, nil }, header, nil } sql.AddPlanHook(testingPlanHook) }
func makePrefixRange(prefix parser.DString, datum parser.TypedExpr, complete bool) parser.TypedExpr { if complete { return parser.NewTypedComparisonExpr( parser.EQ, datum, &prefix, ) } if len(prefix) == 0 { return parser.MakeDBool(true) } return parser.NewTypedAndExpr( parser.NewTypedComparisonExpr( parser.GE, datum, &prefix, ), parser.NewTypedComparisonExpr( parser.LT, datum, parser.NewDString(string(roachpb.Key(prefix).PrefixEnd())), ), ) }
// ShowConstraints returns all the constraints for a table. // Privileges: Any privilege on table. // Notes: postgres does not have a SHOW CONSTRAINTS statement. // mysql requires some privilege for any column. func (p *planner) ShowConstraints(n *parser.ShowConstraints) (planNode, error) { tn, err := n.Table.NormalizeWithDatabaseName(p.session.Database) if err != nil { return nil, err } desc, err := p.mustGetTableDesc(tn) if err != nil { return nil, err } if err := p.anyPrivilege(desc); err != nil { return nil, err } columns := ResultColumns{ {Name: "Table", Typ: parser.TypeString}, {Name: "Name", Typ: parser.TypeString}, {Name: "Type", Typ: parser.TypeString}, {Name: "Column(s)", Typ: parser.TypeString}, {Name: "Details", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "SHOW CONSTRAINTS FROM " + tn.String(), columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) info, err := desc.GetConstraintInfo(p.txn) if err != nil { return nil, err } for name, c := range info { detailsDatum := parser.DNull if c.Details != "" { detailsDatum = parser.NewDString(c.Details) } columnsDatum := parser.DNull if c.Columns != nil { columnsDatum = parser.NewDString(strings.Join(c.Columns, ", ")) } kind := string(c.Kind) if c.Unvalidated { kind += " (UNVALIDATED)" } newRow := []parser.Datum{ parser.NewDString(tn.Table()), parser.NewDString(name), parser.NewDString(kind), columnsDatum, detailsDatum, } if _, err := v.rows.AddRow(newRow); err != nil { v.Close() return nil, err } } // Sort the results by constraint name. sort := &sortNode{ ctx: p.ctx(), p: p, ordering: sqlbase.ColumnOrdering{ {ColIdx: 0, Direction: encoding.Ascending}, {ColIdx: 1, Direction: encoding.Ascending}, }, columns: v.columns, } return &selectTopNode{source: v, sort: sort}, nil }, }, nil }
// ShowIndex returns all the indexes for a table. // Privileges: Any privilege on table. // Notes: postgres does not have a SHOW INDEXES statement. // mysql requires some privilege for any column. func (p *planner) ShowIndex(n *parser.ShowIndex) (planNode, error) { tn, err := n.Table.NormalizeWithDatabaseName(p.session.Database) if err != nil { return nil, err } desc, err := p.mustGetTableDesc(tn) if err != nil { return nil, err } if err := p.anyPrivilege(desc); err != nil { return nil, err } columns := ResultColumns{ {Name: "Table", Typ: parser.TypeString}, {Name: "Name", Typ: parser.TypeString}, {Name: "Unique", Typ: parser.TypeBool}, {Name: "Seq", Typ: parser.TypeInt}, {Name: "Column", Typ: parser.TypeString}, {Name: "Direction", Typ: parser.TypeString}, {Name: "Storing", Typ: parser.TypeBool}, } return &delayedNode{ p: p, name: "SHOW INDEX FROM " + tn.String(), columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) appendRow := func(index sqlbase.IndexDescriptor, colName string, sequence int, direction string, isStored bool) error { newRow := parser.DTuple{ parser.NewDString(tn.Table()), parser.NewDString(index.Name), parser.MakeDBool(parser.DBool(index.Unique)), parser.NewDInt(parser.DInt(sequence)), parser.NewDString(colName), parser.NewDString(direction), parser.MakeDBool(parser.DBool(isStored)), } _, err := v.rows.AddRow(newRow) return err } for _, index := range append([]sqlbase.IndexDescriptor{desc.PrimaryIndex}, desc.Indexes...) { sequence := 1 for i, col := range index.ColumnNames { if err := appendRow(index, col, sequence, index.ColumnDirections[i].String(), false); err != nil { v.rows.Close() return nil, err } sequence++ } for _, col := range index.StoreColumnNames { if err := appendRow(index, col, sequence, "N/A", true); err != nil { v.rows.Close() return nil, err } sequence++ } } return v, nil }, }, nil }
// ShowCreateView returns a CREATE VIEW statement for the specified view in // Traditional syntax. // Privileges: Any privilege on view. func (p *planner) ShowCreateView(n *parser.ShowCreateView) (planNode, error) { tn, err := n.View.NormalizeWithDatabaseName(p.session.Database) if err != nil { return nil, err } desc, err := p.mustGetViewDesc(tn) if err != nil { return nil, err } if err := p.anyPrivilege(desc); err != nil { return nil, err } columns := ResultColumns{ {Name: "View", Typ: parser.TypeString}, {Name: "CreateView", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "SHOW CREATE VIEW " + tn.String(), columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) var buf bytes.Buffer fmt.Fprintf(&buf, "CREATE VIEW %s ", quoteNames(n.View.String())) // Determine whether custom column names were specified when the view // was created, and include them if so. customColNames := false stmt, err := parser.ParseOneTraditional(desc.ViewQuery) if err != nil { return nil, errors.Wrapf(err, "failed to parse underlying query from view %q", tn) } sel, ok := stmt.(*parser.Select) if !ok { return nil, errors.Errorf("failed to parse underlying query from view %q as a select", tn) } // When constructing the Select plan, make sure we don't require any // privileges on the underlying tables. p.skipSelectPrivilegeChecks = true defer func() { p.skipSelectPrivilegeChecks = false }() sourcePlan, err := p.Select(sel, []parser.Type{}, false) if err != nil { return nil, err } for i, col := range sourcePlan.Columns() { if col.Name != desc.Columns[i].Name { customColNames = true break } } if customColNames { colNames := make([]string, 0, len(desc.Columns)) for _, col := range desc.Columns { colNames = append(colNames, col.Name) } fmt.Fprintf(&buf, "(%s) ", strings.Join(colNames, ", ")) } fmt.Fprintf(&buf, "AS %s", desc.ViewQuery) if _, err := v.rows.AddRow(parser.DTuple{ parser.NewDString(n.View.String()), parser.NewDString(buf.String()), }); err != nil { v.rows.Close() return nil, err } return v, nil }, }, nil }
// ShowCreateTable returns a CREATE TABLE statement for the specified table in // Traditional syntax. // Privileges: Any privilege on table. func (p *planner) ShowCreateTable(n *parser.ShowCreateTable) (planNode, error) { tn, err := n.Table.NormalizeWithDatabaseName(p.session.Database) if err != nil { return nil, err } desc, err := p.mustGetTableDesc(tn) if err != nil { return nil, err } if err := p.anyPrivilege(desc); err != nil { return nil, err } columns := ResultColumns{ {Name: "Table", Typ: parser.TypeString}, {Name: "CreateTable", Typ: parser.TypeString}, } return &delayedNode{ p: p, name: "SHOW CREATE TABLE " + tn.String(), columns: columns, constructor: func(p *planner) (planNode, error) { v := p.newContainerValuesNode(columns, 0) var buf bytes.Buffer fmt.Fprintf(&buf, "CREATE TABLE %s (", quoteNames(n.Table.String())) var primary string for i, col := range desc.VisibleColumns() { if i != 0 { buf.WriteString(",") } buf.WriteString("\n\t") fmt.Fprintf(&buf, "%s %s", quoteNames(col.Name), col.Type.SQLString()) if col.Nullable { buf.WriteString(" NULL") } else { buf.WriteString(" NOT NULL") } if col.DefaultExpr != nil { fmt.Fprintf(&buf, " DEFAULT %s", *col.DefaultExpr) } if desc.IsPhysicalTable() && desc.PrimaryIndex.ColumnIDs[0] == col.ID { // Only set primary if the primary key is on a visible column (not rowid). primary = fmt.Sprintf(",\n\tCONSTRAINT %s PRIMARY KEY (%s)", quoteNames(desc.PrimaryIndex.Name), quoteNames(desc.PrimaryIndex.ColumnNames...), ) } } buf.WriteString(primary) for _, idx := range desc.Indexes { var storing string if len(idx.StoreColumnNames) > 0 { storing = fmt.Sprintf(" STORING (%s)", quoteNames(idx.StoreColumnNames...)) } interleave, err := p.showCreateInterleave(&idx) if err != nil { v.rows.Close() return nil, err } fmt.Fprintf(&buf, ",\n\t%sINDEX %s (%s)%s%s", isUnique[idx.Unique], quoteNames(idx.Name), quoteNames(idx.ColumnNames...), storing, interleave, ) } for _, fam := range desc.Families { activeColumnNames := make([]string, 0, len(fam.ColumnNames)) for i, colID := range fam.ColumnIDs { if _, err := desc.FindActiveColumnByID(colID); err == nil { activeColumnNames = append(activeColumnNames, fam.ColumnNames[i]) } } fmt.Fprintf(&buf, ",\n\tFAMILY %s (%s)", quoteNames(fam.Name), quoteNames(activeColumnNames...), ) } for _, e := range desc.Checks { fmt.Fprintf(&buf, ",\n\t") if len(e.Name) > 0 { fmt.Fprintf(&buf, "CONSTRAINT %s ", quoteNames(e.Name)) } fmt.Fprintf(&buf, "CHECK (%s)", e.Expr) } buf.WriteString("\n)") interleave, err := p.showCreateInterleave(&desc.PrimaryIndex) if err != nil { v.rows.Close() return nil, err } buf.WriteString(interleave) if _, err := v.rows.AddRow(parser.DTuple{ parser.NewDString(n.Table.String()), parser.NewDString(buf.String()), }); err != nil { v.rows.Close() return nil, err } return v, nil }, }, nil }
// See: https://www.postgresql.org/docs/current/static/catalog-pg-am.html. var pgCatalogAmTable = virtualSchemaTable{ schema: ` CREATE TABLE pg_catalog.pg_am ( oid INT, amname STRING, amhandler INT, amtype CHAR ); `, populate: func(p *planner, addRow func(...parser.Datum) error) error { h := makeOidHasher() h.writeStr(cockroachIndexEncoding) return addRow( h.getOid(), parser.NewDString(cockroachIndexEncoding), parser.DNull, parser.NewDString("i"), ) }, } // See: https://www.postgresql.org/docs/9.6/static/catalog-pg-attrdef.html. var pgCatalogAttrDefTable = virtualSchemaTable{ schema: ` CREATE TABLE pg_catalog.pg_attrdef ( oid INT, adrelid INT, adnum INT, adbin STRING, adsrc STRING
// decodeOidDatum decodes bytes with specified Oid and format code into // a datum. func decodeOidDatum(id oid.Oid, code formatCode, b []byte) (parser.Datum, error) { var d parser.Datum switch id { case oid.T_bool: switch code { case formatText: v, err := strconv.ParseBool(string(b)) if err != nil { return d, err } d = parser.MakeDBool(parser.DBool(v)) case formatBinary: switch b[0] { case 0: d = parser.MakeDBool(false) case 1: d = parser.MakeDBool(true) default: return d, errors.Errorf("unsupported binary bool: %q", b) } default: return d, errors.Errorf("unsupported bool format code: %d", code) } case oid.T_int2: switch code { case formatText: i, err := strconv.ParseInt(string(b), 10, 64) if err != nil { return d, err } d = parser.NewDInt(parser.DInt(i)) case formatBinary: if len(b) < 2 { return d, errors.Errorf("int2 requires 2 bytes for binary format") } i := int16(binary.BigEndian.Uint16(b)) d = parser.NewDInt(parser.DInt(i)) default: return d, errors.Errorf("unsupported int2 format code: %d", code) } case oid.T_int4: switch code { case formatText: i, err := strconv.ParseInt(string(b), 10, 64) if err != nil { return d, err } d = parser.NewDInt(parser.DInt(i)) case formatBinary: if len(b) < 4 { return d, errors.Errorf("int4 requires 4 bytes for binary format") } i := int32(binary.BigEndian.Uint32(b)) d = parser.NewDInt(parser.DInt(i)) default: return d, errors.Errorf("unsupported int4 format code: %d", code) } case oid.T_int8: switch code { case formatText: i, err := strconv.ParseInt(string(b), 10, 64) if err != nil { return d, err } d = parser.NewDInt(parser.DInt(i)) case formatBinary: if len(b) < 8 { return d, errors.Errorf("int8 requires 8 bytes for binary format") } i := int64(binary.BigEndian.Uint64(b)) d = parser.NewDInt(parser.DInt(i)) default: return d, errors.Errorf("unsupported int8 format code: %d", code) } case oid.T_float4: switch code { case formatText: f, err := strconv.ParseFloat(string(b), 64) if err != nil { return d, err } d = parser.NewDFloat(parser.DFloat(f)) case formatBinary: if len(b) < 4 { return d, errors.Errorf("float4 requires 4 bytes for binary format") } f := math.Float32frombits(binary.BigEndian.Uint32(b)) d = parser.NewDFloat(parser.DFloat(f)) default: return d, errors.Errorf("unsupported float4 format code: %d", code) } case oid.T_float8: switch code { case formatText: f, err := strconv.ParseFloat(string(b), 64) if err != nil { return d, err } d = parser.NewDFloat(parser.DFloat(f)) case formatBinary: if len(b) < 8 { return d, errors.Errorf("float8 requires 8 bytes for binary format") } f := math.Float64frombits(binary.BigEndian.Uint64(b)) d = parser.NewDFloat(parser.DFloat(f)) default: return d, errors.Errorf("unsupported float8 format code: %d", code) } case oid.T_numeric: switch code { case formatText: dd := &parser.DDecimal{} if _, ok := dd.SetString(string(b)); !ok { return nil, errors.Errorf("could not parse string %q as decimal", b) } d = dd case formatBinary: r := bytes.NewReader(b) alloc := struct { pgNum pgNumeric i16 int16 dd parser.DDecimal }{} for _, ptr := range []interface{}{ &alloc.pgNum.ndigits, &alloc.pgNum.weight, &alloc.pgNum.sign, &alloc.pgNum.dscale, } { if err := binary.Read(r, binary.BigEndian, ptr); err != nil { return d, err } } if alloc.pgNum.ndigits > 0 { decDigits := make([]byte, 0, alloc.pgNum.ndigits*pgDecDigits) nextDigit := func() error { if err := binary.Read(r, binary.BigEndian, &alloc.i16); err != nil { return err } numZeroes := pgDecDigits for i16 := alloc.i16; i16 > 0; i16 /= 10 { numZeroes-- } for ; numZeroes > 0; numZeroes-- { decDigits = append(decDigits, '0') } return nil } for i := int16(0); i < alloc.pgNum.ndigits-1; i++ { if err := nextDigit(); err != nil { return d, err } if alloc.i16 > 0 { decDigits = strconv.AppendUint(decDigits, uint64(alloc.i16), 10) } } // The last digit may contain padding, which we need to deal with. if err := nextDigit(); err != nil { return d, err } dscale := (alloc.pgNum.ndigits - (alloc.pgNum.weight + 1)) * pgDecDigits if overScale := dscale - alloc.pgNum.dscale; overScale > 0 { dscale -= overScale for i := int16(0); i < overScale; i++ { alloc.i16 /= 10 } } decDigits = strconv.AppendUint(decDigits, uint64(alloc.i16), 10) decString := string(decDigits) if _, ok := alloc.dd.UnscaledBig().SetString(decString, 10); !ok { return nil, errors.Errorf("could not parse string %q as decimal", decString) } alloc.dd.SetScale(inf.Scale(dscale)) } switch alloc.pgNum.sign { case pgNumericPos: case pgNumericNeg: alloc.dd.Neg(&alloc.dd.Dec) default: return d, errors.Errorf("unsupported numeric sign: %d", alloc.pgNum.sign) } d = &alloc.dd default: return d, errors.Errorf("unsupported numeric format code: %d", code) } case oid.T_text, oid.T_varchar: switch code { case formatText, formatBinary: d = parser.NewDString(string(b)) default: return d, errors.Errorf("unsupported text format code: %d", code) } case oid.T_bytea: switch code { case formatText: // http://www.postgresql.org/docs/current/static/datatype-binary.html#AEN5667 // Code cribbed from github.com/lib/pq. // We only support hex encoding. if len(b) >= 2 && bytes.Equal(b[:2], []byte("\\x")) { b = b[2:] // trim off leading "\\x" result := make([]byte, hex.DecodedLen(len(b))) _, err := hex.Decode(result, b) if err != nil { return d, err } d = parser.NewDBytes(parser.DBytes(result)) } else { return d, errors.Errorf("unsupported bytea encoding: %q", b) } case formatBinary: d = parser.NewDBytes(parser.DBytes(b)) default: return d, errors.Errorf("unsupported bytea format code: %d", code) } case oid.T_timestamp: switch code { case formatText: ts, err := parseTs(string(b)) if err != nil { return d, errors.Errorf("could not parse string %q as timestamp", b) } d = parser.MakeDTimestamp(ts, time.Microsecond) case formatBinary: if len(b) < 8 { return d, errors.Errorf("timestamp requires 8 bytes for binary format") } i := int64(binary.BigEndian.Uint64(b)) d = parser.MakeDTimestamp(pgBinaryToTime(i), time.Microsecond) default: return d, errors.Errorf("unsupported timestamp format code: %d", code) } case oid.T_timestamptz: switch code { case formatText: ts, err := parseTs(string(b)) if err != nil { return d, errors.Errorf("could not parse string %q as timestamp", b) } d = parser.MakeDTimestampTZ(ts, time.Microsecond) case formatBinary: if len(b) < 8 { return d, errors.Errorf("timestamptz requires 8 bytes for binary format") } i := int64(binary.BigEndian.Uint64(b)) d = parser.MakeDTimestampTZ(pgBinaryToTime(i), time.Microsecond) default: return d, errors.Errorf("unsupported timestamptz format code: %d", code) } case oid.T_date: switch code { case formatText: ts, err := parseTs(string(b)) if err != nil { res, err := parser.ParseDDate(string(b), time.UTC) if err != nil { return d, errors.Errorf("could not parse string %q as date", b) } d = res } else { daysSinceEpoch := ts.Unix() / secondsInDay d = parser.NewDDate(parser.DDate(daysSinceEpoch)) } case formatBinary: if len(b) < 4 { return d, errors.Errorf("date requires 4 bytes for binary format") } i := int32(binary.BigEndian.Uint32(b)) d = pgBinaryToDate(i) default: return d, errors.Errorf("unsupported date format code: %d", code) } case oid.T_interval: switch code { case formatText: d, err := parser.ParseDInterval(string(b)) if err != nil { return d, errors.Errorf("could not parse string %q as interval", b) } return d, nil default: return d, errors.Errorf("unsupported interval format code: %d", code) } default: return d, errors.Errorf("unsupported OID: %v", id) } return d, nil }
// RowEnglishFn is a GenValueFn which returns an English representation of the // row number, as a DString func RowEnglishFn(row int) parser.Datum { return parser.NewDString(IntToEnglish(row)) }
func benchmarkWriteString(b *testing.B, format formatCode) { benchmarkWriteType(b, parser.NewDString("testing"), format) }
func dumpTable(w io.Writer, conn *sqlConn, origDBName, origTableName string) error { const limit = 100 // Escape names since they can't be used in placeholders. dbname := parser.Name(origDBName).String() tablename := parser.Name(origTableName).String() if err := conn.Exec(fmt.Sprintf("SET DATABASE = %s", dbname), nil); err != nil { return err } // Fetch all table metadata in a transaction and its time to guarantee it // doesn't change between the various SHOW statements. if err := conn.Exec("BEGIN", nil); err != nil { return err } vals, err := conn.QueryRow("SELECT cluster_logical_timestamp()", nil) if err != nil { return err } clusterTS := string(vals[0].([]byte)) // A previous version of the code did a SELECT on system.descriptor. This // required the SELECT privilege to the descriptor table, which only root // has. Allowing non-root to do this would let users see other users' table // descriptors which is a problem in multi-tenancy. // Fetch column types. rows, err := conn.Query(fmt.Sprintf("SHOW COLUMNS FROM %s", tablename), nil) if err != nil { return err } vals = make([]driver.Value, 2) coltypes := make(map[string]string) for { if err := rows.Next(vals); err == io.EOF { break } else if err != nil { return err } nameI, typI := vals[0], vals[1] name, ok := nameI.(string) if !ok { return fmt.Errorf("unexpected value: %T", nameI) } typ, ok := typI.(string) if !ok { return fmt.Errorf("unexpected value: %T", typI) } coltypes[name] = typ } if err := rows.Close(); err != nil { return err } // index holds the names, in order, of the primary key columns. var index []string // Primary index is always the first index returned by SHOW INDEX. rows, err = conn.Query(fmt.Sprintf("SHOW INDEX FROM %s", tablename), nil) if err != nil { return err } vals = make([]driver.Value, 5) var primaryIndex string // Find the primary index columns. for { if err := rows.Next(vals); err == io.EOF { break } else if err != nil { return err } b, ok := vals[1].(string) if !ok { return fmt.Errorf("unexpected value: %T", vals[1]) } if primaryIndex == "" { primaryIndex = b } else if primaryIndex != b { break } b, ok = vals[4].(string) if !ok { return fmt.Errorf("unexpected value: %T", vals[4]) } index = append(index, parser.Name(b).String()) } if err := rows.Close(); err != nil { return err } if len(index) == 0 { return fmt.Errorf("no primary key index found") } indexes := strings.Join(index, ", ") // Build the SELECT query. var sbuf bytes.Buffer fmt.Fprintf(&sbuf, "SELECT %s, * FROM %s@%s AS OF SYSTEM TIME %s", indexes, tablename, primaryIndex, clusterTS) var wbuf bytes.Buffer fmt.Fprintf(&wbuf, " WHERE ROW (%s) > ROW (", indexes) for i := range index { if i > 0 { wbuf.WriteString(", ") } fmt.Fprintf(&wbuf, "$%d", i+1) } wbuf.WriteString(")") // No WHERE clause first time, so add a place to inject it. fmt.Fprintf(&sbuf, "%%s ORDER BY %s LIMIT %d", indexes, limit) bs := sbuf.String() vals, err = conn.QueryRow(fmt.Sprintf("SHOW CREATE TABLE %s", tablename), nil) if err != nil { return err } create := vals[1].(string) if _, err := w.Write([]byte(create)); err != nil { return err } if _, err := w.Write([]byte(";\n")); err != nil { return err } if err := conn.Exec("COMMIT", nil); err != nil { return err } // pk holds the last values of the fetched primary keys var pk []driver.Value q := fmt.Sprintf(bs, "") for { rows, err := conn.Query(q, pk) if err != nil { return err } cols := rows.Columns() pkcols := cols[:len(index)] cols = cols[len(index):] inserts := make([][]string, 0, limit) i := 0 for i < limit { vals := make([]driver.Value, len(cols)+len(pkcols)) if err := rows.Next(vals); err == io.EOF { break } else if err != nil { return err } if pk == nil { q = fmt.Sprintf(bs, wbuf.String()) } pk = vals[:len(index)] vals = vals[len(index):] ivals := make([]string, len(vals)) // Values need to be correctly encoded for INSERT statements in a text file. for si, sv := range vals { switch t := sv.(type) { case nil: ivals[si] = "NULL" case bool: ivals[si] = parser.MakeDBool(parser.DBool(t)).String() case int64: ivals[si] = parser.NewDInt(parser.DInt(t)).String() case float64: ivals[si] = parser.NewDFloat(parser.DFloat(t)).String() case string: ivals[si] = parser.NewDString(t).String() case []byte: switch ct := coltypes[cols[si]]; ct { case "INTERVAL": ivals[si] = fmt.Sprintf("'%s'", t) case "BYTES": ivals[si] = parser.NewDBytes(parser.DBytes(t)).String() default: // STRING and DECIMAL types can have optional length // suffixes, so only examine the prefix of the type. if strings.HasPrefix(coltypes[cols[si]], "STRING") { ivals[si] = parser.NewDString(string(t)).String() } else if strings.HasPrefix(coltypes[cols[si]], "DECIMAL") { ivals[si] = string(t) } else { panic(errors.Errorf("unknown []byte type: %s, %v: %s", t, cols[si], coltypes[cols[si]])) } } case time.Time: var d parser.Datum ct := coltypes[cols[si]] switch ct { case "DATE": d = parser.NewDDateFromTime(t, time.UTC) case "TIMESTAMP": d = parser.MakeDTimestamp(t, time.Nanosecond) case "TIMESTAMP WITH TIME ZONE": d = parser.MakeDTimestampTZ(t, time.Nanosecond) default: panic(errors.Errorf("unknown timestamp type: %s, %v: %s", t, cols[si], coltypes[cols[si]])) } ivals[si] = fmt.Sprintf("'%s'", d) default: panic(errors.Errorf("unknown field type: %T (%s)", t, cols[si])) } } inserts = append(inserts, ivals) i++ } for si, sv := range pk { b, ok := sv.([]byte) if ok && strings.HasPrefix(coltypes[pkcols[si]], "STRING") { // Primary key strings need to be converted to a go string, but not SQL // encoded since they aren't being written to a text file. pk[si] = string(b) } } if err := rows.Close(); err != nil { return err } if i == 0 { break } fmt.Fprintf(w, "\nINSERT INTO %s VALUES", tablename) for idx, values := range inserts { if idx > 0 { fmt.Fprint(w, ",") } fmt.Fprint(w, "\n\t(") for vi, v := range values { if vi > 0 { fmt.Fprint(w, ", ") } fmt.Fprint(w, v) } fmt.Fprint(w, ")") } fmt.Fprintln(w, ";") if i < limit { break } } return nil }
// clarify the hierarchical structure of the plans when doIndent is // true. prefix string // doIndent indicates whether the output should be clarified // with leading white spaces. doIndent bool // makeRow produces one row of EXPLAIN output. makeRow func(level int, typ, field, desc string, plan planNode) // err remembers whether any error was encountered by makeRow. err error } var emptyString = parser.NewDString("") // populateExplain invokes explain() with a makeRow method // which populates a valuesNode. func (e *explainer) populateExplain(v *valuesNode, plan planNode) error { e.makeRow = func(level int, name, field, description string, plan planNode) { if e.err != nil { return } row := parser.DTuple{ parser.NewDInt(parser.DInt(level)), parser.NewDString(name), parser.NewDString(field), parser.NewDString(description), }