go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/pkg/types/table.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package types 9 10 import ( 11 "encoding/csv" 12 "fmt" 13 "io" 14 ) 15 16 // TableFromCSVReader returns a table from a reader that holds csv data. 17 func TableFromCSVReader(data io.Reader) (*Table, error) { 18 output := new(Table) 19 20 csvr := csv.NewReader(data) 21 22 headers, err := csvr.Read() 23 if err != nil { 24 return nil, err 25 } 26 for _, header := range headers { 27 output.Columns = append(output.Columns, TableColumn{ 28 Name: header, 29 }) 30 } 31 var row []string 32 for { 33 row, err = csvr.Read() 34 if err == io.EOF { 35 break 36 } 37 if err != nil { 38 return nil, err 39 } 40 for index, colValue := range row { 41 if index < len(output.Columns) { 42 output.Columns[index].Values = append(output.Columns[index].Values, colValue) 43 } 44 } 45 } 46 return output, nil 47 } 48 49 // TableFromCSV returns a table from a csv. 50 func TableFromCSV(data [][]string) (*Table, error) { 51 output := new(Table) 52 if len(data) == 0 { 53 return output, nil 54 } 55 56 headers := data[0] 57 for _, header := range headers { 58 output.Columns = append(output.Columns, TableColumn{ 59 Name: header, 60 }) 61 } 62 if len(data) > 1 { 63 for _, row := range data[1:] { 64 for index, colValue := range row { 65 if index < len(output.Columns) { 66 output.Columns[index].Values = append(output.Columns[index].Values, colValue) 67 } 68 } 69 } 70 } 71 return output, nil 72 } 73 74 // TableFromJSON returns a table from a json object. 75 func TableFromJSON(obj map[string]any) (*Table, error) { 76 if obj == nil { 77 return nil, nil 78 } 79 80 output := new(Table) 81 82 rawColumns, ok := obj["columns"] 83 if !ok { 84 return nil, fmt.Errorf("raw table field missing: 'columns'") 85 } 86 87 typedRawColumns, _ := rawColumns.([]any) 88 for _, rawColumn := range typedRawColumns { 89 if typedRawColumn, ok := rawColumn.(map[string]any); ok { 90 tableColumn, err := TableColumnFromJSON(typedRawColumn) 91 if err != nil { 92 return nil, err 93 } 94 if tableColumn != nil { 95 output.Columns = append(output.Columns, *tableColumn) 96 } 97 } 98 } 99 return output, nil 100 } 101 102 // TableColumnFromJSON returns a table column from a json object. 103 func TableColumnFromJSON(obj map[string]any) (*TableColumn, error) { 104 if obj == nil { 105 return nil, nil 106 } 107 output := new(TableColumn) 108 output.Name, _ = obj["name"].(string) 109 output.ValueType, _ = obj["value_type"].(string) 110 111 rawValues, ok := obj["values"] 112 if !ok { 113 return nil, fmt.Errorf("raw table column field missing: 'values'") 114 } 115 if typedRawValues, ok := rawValues.([]any); ok { 116 output.Values = append(output.Values, typedRawValues...) 117 } 118 return output, nil 119 } 120 121 // Table is a column oriented store of data. 122 // 123 // Specifically it breaks the data into columns, and those columns have values 124 // which you can think of as rows. 125 // 126 // This makes inserting a new column very easy (and fast!), but it also means 127 // that some operations are kind of finicky (specifically inserting rows). 128 type Table struct { 129 Columns []TableColumn `json:"columns"` 130 } 131 132 // RowCount returns the maximum number of rows 133 // in any of the columns of the table. 134 func (t *Table) RowCount() (maxRows int) { 135 if len(t.Columns) == 0 { 136 return 137 } 138 maxRows = len(t.Columns[0].Values) 139 for _, c := range t.Columns[1:] { 140 if len(c.Values) > maxRows { 141 maxRows = len(c.Values) 142 } 143 } 144 return 145 } 146 147 // Rows returns the values of the table organized 148 // by row (instead of by column.) 149 func (t *Table) Rows() [][]any { 150 var output [][]any 151 if len(t.Columns) == 0 { 152 return output 153 } 154 var maxRows int = len(t.Columns[0].Values) 155 for _, c := range t.Columns[1:] { 156 if len(c.Values) > maxRows { 157 maxRows = len(c.Values) 158 } 159 } 160 output = make([][]any, 0, maxRows) 161 for rowIndex := 0; rowIndex < maxRows; rowIndex++ { 162 row := make([]any, 0, len(t.Columns)) 163 for _, c := range t.Columns { 164 if rowIndex >= len(c.Values) { 165 row = append(row, nil) 166 continue 167 } 168 row = append(row, c.Values[rowIndex]) 169 } 170 output = append(output, row) 171 } 172 return output 173 } 174 175 // Set sets the value for a given row and column. 176 // 177 // If the row is beyond the current row count for a given column, 178 // nil values are added to satisfy the "density" of the table. 179 func (t *Table) Set(row, col int, value any) error { 180 if col < 0 { 181 return fmt.Errorf("table column index invalid: %d", col) 182 } 183 if col >= len(t.Columns) { 184 return fmt.Errorf("table column index out of range: %d", col) 185 } 186 if row < 0 { 187 return fmt.Errorf("table row index invalid: %d", row) 188 } 189 vlen := len(t.Columns[col].Values) 190 if row >= vlen { 191 missing := (row + 1) - vlen 192 for x := 0; x < missing; x++ { 193 t.Columns[col].Values = append(t.Columns[col].Values, nil) 194 } 195 } 196 t.Columns[col].Values[row] = value 197 return nil 198 } 199 200 // Delete deletes a value at a given row and column. 201 func (t *Table) Delete(row, col int) error { 202 if col < 0 { 203 return fmt.Errorf("table column index invalid: %d", col) 204 } 205 if col >= len(t.Columns) { 206 return fmt.Errorf("table column index out of range: %d", col) 207 } 208 if row < 0 { 209 return fmt.Errorf("table row index invalid: %d", row) 210 } 211 if row >= len(t.Columns[col].Values) { 212 return fmt.Errorf("table row index out of range: %d", row) 213 } 214 t.Columns[col].Values[row] = nil 215 return nil 216 } 217 218 // InsertColumn insets a new column at a given index. 219 func (t *Table) InsertColumn(columnIndex int, newColumn TableColumn) error { 220 if columnIndex < 0 { 221 return fmt.Errorf("table column index invalid: %d", columnIndex) 222 } 223 if columnIndex > len(t.Columns) { 224 return fmt.Errorf("table column index out of range: %d", columnIndex) 225 } 226 if columnIndex >= len(t.Columns) { 227 t.Columns = append(t.Columns, newColumn) 228 } else { 229 before := t.Columns[:columnIndex] 230 after := t.Columns[columnIndex:] 231 t.Columns = append(before, append([]TableColumn{newColumn}, after...)...) 232 } 233 return nil 234 } 235 236 // DeleteColumn deletes a column at a given index. 237 func (t *Table) DeleteColumn(columnIndex int) error { 238 if columnIndex < 0 { 239 return fmt.Errorf("table column index invalid: %d", columnIndex) 240 } 241 if columnIndex >= len(t.Columns) { 242 return fmt.Errorf("table column index out of range: %d", columnIndex) 243 } 244 before := t.Columns[:columnIndex] 245 after := t.Columns[columnIndex+1:] 246 t.Columns = append(before, after...) 247 return nil 248 } 249 250 // InsertRow inserts a row at a given index. 251 func (t *Table) InsertRow(rowIndex int, rowValues []any) error { 252 if rowIndex < 0 { 253 return fmt.Errorf("table insert row index invalid: %d", rowIndex) 254 } 255 for index := range t.Columns { 256 var rowValue any 257 if index < len(rowValues) { 258 rowValue = rowValues[index] 259 } 260 if len(t.Columns[index].Values) >= rowIndex { 261 t.Columns[index].Values = append(t.Columns[index].Values[:rowIndex], append([]any{rowValue}, t.Columns[index].Values[rowIndex:]...)...) 262 } else { 263 missing := (rowIndex - len(t.Columns[index].Values)) 264 for x := 0; x < missing; x++ { 265 t.Columns[index].Values = append(t.Columns[index].Values, nil) 266 } 267 t.Columns[index].Values = append(t.Columns[index].Values, rowValue) 268 } 269 } 270 return nil 271 } 272 273 // DeleteRow deletes a row at a given index. 274 func (t *Table) DeleteRow(rowIndex int) error { 275 if rowIndex < 0 { 276 return fmt.Errorf("table column index invalid: %d", rowIndex) 277 } 278 for columnIndex := range t.Columns { 279 if rowIndex >= len(t.Columns[columnIndex].Values) { 280 continue 281 } 282 before := t.Columns[columnIndex].Values[:rowIndex] 283 after := t.Columns[columnIndex].Values[rowIndex+1:] 284 t.Columns[columnIndex].Values = append(before, after...) 285 } 286 return nil 287 } 288 289 // ReorderColumn swaps the column order from a given old index to a given new index. 290 func (t *Table) ReorderColumn(oldColumn, newColumn int) error { 291 if oldColumn < 0 { 292 return fmt.Errorf("table old column index invalid: %d", oldColumn) 293 } 294 if oldColumn >= len(t.Columns) { 295 return fmt.Errorf("table old column index out of range: %d", oldColumn) 296 } 297 if newColumn < 0 { 298 return fmt.Errorf("table new column index invalid: %d", newColumn) 299 } 300 if newColumn >= len(t.Columns) { 301 return fmt.Errorf("table new column index out of range: %d", newColumn) 302 } 303 t.Columns[oldColumn], t.Columns[newColumn] = t.Columns[newColumn], t.Columns[oldColumn] 304 return nil 305 } 306 307 // ReorderRow swaps the row order from a given old index to a given new index. 308 func (t *Table) ReorderRow(oldRow, newRow int) error { 309 if len(t.Columns) == 0 { 310 return fmt.Errorf("table reorder row; cannot re-order empty table") 311 } 312 minColumnLength := len(t.Columns[0].Values) 313 for _, col := range t.Columns[1:] { 314 if len(col.Values) < minColumnLength { 315 minColumnLength = len(col.Values) 316 } 317 } 318 if oldRow < 0 { 319 return fmt.Errorf("table old row index invalid: %d", oldRow) 320 } 321 if oldRow >= minColumnLength { 322 return fmt.Errorf("table old row index out of range: %d", oldRow) 323 } 324 if newRow < 0 { 325 return fmt.Errorf("table new row index invalid: %d", newRow) 326 } 327 if newRow >= minColumnLength { 328 return fmt.Errorf("table new row index out of range: %d", newRow) 329 } 330 for index := range t.Columns { 331 t.Columns[index].Values[oldRow], t.Columns[index].Values[newRow] = t.Columns[index].Values[newRow], t.Columns[index].Values[oldRow] 332 } 333 return nil 334 } 335 336 // SetColumnName sets the column name for a column at a given index. 337 func (t *Table) SetColumnName(columnIndex int, columnName string) error { 338 if columnIndex < 0 { 339 return fmt.Errorf("table column index invalid: %d", columnIndex) 340 } 341 if columnIndex >= len(t.Columns) { 342 return fmt.Errorf("table column index out of range: %d", columnIndex) 343 } 344 t.Columns[columnIndex].Name = columnName 345 return nil 346 } 347 348 // ApplyOps applies operations to the table. 349 func (t *Table) ApplyOps(ops ...TableOp) error { 350 var err error 351 for _, op := range ops { 352 switch op.Type { 353 case TableOpTypeSet: 354 err = t.Set(op.Row, op.Col, op.Value) 355 case TableOpTypeSetColumnName: 356 err = t.SetColumnName(op.Col, op.Name) 357 case TableOpTypeDelete: 358 err = t.Delete(op.Row, op.Col) 359 case TableOpTypeInsertColumn: 360 err = t.InsertColumn(op.Col, TableColumn{ 361 Name: op.Name, 362 ValueType: op.ValueType, 363 Values: op.Values, 364 }) 365 case TableOpTypeInsertRow: 366 err = t.InsertRow(op.Row, op.Values) 367 case TableOpTypeDeleteColumn: 368 err = t.DeleteColumn(op.Col) 369 case TableOpTypeDeleteRow: 370 err = t.DeleteRow(op.Row) 371 case TableOpTypeReorderColumn: 372 err = t.ReorderColumn(op.Old, op.New) 373 case TableOpTypeReorderRow: 374 err = t.ReorderRow(op.Old, op.New) 375 default: 376 err = fmt.Errorf("invalid table op type: %v", op.Type) 377 } 378 if err != nil { 379 return err 380 } 381 } 382 return nil 383 } 384 385 // Range returns a subset of the table data according to a given range predicate. 386 func (t *Table) Range(r TableRange) [][]any { 387 output := [][]any{} 388 for row := r.Top; row <= r.Bottom; row++ { 389 var rowValues []any 390 for col := r.Left; col <= r.Right; col++ { 391 if col < len(t.Columns) && row < len(t.Columns[col].Values) { 392 rowValues = append(rowValues, t.Columns[col].Values[row]) 393 continue 394 } 395 rowValues = append(rowValues, nil) 396 } 397 output = append(output, rowValues) 398 } 399 return output 400 } 401 402 // TableColumn is a vector of data. 403 type TableColumn struct { 404 Name string `json:"name"` 405 ValueType string `json:"value_type"` 406 Values []any `json:"values"` 407 } 408 409 // TableOp is an operation a user can take on a table. 410 // 411 // NOTE(wc): The format here is kind of gross because we don't have the ability 412 // to do the typescript thing where we discriminate types based on a string field value. 413 // 414 // We could use interfaces and do a bunch of careful implementing but this is fine for now. 415 type TableOp struct { 416 // type determines what function to call 417 Type string `json:"type"` 418 // the column name typically 419 Name string `json:"name"` 420 // the colmn value type typically 421 ValueType string `json:"value_type"` 422 423 // used for various commands 424 Col int `json:"col"` 425 Row int `json:"row"` 426 427 // used for reodering 428 Old int `json:"old"` 429 New int `json:"new"` 430 431 // for setting the value 432 Value any `json:"value"` 433 // for inserting rows and columns 434 Values []any `json:"values"` 435 } 436 437 // TableOpType is a type of operation on a table. 438 type TableOpType string 439 440 // TableOp types. 441 const ( 442 TableOpTypeSet = "set" 443 TableOpTypeSetColumnName = "set_column_name" 444 TableOpTypeDelete = "delete" 445 TableOpTypeInsertColumn = "insert_column" 446 TableOpTypeDeleteColumn = "delete_column" 447 TableOpTypeInsertRow = "insert_row" 448 TableOpTypeDeleteRow = "delete_row" 449 TableOpTypeReorderColumn = "reorder_column" 450 TableOpTypeReorderRow = "reorder_row" 451 ) 452 453 // TableInfo is the table metdata less the actual data. 454 type TableInfo struct { 455 Rows int `json:"rows"` 456 Columns []string `json:"columns"` 457 } 458 459 // TableRange holds visible coordinates. 460 type TableRange struct { 461 Top int `json:"top"` 462 Left int `json:"left"` 463 Bottom int `json:"bottom"` 464 Right int `json:"right"` 465 }