github.com/influxdata/influxdb/v2@v2.7.6/measurement_schema.go (about)

     1  package influxdb
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	influxid "github.com/influxdata/influxdb/v2/kit/platform"
    12  	influxerror "github.com/influxdata/influxdb/v2/kit/platform/errors"
    13  	"github.com/influxdata/influxdb/v2/models"
    14  	"go.uber.org/multierr"
    15  )
    16  
    17  // SchemaType differentiates the supported schema for a bucket.
    18  type SchemaType int
    19  
    20  const (
    21  	SchemaTypeImplicit SchemaType = iota // SchemaTypeImplicit specifies the bucket has an implicit schema.
    22  	SchemaTypeExplicit                   // SchemaTypeExplicit specifies the bucket has an explicit schema.
    23  )
    24  
    25  // SchemaTypeFromString returns the SchemaType for s
    26  // or nil if none exists.
    27  func SchemaTypeFromString(s string) *SchemaType {
    28  	switch s {
    29  	case "implicit":
    30  		return SchemaTypeImplicit.Ptr()
    31  	case "explicit":
    32  		return SchemaTypeExplicit.Ptr()
    33  	default:
    34  		return nil
    35  	}
    36  }
    37  
    38  func (s *SchemaType) String() string {
    39  	if s == nil {
    40  		return ""
    41  	}
    42  
    43  	switch s := *s; s {
    44  	case SchemaTypeImplicit:
    45  		return "implicit"
    46  	case SchemaTypeExplicit:
    47  		return "explicit"
    48  	default:
    49  		return "SchemaType(" + strconv.FormatInt(int64(s), 10) + ")"
    50  	}
    51  }
    52  
    53  func (s *SchemaType) UnmarshalJSON(d []byte) error {
    54  	var val string
    55  	if err := json.Unmarshal(d, &val); err != nil {
    56  		return err
    57  	}
    58  
    59  	switch val {
    60  	case "implicit":
    61  		*s = SchemaTypeImplicit
    62  	case "explicit":
    63  		*s = SchemaTypeExplicit
    64  	default:
    65  		return errors.New("unexpected value")
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func (s *SchemaType) Equals(other *SchemaType) bool {
    72  	if s == nil && other == nil {
    73  		return true
    74  	} else if s == nil || other == nil {
    75  		return false
    76  	}
    77  
    78  	return *s == *other
    79  }
    80  
    81  func (s SchemaType) MarshalJSON() ([]byte, error) {
    82  	switch s {
    83  	case SchemaTypeImplicit:
    84  		return []byte(`"implicit"`), nil
    85  	case SchemaTypeExplicit:
    86  		return []byte(`"explicit"`), nil
    87  	default:
    88  		return nil, errors.New("unexpected value")
    89  	}
    90  }
    91  
    92  // Ptr returns a pointer to s.
    93  func (s SchemaType) Ptr() *SchemaType { return &s }
    94  
    95  // SemanticColumnType specifies the semantics of a measurement column.
    96  type SemanticColumnType int
    97  
    98  const (
    99  	SemanticColumnTypeTimestamp SemanticColumnType = iota // SemanticColumnTypeTimestamp identifies the column is used as the timestamp
   100  	SemanticColumnTypeTag                                 // SemanticColumnTypeTag identifies the column is used as a tag
   101  	SemanticColumnTypeField                               // SemanticColumnTypeField identifies the column is used as a field
   102  )
   103  
   104  func SemanticColumnTypeFromString(s string) *SemanticColumnType {
   105  	switch s {
   106  	case "timestamp":
   107  		return SemanticColumnTypeTimestamp.Ptr()
   108  	case "tag":
   109  		return SemanticColumnTypeTag.Ptr()
   110  	case "field":
   111  		return SemanticColumnTypeField.Ptr()
   112  	default:
   113  		return nil
   114  	}
   115  }
   116  
   117  func (s SemanticColumnType) String() string {
   118  	switch s {
   119  	case SemanticColumnTypeTimestamp:
   120  		return "timestamp"
   121  	case SemanticColumnTypeTag:
   122  		return "tag"
   123  	case SemanticColumnTypeField:
   124  		return "field"
   125  	default:
   126  		return "SemanticColumnType(" + strconv.FormatInt(int64(s), 10) + ")"
   127  	}
   128  }
   129  
   130  func (s SemanticColumnType) Ptr() *SemanticColumnType {
   131  	return &s
   132  }
   133  
   134  func (s *SemanticColumnType) UnmarshalJSON(d []byte) error {
   135  	var val string
   136  	if err := json.Unmarshal(d, &val); err != nil {
   137  		return err
   138  	}
   139  
   140  	switch val {
   141  	case "timestamp":
   142  		*s = SemanticColumnTypeTimestamp
   143  	case "tag":
   144  		*s = SemanticColumnTypeTag
   145  	case "field":
   146  		*s = SemanticColumnTypeField
   147  	default:
   148  		return errors.New("unexpected value")
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func (s SemanticColumnType) MarshalJSON() ([]byte, error) {
   155  	switch s {
   156  	case SemanticColumnTypeTimestamp:
   157  		return []byte(`"timestamp"`), nil
   158  	case SemanticColumnTypeTag:
   159  		return []byte(`"tag"`), nil
   160  	case SemanticColumnTypeField:
   161  		return []byte(`"field"`), nil
   162  	default:
   163  		return nil, errors.New("unexpected value")
   164  	}
   165  }
   166  
   167  type SchemaColumnDataType uint
   168  
   169  const (
   170  	SchemaColumnDataTypeFloat SchemaColumnDataType = iota
   171  	SchemaColumnDataTypeInteger
   172  	SchemaColumnDataTypeUnsigned
   173  	SchemaColumnDataTypeString
   174  	SchemaColumnDataTypeBoolean
   175  )
   176  
   177  func SchemaColumnDataTypeFromString(s string) *SchemaColumnDataType {
   178  	switch s {
   179  	case "float":
   180  		return SchemaColumnDataTypeFloat.Ptr()
   181  	case "integer":
   182  		return SchemaColumnDataTypeInteger.Ptr()
   183  	case "unsigned":
   184  		return SchemaColumnDataTypeUnsigned.Ptr()
   185  	case "string":
   186  		return SchemaColumnDataTypeString.Ptr()
   187  	case "boolean":
   188  		return SchemaColumnDataTypeBoolean.Ptr()
   189  	default:
   190  		return nil
   191  	}
   192  }
   193  
   194  // Ptr returns a pointer to s.
   195  func (s SchemaColumnDataType) Ptr() *SchemaColumnDataType { return &s }
   196  
   197  func (s *SchemaColumnDataType) String() string {
   198  	if s == nil {
   199  		return ""
   200  	}
   201  
   202  	switch *s {
   203  	case SchemaColumnDataTypeFloat:
   204  		return "float"
   205  	case SchemaColumnDataTypeInteger:
   206  		return "integer"
   207  	case SchemaColumnDataTypeUnsigned:
   208  		return "unsigned"
   209  	case SchemaColumnDataTypeString:
   210  		return "string"
   211  	case SchemaColumnDataTypeBoolean:
   212  		return "boolean"
   213  	default:
   214  		return "SchemaColumnDataType(" + strconv.FormatInt(int64(*s), 10) + ")"
   215  	}
   216  }
   217  
   218  func (s *SchemaColumnDataType) UnmarshalJSON(d []byte) error {
   219  	var val string
   220  	if err := json.Unmarshal(d, &val); err != nil {
   221  		return err
   222  	}
   223  
   224  	switch val {
   225  	case "float":
   226  		*s = SchemaColumnDataTypeFloat
   227  	case "integer":
   228  		*s = SchemaColumnDataTypeInteger
   229  	case "unsigned":
   230  		*s = SchemaColumnDataTypeUnsigned
   231  	case "string":
   232  		*s = SchemaColumnDataTypeString
   233  	case "boolean":
   234  		*s = SchemaColumnDataTypeBoolean
   235  	default:
   236  		return errors.New("unexpected value")
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  func (s SchemaColumnDataType) MarshalJSON() ([]byte, error) {
   243  	switch s {
   244  	case SchemaColumnDataTypeFloat:
   245  		return []byte(`"float"`), nil
   246  	case SchemaColumnDataTypeInteger:
   247  		return []byte(`"integer"`), nil
   248  	case SchemaColumnDataTypeUnsigned:
   249  		return []byte(`"unsigned"`), nil
   250  	case SchemaColumnDataTypeString:
   251  		return []byte(`"string"`), nil
   252  	case SchemaColumnDataTypeBoolean:
   253  		return []byte(`"boolean"`), nil
   254  	default:
   255  		return nil, errors.New("unexpected value")
   256  	}
   257  }
   258  
   259  var (
   260  	schemaTypeToFieldTypeMap = [...]models.FieldType{
   261  		SchemaColumnDataTypeFloat:    models.Float,
   262  		SchemaColumnDataTypeInteger:  models.Integer,
   263  		SchemaColumnDataTypeUnsigned: models.Unsigned,
   264  		SchemaColumnDataTypeString:   models.String,
   265  		SchemaColumnDataTypeBoolean:  models.Boolean,
   266  	}
   267  )
   268  
   269  // ToFieldType maps SchemaColumnDataType to the equivalent models.FieldType or
   270  // models.Empty if no such mapping exists.
   271  func (s SchemaColumnDataType) ToFieldType() models.FieldType {
   272  	if int(s) > len(schemaTypeToFieldTypeMap) {
   273  		return models.Empty
   274  	}
   275  	return schemaTypeToFieldTypeMap[s]
   276  }
   277  
   278  type MeasurementSchema struct {
   279  	ID       influxid.ID               `json:"id,omitempty"`
   280  	OrgID    influxid.ID               `json:"orgID"`
   281  	BucketID influxid.ID               `json:"bucketID"`
   282  	Name     string                    `json:"name"`
   283  	Columns  []MeasurementSchemaColumn `json:"columns"`
   284  	CRUDLog
   285  }
   286  
   287  func (m *MeasurementSchema) Validate() error {
   288  	var err error
   289  
   290  	err = multierr.Append(err, m.validateName("name", m.Name))
   291  	err = multierr.Append(err, m.validateColumns())
   292  
   293  	return err
   294  }
   295  
   296  // ValidateMeasurementSchemaName determines if name is a valid identifier for
   297  // a measurement schema or column name and if not, returns an error.
   298  func ValidateMeasurementSchemaName(name string) error {
   299  	if len(name) == 0 {
   300  		return ErrMeasurementSchemaNameTooShort
   301  	}
   302  
   303  	if len(name) > 128 {
   304  		return ErrMeasurementSchemaNameTooLong
   305  	}
   306  
   307  	if err := models.CheckToken([]byte(name)); err != nil {
   308  		return &influxerror.Error{
   309  			Code: influxerror.EInvalid,
   310  			Err:  err,
   311  		}
   312  	}
   313  
   314  	if strings.HasPrefix(name, "_") {
   315  		return ErrMeasurementSchemaNameUnderscore
   316  	}
   317  
   318  	if strings.Contains(name, `"`) || strings.Contains(name, `'`) {
   319  		return ErrMeasurementSchemaNameQuotes
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func (m *MeasurementSchema) validateName(prefix, name string) error {
   326  	if err := ValidateMeasurementSchemaName(name); err != nil {
   327  		return fmt.Errorf("%s %q: %w", prefix, name, err)
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  // columns implements sort.Interface to efficiently sort a MeasurementSchemaColumn slice
   334  // by using indices to store the sorted element indices.
   335  type columns struct {
   336  	indices []int // indices is a list of indices representing a sorted columns
   337  	columns []MeasurementSchemaColumn
   338  }
   339  
   340  // newColumns returns an instance of columns which contains a sorted version of c.
   341  func newColumns(c []MeasurementSchemaColumn) columns {
   342  	colIndices := make([]int, len(c))
   343  	for i := range c {
   344  		colIndices[i] = i
   345  	}
   346  	res := columns{
   347  		indices: colIndices,
   348  		columns: c,
   349  	}
   350  	sort.Sort(res)
   351  	return res
   352  }
   353  
   354  func (c columns) Len() int {
   355  	return len(c.columns)
   356  }
   357  
   358  func (c columns) Less(i, j int) bool {
   359  	return c.columns[c.indices[i]].Name < c.columns[c.indices[j]].Name
   360  }
   361  
   362  func (c columns) Swap(i, j int) {
   363  	c.indices[i], c.indices[j] = c.indices[j], c.indices[i]
   364  }
   365  
   366  // Index returns the sorted
   367  func (c columns) Index(i int) *MeasurementSchemaColumn {
   368  	return &c.columns[c.indices[i]]
   369  }
   370  
   371  func (m *MeasurementSchema) validateColumns() (err error) {
   372  	if len(m.Columns) == 0 {
   373  		return ErrMeasurementSchemaColumnsMissing
   374  	}
   375  
   376  	cols := newColumns(m.Columns)
   377  
   378  	timeCount := 0
   379  	fieldCount := 0
   380  	for i := range cols.columns {
   381  		col := &cols.columns[i]
   382  
   383  		err = multierr.Append(err, m.validateName("column name", col.Name))
   384  
   385  		// special handling for time column
   386  		if col.Name == "time" {
   387  			timeCount++
   388  			if col.Type != SemanticColumnTypeTimestamp {
   389  				err = multierr.Append(err, ErrMeasurementSchemaColumnsTimeInvalidSemantic)
   390  			} else if col.DataType != nil {
   391  				err = multierr.Append(err, ErrMeasurementSchemaColumnsTimestampSemanticDataType)
   392  			}
   393  			continue
   394  		}
   395  
   396  		// ensure no other columns have a timestamp semantic
   397  		switch col.Type {
   398  		case SemanticColumnTypeTimestamp:
   399  			if col.Name != "time" {
   400  				err = multierr.Append(err, ErrMeasurementSchemaColumnsTimestampSemanticInvalidName)
   401  			} else {
   402  				if col.DataType != nil {
   403  					err = multierr.Append(err, ErrMeasurementSchemaColumnsTimestampSemanticDataType)
   404  				}
   405  			}
   406  
   407  		case SemanticColumnTypeTag:
   408  			// ensure tag columns don't include a data type value
   409  			if col.DataType != nil {
   410  				err = multierr.Append(err, ErrMeasurementSchemaColumnsTagSemanticDataType)
   411  			}
   412  
   413  		case SemanticColumnTypeField:
   414  			if col.DataType == nil {
   415  				err = multierr.Append(err, ErrMeasurementSchemaColumnsFieldSemanticMissingDataType)
   416  			}
   417  			fieldCount++
   418  		}
   419  	}
   420  
   421  	if timeCount == 0 {
   422  		err = multierr.Append(err, ErrMeasurementSchemaColumnsMissingTime)
   423  	}
   424  
   425  	// ensure there is at least one field defined
   426  	if fieldCount == 0 {
   427  		err = multierr.Append(err, ErrMeasurementSchemaColumnsMissingFields)
   428  	}
   429  
   430  	// check for duplicate columns using general UTF-8 case insensitive comparison
   431  	for i := range cols.columns[1:] {
   432  		if strings.EqualFold(cols.Index(i).Name, cols.Index(i+1).Name) {
   433  			err = multierr.Append(err, ErrMeasurementSchemaColumnsDuplicateNames)
   434  			break
   435  		}
   436  	}
   437  
   438  	return err
   439  }
   440  
   441  type MeasurementSchemaColumn struct {
   442  	Name     string                `json:"name"`
   443  	Type     SemanticColumnType    `json:"type"`
   444  	DataType *SchemaColumnDataType `json:"dataType,omitempty"`
   445  }