refactor: update MockTableAnalyzer methods to improve parameter handling and capture range constraints
This commit is contained in:
@@ -10,32 +10,39 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MockTableAnalyzer struct {
|
type MockTableAnalyzer struct {
|
||||||
minValue int64
|
minValue int64
|
||||||
maxValue int64
|
maxValue int64
|
||||||
|
totalRows int64
|
||||||
|
capturedRangeConstraint config.RangeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableAnalyzer) QueryColumnTypes(ctx context.Context, tableInfo config.TableInfo) ([]models.ColumnType, error) {
|
func (m *MockTableAnalyzer) QueryColumnTypes(_ context.Context, _ config.TableInfo) ([]models.ColumnType, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableAnalyzer) EstimateTotalRows(ctx context.Context, tableInfo config.TableInfo) (int64, error) {
|
func (m *MockTableAnalyzer) EstimateTotalRows(_ context.Context, _ config.TableInfo) (int64, error) {
|
||||||
return 0, nil
|
return m.totalRows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableAnalyzer) QueryMaxMinFromColumn(ctx context.Context, tableInfo config.TableInfo, columnName string) (etl.MaxMinColumnResult, error) {
|
func (m *MockTableAnalyzer) QueryMaxMinFromColumn(_ context.Context, _ config.TableInfo, _ string) (etl.MaxMinColumnResult, error) {
|
||||||
return etl.MaxMinColumnResult{Min: m.minValue, Max: m.maxValue}, nil
|
return etl.MaxMinColumnResult{Min: m.minValue, Max: m.maxValue}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockTableAnalyzer) CalculatePartitionRanges(ctx context.Context, tableInfo config.TableInfo, partitionColumn string, maxPartitions int64, rangeConstraint config.RangeConfig) ([]models.Partition, error) {
|
func (m *MockTableAnalyzer) CalculatePartitionRanges(_ context.Context, _ config.TableInfo, _ string, _ int64, rangeConstraint config.RangeConfig) ([]models.Partition, error) {
|
||||||
return nil, nil
|
m.capturedRangeConstraint = rangeConstraint
|
||||||
|
return []models.Partition{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:fix inline
|
||||||
|
func ptr64(v int64) *int64 { return new(v) }
|
||||||
|
|
||||||
|
var testTableInfo = config.TableInfo{Schema: "dbo", Table: "test"}
|
||||||
|
|
||||||
func TestCalculatePartitionsEstimation_NoOverlap(t *testing.T) {
|
func TestCalculatePartitionsEstimation_NoOverlap(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mock := &MockTableAnalyzer{minValue: 0, maxValue: 100}
|
mock := &MockTableAnalyzer{minValue: 0, maxValue: 100}
|
||||||
tableInfo := config.TableInfo{Schema: "dbo", Table: "test"}
|
|
||||||
|
|
||||||
partitions, err := calculatePartitionsEstimation(ctx, mock, tableInfo, "id", 4, config.RangeConfig{})
|
partitions, err := calculatePartitionsEstimation(ctx, mock, testTableInfo, "id", 4, config.RangeConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -47,26 +54,17 @@ func TestCalculatePartitionsEstimation_NoOverlap(t *testing.T) {
|
|||||||
for i := 0; i < len(partitions)-1; i++ {
|
for i := 0; i < len(partitions)-1; i++ {
|
||||||
current := partitions[i].Range
|
current := partitions[i].Range
|
||||||
next := partitions[i+1].Range
|
next := partitions[i+1].Range
|
||||||
|
if current.Max == next.Min && current.IsMaxInclusive && next.IsMinInclusive {
|
||||||
if current.Max == next.Min {
|
t.Errorf("partition %d and %d overlap at value %d (both inclusive)", i, i+1, current.Max)
|
||||||
if current.IsMaxInclusive && next.IsMinInclusive {
|
|
||||||
t.Errorf("partition %d and %d overlap at value %d (both inclusive)", i, i+1, current.Max)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Partitions generated:")
|
|
||||||
for i, p := range partitions {
|
|
||||||
t.Logf(" P%d: [%d, %d] (minInc=%v, maxInc=%v)", i, p.Range.Min, p.Range.Max, p.Range.IsMinInclusive, p.Range.IsMaxInclusive)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculatePartitionsEstimation_CoverageComplete(t *testing.T) {
|
func TestCalculatePartitionsEstimation_CoverageComplete(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mock := &MockTableAnalyzer{minValue: 1000, maxValue: 2000}
|
mock := &MockTableAnalyzer{minValue: 1000, maxValue: 2000}
|
||||||
tableInfo := config.TableInfo{Schema: "dbo", Table: "test"}
|
|
||||||
|
|
||||||
partitions, err := calculatePartitionsEstimation(ctx, mock, tableInfo, "id", 5, config.RangeConfig{})
|
partitions, err := calculatePartitionsEstimation(ctx, mock, testTableInfo, "id", 5, config.RangeConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -84,9 +82,8 @@ func TestCalculatePartitionsEstimation_CoverageComplete(t *testing.T) {
|
|||||||
func TestCalculatePartitionsEstimation_FirstPartitionInclusive(t *testing.T) {
|
func TestCalculatePartitionsEstimation_FirstPartitionInclusive(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mock := &MockTableAnalyzer{minValue: 50, maxValue: 70}
|
mock := &MockTableAnalyzer{minValue: 50, maxValue: 70}
|
||||||
tableInfo := config.TableInfo{Schema: "dbo", Table: "test"}
|
|
||||||
|
|
||||||
partitions, err := calculatePartitionsEstimation(ctx, mock, tableInfo, "id", 3, config.RangeConfig{})
|
partitions, err := calculatePartitionsEstimation(ctx, mock, testTableInfo, "id", 3, config.RangeConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -105,3 +102,231 @@ func TestCalculatePartitionsEstimation_FirstPartitionInclusive(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Exact_NoRange_PassesEmptyConstraint(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000}
|
||||||
|
|
||||||
|
_, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, config.RangeConfig{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mock.capturedRangeConstraint.Min != nil || mock.capturedRangeConstraint.Max != nil {
|
||||||
|
t.Errorf("expected empty range constraint, got min=%v max=%v",
|
||||||
|
mock.capturedRangeConstraint.Min, mock.capturedRangeConstraint.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Exact_BothBounds_PassesBothToAnalyzer(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(200), Max: ptr64(800), IsMinInclusive: true, IsMaxInclusive: true}
|
||||||
|
|
||||||
|
_, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := mock.capturedRangeConstraint
|
||||||
|
if rc.Min == nil || *rc.Min != 200 {
|
||||||
|
t.Errorf("expected Min=200, got %v", rc.Min)
|
||||||
|
}
|
||||||
|
if rc.Max == nil || *rc.Max != 800 {
|
||||||
|
t.Errorf("expected Max=800, got %v", rc.Max)
|
||||||
|
}
|
||||||
|
if !rc.IsMinInclusive || !rc.IsMaxInclusive {
|
||||||
|
t.Errorf("expected both bounds inclusive, got minInc=%v maxInc=%v", rc.IsMinInclusive, rc.IsMaxInclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Exact_MinOnly_PassesMinNilMax(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(500)}
|
||||||
|
|
||||||
|
_, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := mock.capturedRangeConstraint
|
||||||
|
if rc.Min == nil || *rc.Min != 500 {
|
||||||
|
t.Errorf("expected Min=500, got %v", rc.Min)
|
||||||
|
}
|
||||||
|
if rc.Max != nil {
|
||||||
|
t.Errorf("expected Max=nil (no upper bound), got %v", rc.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Exact_MaxOnly_PassesMaxNilMin(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000}
|
||||||
|
jobRange := config.RangeConfig{Max: ptr64(300)}
|
||||||
|
|
||||||
|
_, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := mock.capturedRangeConstraint
|
||||||
|
if rc.Min != nil {
|
||||||
|
t.Errorf("expected Min=nil (no lower bound), got %v", rc.Min)
|
||||||
|
}
|
||||||
|
if rc.Max == nil || *rc.Max != 300 {
|
||||||
|
t.Errorf("expected Max=300, got %v", rc.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Estimation_BothBounds_UsesUserRange(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
// DB min/max differ intentionally — user bounds should take precedence.
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000, minValue: 0, maxValue: 999}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(200), Max: ptr64(700), IsMinInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "ESTIMATION", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitions) == 0 {
|
||||||
|
t.Fatal("expected at least one partition")
|
||||||
|
}
|
||||||
|
|
||||||
|
if partitions[0].Range.Min != 200 {
|
||||||
|
t.Errorf("first partition should start at user min=200, got %d", partitions[0].Range.Min)
|
||||||
|
}
|
||||||
|
if partitions[len(partitions)-1].Range.Max != 700 {
|
||||||
|
t.Errorf("last partition should end at user max=700, got %d", partitions[len(partitions)-1].Range.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Estimation_MinOnly_QueriesDBForMax(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000, minValue: 0, maxValue: 999}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(500), IsMinInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "ESTIMATION", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitions) == 0 {
|
||||||
|
t.Fatal("expected at least one partition")
|
||||||
|
}
|
||||||
|
|
||||||
|
if partitions[0].Range.Min != 500 {
|
||||||
|
t.Errorf("first partition should start at user min=500, got %d", partitions[0].Range.Min)
|
||||||
|
}
|
||||||
|
if partitions[len(partitions)-1].Range.Max != 999 {
|
||||||
|
t.Errorf("last partition should end at DB max=999, got %d", partitions[len(partitions)-1].Range.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_Estimation_MaxOnly_QueriesDBForMin(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 1000, minValue: 100, maxValue: 999}
|
||||||
|
jobRange := config.RangeConfig{Max: ptr64(600), IsMaxInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "ESTIMATION", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitions) == 0 {
|
||||||
|
t.Fatal("expected at least one partition")
|
||||||
|
}
|
||||||
|
|
||||||
|
if partitions[0].Range.Min != 100 {
|
||||||
|
t.Errorf("first partition should start at DB min=100, got %d", partitions[0].Range.Min)
|
||||||
|
}
|
||||||
|
if partitions[len(partitions)-1].Range.Max != 600 {
|
||||||
|
t.Errorf("last partition should end at user max=600, got %d", partitions[len(partitions)-1].Range.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_SinglePartition_NoRange(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 50}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, config.RangeConfig{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitions) != 1 {
|
||||||
|
t.Fatalf("expected 1 partition, got %d", len(partitions))
|
||||||
|
}
|
||||||
|
if partitions[0].HasRange {
|
||||||
|
t.Error("single partition with no range should have HasRange=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_SinglePartition_BothBounds(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 50}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(100), Max: ptr64(200), IsMinInclusive: true, IsMaxInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitions) != 1 {
|
||||||
|
t.Fatalf("expected 1 partition, got %d", len(partitions))
|
||||||
|
}
|
||||||
|
p := partitions[0]
|
||||||
|
if !p.HasRange {
|
||||||
|
t.Error("expected HasRange=true")
|
||||||
|
}
|
||||||
|
if p.Range.Min != 100 || p.Range.Max != 200 {
|
||||||
|
t.Errorf("expected [100, 200], got [%d, %d]", p.Range.Min, p.Range.Max)
|
||||||
|
}
|
||||||
|
if !p.Range.IsMinInclusive || !p.Range.IsMaxInclusive {
|
||||||
|
t.Errorf("expected both inclusive, got minInc=%v maxInc=%v", p.Range.IsMinInclusive, p.Range.IsMaxInclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_SinglePartition_MinOnly(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 50}
|
||||||
|
jobRange := config.RangeConfig{Min: ptr64(100), IsMinInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := partitions[0]
|
||||||
|
if !p.HasRange {
|
||||||
|
t.Error("expected HasRange=true")
|
||||||
|
}
|
||||||
|
if p.Range.Min != 100 {
|
||||||
|
t.Errorf("expected Min=100, got %d", p.Range.Min)
|
||||||
|
}
|
||||||
|
if p.Range.Max != 0 {
|
||||||
|
t.Errorf("expected Max=0 (no upper bound), got %d", p.Range.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartitionRangeGenerator_SinglePartition_MaxOnly(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mock := &MockTableAnalyzer{totalRows: 50}
|
||||||
|
jobRange := config.RangeConfig{Max: ptr64(200), IsMaxInclusive: true}
|
||||||
|
|
||||||
|
partitions, err := PartitionRangeGenerator(ctx, mock, testTableInfo, "id", "EXACT", 100, jobRange)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := partitions[0]
|
||||||
|
if !p.HasRange {
|
||||||
|
t.Error("expected HasRange=true")
|
||||||
|
}
|
||||||
|
if p.Range.Min != 0 {
|
||||||
|
t.Errorf("expected Min=0 (no lower bound), got %d", p.Range.Min)
|
||||||
|
}
|
||||||
|
if p.Range.Max != 200 {
|
||||||
|
t.Errorf("expected Max=200, got %d", p.Range.Max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user