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 }