Files
s3/weed/mq/schema/flat_schema_utils.go
zooqueen dd74d3e6d6 Replace AGPL MinIO fork with Apache-2.0 SeaweedFS fork; rebrand CLI weed->s3
hanzoai/s3 was an AGPL-3.0 MinIO fork whose network-copyleft blocks embedding
and reselling as a managed service. Replace it wholesale with a fork of
SeaweedFS (github.com/seaweedfs/seaweedfs, Apache-2.0, Go) which ships a
built-in S3 gateway + Filer.

- License: AGPL-3.0 -> Apache-2.0 (upstream LICENSE kept verbatim, incl.
  "Copyright 2025 Chris Lu"); NOTICE added crediting SeaweedFS. No AGPL/BUSL/
  SSPL/GPL or enterprise ("ee") files remain.
- Module: go.mod path set to github.com/hanzoai/s3 with a self-replace
  (replace github.com/seaweedfs/seaweedfs => ./) so the ~1800 internal Go files
  keep their upstream import paths with zero churn. Internal identifiers and
  import paths are intentionally NOT renamed.
- CLI rebrand: the binary is built as `s3` (was `weed`); root usage, per-command
  help/usage/flag strings, shell autocomplete program name, and the product name
  in CLI output now read "Hanzo S3" / `s3`. Users never see `weed`.
  (e.g. `s3 server`, `s3 filer`, `s3 master`, `s3 volume`, `s3 s3`, `s3 mount`.)
- Build: `make` (or `go build -o s3 ./weed`) builds the `s3` binary green;
  `cmd/weed-db` and `cmd/weed-sql` also build; `go vet ./weed/command/` clean.
- README/LLM.md rewritten for Hanzo S3; AGENTS.md/CLAUDE.md symlinks preserved.
- Dropped a 33MB prebuilt Mach-O `stress` binary upstream had committed; `.gitignore`
  updated to ignore the `s3` build output.
2026-06-18 23:41:25 -07:00

207 lines
5.5 KiB
Go

package schema
import (
"fmt"
"sort"
"strings"
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
)
// SplitFlatSchemaToKeyValue takes a flat RecordType and key column names,
// returns separate key and value RecordTypes
func SplitFlatSchemaToKeyValue(flatSchema *schema_pb.RecordType, keyColumns []string) (*schema_pb.RecordType, *schema_pb.RecordType, error) {
if flatSchema == nil {
return nil, nil, nil
}
// Create maps for fast lookup
keyColumnSet := make(map[string]bool)
for _, col := range keyColumns {
keyColumnSet[col] = true
}
var keyFields []*schema_pb.Field
var valueFields []*schema_pb.Field
// Split fields based on key columns
for _, field := range flatSchema.Fields {
if keyColumnSet[field.Name] {
// Create key field with reindexed field index
keyField := &schema_pb.Field{
Name: field.Name,
FieldIndex: int32(len(keyFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
keyFields = append(keyFields, keyField)
} else {
// Create value field with reindexed field index
valueField := &schema_pb.Field{
Name: field.Name,
FieldIndex: int32(len(valueFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
valueFields = append(valueFields, valueField)
}
}
// Validate that all key columns were found
if len(keyFields) != len(keyColumns) {
missingCols := []string{}
for _, col := range keyColumns {
found := false
for _, field := range keyFields {
if field.Name == col {
found = true
break
}
}
if !found {
missingCols = append(missingCols, col)
}
}
if len(missingCols) > 0 {
return nil, nil, fmt.Errorf("key columns not found in schema: %v", missingCols)
}
}
var keyRecordType *schema_pb.RecordType
if len(keyFields) > 0 {
keyRecordType = &schema_pb.RecordType{Fields: keyFields}
}
var valueRecordType *schema_pb.RecordType
if len(valueFields) > 0 {
valueRecordType = &schema_pb.RecordType{Fields: valueFields}
}
return keyRecordType, valueRecordType, nil
}
// CombineFlatSchemaFromKeyValue creates a flat RecordType by combining key and value schemas
// Key fields are placed first, then value fields
func CombineFlatSchemaFromKeyValue(keySchema *schema_pb.RecordType, valueSchema *schema_pb.RecordType) (*schema_pb.RecordType, []string) {
var combinedFields []*schema_pb.Field
var keyColumns []string
// Add key fields first
if keySchema != nil {
for _, field := range keySchema.Fields {
combinedField := &schema_pb.Field{
Name: field.Name,
FieldIndex: int32(len(combinedFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
combinedFields = append(combinedFields, combinedField)
keyColumns = append(keyColumns, field.Name)
}
}
// Add value fields
if valueSchema != nil {
for _, field := range valueSchema.Fields {
// Check for name conflicts
fieldName := field.Name
for _, keyCol := range keyColumns {
if fieldName == keyCol {
// This shouldn't happen in well-formed schemas, but handle gracefully
fieldName = "value_" + fieldName
break
}
}
combinedField := &schema_pb.Field{
Name: fieldName,
FieldIndex: int32(len(combinedFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
combinedFields = append(combinedFields, combinedField)
}
}
if len(combinedFields) == 0 {
return nil, keyColumns
}
return &schema_pb.RecordType{Fields: combinedFields}, keyColumns
}
// ExtractKeyColumnsFromCombinedSchema tries to infer key columns from a combined schema
// that was created using CreateCombinedRecordType (with key_ prefixes)
func ExtractKeyColumnsFromCombinedSchema(combinedSchema *schema_pb.RecordType) (flatSchema *schema_pb.RecordType, keyColumns []string) {
if combinedSchema == nil {
return nil, nil
}
var flatFields []*schema_pb.Field
var keyColumns_ []string
for _, field := range combinedSchema.Fields {
if strings.HasPrefix(field.Name, "key_") {
// This is a key field - remove the prefix
originalName := strings.TrimPrefix(field.Name, "key_")
flatField := &schema_pb.Field{
Name: originalName,
FieldIndex: int32(len(flatFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
flatFields = append(flatFields, flatField)
keyColumns_ = append(keyColumns_, originalName)
} else {
// This is a value field
flatField := &schema_pb.Field{
Name: field.Name,
FieldIndex: int32(len(flatFields)),
Type: field.Type,
IsRepeated: field.IsRepeated,
IsRequired: field.IsRequired,
}
flatFields = append(flatFields, flatField)
}
}
// Sort key columns to ensure deterministic order
sort.Strings(keyColumns_)
if len(flatFields) == 0 {
return nil, keyColumns_
}
return &schema_pb.RecordType{Fields: flatFields}, keyColumns_
}
// ValidateKeyColumns checks that all key columns exist in the schema
func ValidateKeyColumns(schema *schema_pb.RecordType, keyColumns []string) error {
if schema == nil || len(keyColumns) == 0 {
return nil
}
fieldNames := make(map[string]bool)
for _, field := range schema.Fields {
fieldNames[field.Name] = true
}
var missingColumns []string
for _, keyCol := range keyColumns {
if !fieldNames[keyCol] {
missingColumns = append(missingColumns, keyCol)
}
}
if len(missingColumns) > 0 {
return fmt.Errorf("key columns not found in schema: %v", missingColumns)
}
return nil
}