Repository: featureform/featureform
Pull request #1632: Add rest of primary table adjustments
Author: login: aolfat
Created: 2025-04-15T02:50:16Z | Merged: 2025-04-15T17:23:26Z

# Description

<!--- Please include a summary of the changes and the related issue. -->
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## Type of change

### Does this correspond to an open issue?
<!--- Provide a link to the issue if not already associated -->

### Select type(s) of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Chore (non-breaking change that modifies some code)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

# Checklist:

- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have fixed any merge conflicts


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Standardized method signatures across multiple data providers to return a unified dataset interface, enhancing consistency and compatibility across storage backends.
- **Tests**
  - Updated test functions and mocks to work with the new dataset interface, including improved dataset content verification and unified handling of table identifiers and write/read operations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/provider/bigquery.go b/provider/bigquery.go
index ab66452d61..3a67a51ab1 100644
--- a/provider/bigquery.go
+++ b/provider/bigquery.go
@@ -959,7 +959,7 @@ func (store *bqOfflineStore) RegisterResourceFromSourceTable(id ResourceID, sche
 	return store.newBqOfflineTable(tableName)
 }
 
-func (store *bqOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (store *bqOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	logger := store.logger.With("resourceId", id)
 
 	logger.Debug("Registering primary from source table")
@@ -976,7 +976,12 @@ func (store *bqOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, table
 		return nil, err
 	}
 
-	return store.newBigQueryPrimaryTable(sqlLocation.Location())
+	table, err := store.newBigQueryPrimaryTable(sqlLocation.Location())
+	if err != nil {
+		logger.Errorw("Error creating primary table", "error", err)
+		return nil, err
+	}
+	return &PrimaryTableToDatasetAdapter{pt: table}, nil
 }
 
 func (store *bqOfflineStore) SupportsTransformationOption(opt TransformationOptionType) (bool, error) {
@@ -1075,7 +1080,7 @@ func (store *bqOfflineStore) UpdateTransformation(config TransformationConfig, o
 	return nil
 }
 
-func (store *bqOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (store *bqOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	logger := store.logger.With("resourceId", id)
 
 	logger.Debug("Creating primary table")
@@ -1107,7 +1112,7 @@ func (store *bqOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchem
 	}
 
 	logger.Info("Successfully created primary table")
-	return table, nil
+	return &PrimaryTableToDatasetAdapter{pt: table}, nil
 }
 
 func (store *bqOfflineStore) GetPrimaryTable(id ResourceID, source metadata.SourceVariant) (dataset.Dataset, error) {
diff --git a/provider/clickhouse.go b/provider/clickhouse.go
index 824472381c..dfcc478245 100644
--- a/provider/clickhouse.go
+++ b/provider/clickhouse.go
@@ -743,7 +743,7 @@ func (store *clickHouseOfflineStore) CheckHealth() (bool, error) {
 	return true, nil
 }
 
-func (store *clickHouseOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (store *clickHouseOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	logger := store.logger.With("resourceId", id)
 
 	logger.Debug("Registering primary from source table")
@@ -782,11 +782,13 @@ func (store *clickHouseOfflineStore) RegisterPrimaryFromSourceTable(id ResourceI
 		return nil, err
 	}
 
-	return &clickhousePrimaryTable{
-		db:     store.db,
-		name:   tableName,
-		schema: TableSchema{Columns: columnNames},
-		query:  store.query,
+	return &PrimaryTableToDatasetAdapter{
+		&clickhousePrimaryTable{
+			db:     store.db,
+			name:   tableName,
+			schema: TableSchema{Columns: columnNames},
+			query:  store.query,
+		},
 	}, nil
 }
 
@@ -855,7 +857,7 @@ func (store *clickHouseOfflineStore) UpdateTransformation(config TransformationC
 	return nil
 }
 
-func (store *clickHouseOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (store *clickHouseOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	if err := id.check(Primary); err != nil {
 		return nil, err
 	}
@@ -875,7 +877,7 @@ func (store *clickHouseOfflineStore) CreatePrimaryTable(id ResourceID, schema Ta
 	if err != nil {
 		return nil, err
 	}
-	return table, nil
+	return &PrimaryTableToDatasetAdapter{pt: table}, nil
 }
 
 func (store *clickHouseOfflineStore) GetPrimaryTable(id ResourceID, source metadata.SourceVariant) (dataset.Dataset, error) {
diff --git a/provider/correctness_test.go b/provider/correctness_test.go
index d636f66815..0648679d10 100644
--- a/provider/correctness_test.go
+++ b/provider/correctness_test.go
@@ -20,6 +20,7 @@ import (
 	"time"
 
 	"github.com/featureform/fferr"
+	"github.com/featureform/logging"
 	"github.com/featureform/provider/dataset"
 	ps "github.com/featureform/provider/provider_schema"
 
@@ -1592,6 +1593,49 @@ func verifyPrimaryTable(t *testing.T, primary PrimaryTable, records []GenericRec
 	}
 }
 
+func verifyDataset(t *testing.T, primary dataset.Dataset, records []GenericRecord) {
+	t.Helper()
+
+	// cast to sized
+	sDs, ok := primary.(dataset.SizedDataset)
+	if !ok {
+		t.Fatalf("expected dataset to be sized")
+	}
+
+	numRows, err := sDs.Len()
+	if err != nil {
+		t.Fatalf("could not get number of rows: %v", err)
+	}
+
+	if numRows == 0 {
+		t.Fatalf("expected more than 0 rows")
+	}
+
+	ctx := logging.NewTestContext(t)
+	iterator, err := sDs.Iterator(ctx, 100)
+	if err != nil {
+		t.Fatalf("Could not get generic iterator: %v", err)
+	}
+
+	i := 0
+	for iterator.Next() {
+		for j, v := range iterator.Values() {
+			// NOTE: we're handling float64 differently here given the values returned by Snowflake have less precision
+			// and therefore are not equal unless we round them; if tests require handling of other types, we can add
+			// additional cases here, otherwise the default case will cover all other types
+			switch v.Value.(type) {
+			case float64:
+				assert.True(t, floatsAreClose(v.Value.(float64), records[i][j].(float64), floatTolerance), "expected same values")
+			case time.Time:
+				assert.Equal(t, records[i][j].(time.Time).Truncate(time.Microsecond), v.Value.(time.Time).Truncate(time.Microsecond), "expected same values")
+			default:
+				assert.Equal(t, v.Value, records[i][j], "expected same values")
+			}
+		}
+		i++
+	}
+}
+
 func RegisterTableInDifferentDatabaseTest(t *testing.T, tester OfflineSqlTest) {
 	dbName := fmt.Sprintf("DB_%s", strings.ToUpper(uuid.NewString()[:5]))
 
@@ -1634,7 +1678,7 @@ func RegisterTableInDifferentDatabaseTest(t *testing.T, tester OfflineSqlTest) {
 	}
 
 	// Verify the table contents
-	verifyPrimaryTable(t, primary, records)
+	verifyDataset(t, primary, records)
 }
 
 func RegisterTableInSameDatabaseDifferentSchemaTest(t *testing.T, storeTester OfflineSqlTest) {
@@ -1660,7 +1704,7 @@ func RegisterTableInSameDatabaseDifferentSchemaTest(t *testing.T, storeTester Of
 	}
 
 	// Verify the table contents
-	verifyPrimaryTable(t, primary, records)
+	verifyDataset(t, primary, records)
 }
 
 func RegisterTwoTablesInSameSchemaTest(t *testing.T, tester OfflineSqlTest) {
@@ -1706,8 +1750,8 @@ func RegisterTwoTablesInSameSchemaTest(t *testing.T, tester OfflineSqlTest) {
 	}
 
 	// Verify the table contents
-	verifyPrimaryTable(t, primary1, records)
-	verifyPrimaryTable(t, primary2, records2)
+	verifyDataset(t, primary1, records)
+	verifyDataset(t, primary2, records2)
 }
 
 func CrossDatabaseJoinTest(t *testing.T, test OfflineSqlTest) {
@@ -1769,8 +1813,8 @@ func CrossDatabaseJoinTest(t *testing.T, test OfflineSqlTest) {
 	}
 
 	// Verify the table contents
-	verifyPrimaryTable(t, primary1, records)
-	verifyPrimaryTable(t, primary2, records2)
+	verifyDataset(t, primary1, records)
+	verifyDataset(t, primary2, records2)
 
 	targetTableId := ResourceID{Name: "DUMMY_TABLE_TF", Variant: "test", Type: Transformation}
 	// union the tables using a transformation
diff --git a/provider/k8s.go b/provider/k8s.go
index a3e09f3b7d..98bee04574 100644
--- a/provider/k8s.go
+++ b/provider/k8s.go
@@ -537,12 +537,17 @@ func blobRegisterResourceFromSourceTable(id ResourceID, sourceSchema ResourceSch
 	return &BlobOfflineTable{schema: sourceSchema, store: store}, nil
 }
 
-func (k8s *K8sOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (k8s *K8sOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	fileStoreLocation, isFileStoreLocation := tableLocation.(*pl.FileStoreLocation)
 	if !isFileStoreLocation {
 		return nil, fferr.NewInternalError(fmt.Errorf("location is not a FileStoreLocation"))
 	}
-	return blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)
+	primary, err := blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)
+	if err != nil {
+		k8s.logger.Errorw("Error registering primary table", "error", err)
+		return nil, err
+	}
+	return &PrimaryTableToDatasetAdapter{pt: primary}, nil
 }
 
 func blobRegisterPrimary(id ResourceID, location pl.FileStoreLocation, logger *zap.SugaredLogger, store FileStore) (PrimaryTable, error) {
@@ -873,7 +878,7 @@ func (k8s *K8sOfflineStore) UpdateTransformation(config TransformationConfig, op
 	}
 	return k8s.transformation(config, true)
 }
-func (k8s *K8sOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (k8s *K8sOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	return nil, fferr.NewInternalError(fmt.Errorf("not implemented"))
 }
 
diff --git a/provider/offline.go b/provider/offline.go
index 07efe09e1b..bb90c8113a 100644
--- a/provider/offline.go
+++ b/provider/offline.go
@@ -530,8 +530,8 @@ type OfflineStoreCore interface {
 
 type OfflineStoreDataset interface {
 	// CreatePrimaryTable is not used outside of the context of tests
-	CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error)
-	RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error)
+	CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error)
+	RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error)
 	GetPrimaryTable(id ResourceID, source metadata.SourceVariant) (dataset.Dataset, error)
 	SupportsTransformationOption(opt TransformationOptionType) (bool, error)
 	CreateTransformation(config TransformationConfig, opts ...TransformationOption) error
@@ -1160,17 +1160,17 @@ func (store *memoryOfflineStore) RegisterResourceFromSourceTable(id ResourceID,
 func (store *memoryOfflineStore) RegisterPrimaryFromSourceTable(
 	id ResourceID,
 	tableLocation pl.Location,
-) (PrimaryTable, error) {
+) (dataset.Dataset, error) {
 	if id.Name == "make" && id.Variant == "panic" {
 		panic("This is a panic")
 	}
 	store.tables.Store(id, &memoryPrimaryTable{})
-	return &memoryPrimaryTable{}, nil
+	return &PrimaryTableToDatasetAdapter{pt: &memoryPrimaryTable{}}, nil
 }
 
-func (store *memoryOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (store *memoryOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	store.tables.Store(id, &memoryPrimaryTable{})
-	return &memoryPrimaryTable{}, nil
+	return &PrimaryTableToDatasetAdapter{pt: &memoryPrimaryTable{}}, nil
 }
 
 type memoryPrimaryTable struct {
diff --git a/provider/offline_test.go b/provider/offline_test.go
index da96c7bb43..45bedbf1ba 100644
--- a/provider/offline_test.go
+++ b/provider/offline_test.go
@@ -204,13 +204,14 @@ func testResourceLocation(t *testing.T, store OfflineStore) {
 		t.Fatalf("Failed to get table: %v", err)
 	}
 
+	name := table.Location().Location()
 	svProto := pb.SourceVariant{
-		Table: table.GetName(),
+		Table: name,
 		Definition: &pb.SourceVariant_PrimaryData{
 			PrimaryData: &pb.PrimaryData{
 				Location: &pb.PrimaryData_Table{
 					Table: &pb.SQLTable{
-						Name: table.GetName(),
+						Name: name,
 					},
 				},
 			},
@@ -1927,13 +1928,14 @@ func testPrimaryTableWrite(t *testing.T, store OfflineStore) {
 			t.Fatalf("Could not create table: %v", err)
 		}
 
+		name := table.Location().Location()
 		svProto := pb.SourceVariant{
-			Table: table.GetName(),
+			Table: name,
 			Definition: &pb.SourceVariant_PrimaryData{
 				PrimaryData: &pb.PrimaryData{
 					Location: &pb.PrimaryData_Table{
 						Table: &pb.SQLTable{
-							Name: table.GetName(),
+							Name: name,
 						},
 					},
 				},
@@ -1945,7 +1947,15 @@ func testPrimaryTableWrite(t *testing.T, store OfflineStore) {
 		if err != nil {
 			t.Fatalf("Could not get Primary table: %v", err)
 		}
-		if err := table.WriteBatch(test.Records); err != nil {
+
+		// cast to writableDataset
+		writableDataset, ok := table.(dataset.WriteableDataset)
+		if !ok {
+			t.Fatalf("Could not cast to WritableDataset")
+		}
+
+		ctx := logging.NewTestContext(t)
+		if err := writableDataset.WriteBatch(ctx, GenericRecordsToRows(test.Records)); err != nil {
 			t.Fatalf("Could not write: %v", err)
 		}
 	}
@@ -2066,16 +2076,26 @@ func testTransform(t *testing.T, store OfflineStore) {
 			t.Fatalf("Could not initialize table: %v", err)
 		}
 
-		if err := table.WriteBatch(test.Records); err != nil {
+		writableTable, ok := table.(dataset.WriteableDataset)
+		if !ok {
+			t.Fatalf("Table does not implement WritableDataset interface")
+		}
+
+		if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(test.Records)); err != nil {
 			t.Fatalf("Could not write: %v", err)
 		}
 
-		modifyTransformationConfig(t, t.Name(), table.GetName(), store.Type(), &test.Config)
+		name := table.Location().Location()
+		modifyTransformationConfig(t, t.Name(), name, store.Type(), &test.Config)
 		if err := store.CreateTransformation(test.Config); err != nil {
 			t.Fatalf("Could not create transformation: %v", err)
 		}
 
-		rows, err := table.NumRows()
+		sized, ok := table.(dataset.SizedDataset)
+		if !ok {
+			t.Fatalf("Table does not implement SizedDataset interface")
+		}
+		rows, err := sized.Len()
 		if err != nil {
 			t.Fatalf("could not get NumRows of table: %v", err)
 		}
@@ -2224,18 +2244,31 @@ func testTransformUpdateWithFeatures(t *testing.T, store OfflineStore) {
 		if err != nil {
 			t.Fatalf("Could not initialize table: %v", err)
 		}
-		if err := primaryTable.WriteBatch(test.Records); err != nil {
+
+		writableTable, ok := primaryTable.(dataset.WriteableDataset)
+		if !ok {
+			t.Fatalf("Table does not implement WritableDataset interface")
+		}
+
+		if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(test.Records)); err != nil {
 			t.Fatalf("Could not write value: %v", err)
 		}
 
-		modifyTransformationConfig(t, t.Name(), primaryTable.GetName(), store.Type(), &test.Config)
+		name := primaryTable.Location().Location()
+		modifyTransformationConfig(t, t.Name(), name, store.Type(), &test.Config)
 		if err := store.CreateTransformation(test.Config); err != nil {
 			t.Fatalf("Could not create transformation: %v", err)
 		}
-		rows, err := primaryTable.NumRows()
+
+		sized, ok := primaryTable.(dataset.SizedDataset)
+		if !ok {
+			t.Fatalf("Table does not implement SizedDataset interface")
+		}
+		rows, err := sized.Len()
 		if err != nil {
 			t.Fatalf("could not get NumRows of table: %v", err)
 		}
+
 		if int(rows) != len(test.Records) {
 			t.Fatalf("NumRows do not match. Expected: %d, Got: %d", len(test.Records), rows)
 		}
@@ -2276,13 +2309,14 @@ func testTransformUpdateWithFeatures(t *testing.T, store OfflineStore) {
 			t.Fatalf("Could not create materialization: %v", err)
 		}
 
+		name = primaryTable.Location().Location()
 		svProto := pb.SourceVariant{
-			Table: primaryTable.GetName(),
+			Table: name,
 			Definition: &pb.SourceVariant_PrimaryData{
 				PrimaryData: &pb.PrimaryData{
 					Location: &pb.PrimaryData_Table{
 						Table: &pb.SQLTable{
-							Name: primaryTable.GetName(),
+							Name: name,
 						},
 					},
 				},
@@ -2478,15 +2512,27 @@ func testTransformUpdate(t *testing.T, store OfflineStore) {
 		if err != nil {
 			t.Fatalf("Could not initialize table: %v", err)
 		}
-		if err := primaryTable.WriteBatch(test.Records); err != nil {
+
+		writableTable, ok := primaryTable.(dataset.WriteableDataset)
+		if !ok {
+			t.Fatalf("Table does not implement WritableDataset interface")
+		}
+
+		if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(test.Records)); err != nil {
 			t.Fatalf("Could not write records: %v", err)
 		}
 
-		modifyTransformationConfig(t, t.Name(), primaryTable.GetName(), store.Type(), &test.Config)
+		name := primaryTable.Location().Location()
+		modifyTransformationConfig(t, t.Name(), name, store.Type(), &test.Config)
 		if err := store.CreateTransformation(test.Config); err != nil {
 			t.Fatalf("Could not create transformation: %v", err)
 		}
-		rows, err := primaryTable.NumRows()
+
+		sized, ok := primaryTable.(dataset.SizedDataset)
+		if !ok {
+			t.Fatalf("Table does not implement SizedDataset interface")
+		}
+		rows, err := sized.Len()
 		if err != nil {
 			t.Fatalf("could not get NumRows of table: %v", err)
 		}
@@ -2524,13 +2570,14 @@ func testTransformUpdate(t *testing.T, store OfflineStore) {
 			t.Fatalf("Could not close iterator: %v", err)
 		}
 
+		name = primaryTable.Location().Location()
 		svProto := pb.SourceVariant{
-			Table: primaryTable.GetName(),
+			Table: name,
 			Definition: &pb.SourceVariant_PrimaryData{
 				PrimaryData: &pb.PrimaryData{
 					Location: &pb.PrimaryData_Table{
 						Table: &pb.SQLTable{
-							Name: primaryTable.GetName(),
+							Name: name,
 						},
 					},
 				},
@@ -2659,16 +2706,26 @@ func testTransformCreateFeature(t *testing.T, store OfflineStore) {
 		if err != nil {
 			t.Fatalf("Could not initialize table: %v", err)
 		}
-		if err := table.WriteBatch(test.Records); err != nil {
+
+		writableTable, ok := table.(dataset.WriteableDataset)
+		if !ok {
+			t.Fatalf("Table does not implement WritableDataset interface")
+		}
+
+		if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(test.Records)); err != nil {
 			t.Fatalf("Could not write records: %v", err)
 		}
 
-		tableName := getTableName(string(store.Type()), table.GetName())
-		test.Config.Query = strings.Replace(test.Config.Query, "tb", tableName, 1)
+		name := table.Location().Location()
+		test.Config.Query = strings.Replace(test.Config.Query, "tb", name, 1)
 		if err := store.CreateTransformation(test.Config); err != nil {
 			t.Fatalf("Could not create transformation: %v", err)
 		}
-		rows, err := table.NumRows()
+		sized, ok := table.(dataset.SizedDataset)
+		if !ok {
+			t.Fatalf("Table does not implement SizedDataset interface")
+		}
+		rows, err := sized.Len()
 		if err != nil {
 			t.Fatalf("could not get NumRows of table: %v", err)
 		}
@@ -2811,7 +2868,11 @@ func testChainTransform(t *testing.T, store OfflineStore) {
 	if err != nil {
 		t.Fatalf("Could not initialize table: %v", err)
 	}
-	if err := table.WriteBatch(tests["First"].Records); err != nil {
+	writableTable, ok := table.(dataset.WriteableDataset)
+	if !ok {
+		t.Fatalf("Table does not implement WritableDataset interface")
+	}
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(tests["First"].Records)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 
@@ -2830,12 +2891,17 @@ func testChainTransform(t *testing.T, store OfflineStore) {
 			},
 		},
 	}
-	modifyTransformationConfig(t, t.Name(), table.GetName(), store.Type(), &config)
+	name := table.Location().Location()
+	modifyTransformationConfig(t, t.Name(), name, store.Type(), &config)
 
 	if err := store.CreateTransformation(config); err != nil {
 		t.Fatalf("Could not create transformation: %v", err)
 	}
-	rows, err := table.NumRows()
+	sized, ok := table.(dataset.SizedDataset)
+	if !ok {
+		t.Fatalf("Table does not implement SizedDataset interface")
+	}
+	rows, err := sized.Len()
 	if err != nil {
 		t.Fatalf("could not get NumRows of table: %v", err)
 	}
@@ -2891,7 +2957,9 @@ func testChainTransform(t *testing.T, store OfflineStore) {
 			},
 		},
 	}
-	modifyTransformationConfig(t, t.Name(), table.GetName(), store.Type(), &config)
+
+	name = table.Location().Location()
+	modifyTransformationConfig(t, t.Name(), name, store.Type(), &config)
 	if err := store.CreateTransformation(config); err != nil {
 		t.Fatalf("Could not create transformation: %v", err)
 	}
@@ -2976,29 +3044,38 @@ func testTransformToMaterialize(t *testing.T, store OfflineStore) {
 	if err != nil {
 		t.Fatalf("Could not initialize table: %v", err)
 	}
-	if err := table.WriteBatch(tests["First"].Records); err != nil {
+	writableTable, ok := table.(dataset.WriteableDataset)
+	if !ok {
+		t.Fatalf("Table does not implement WritableDataset interface")
+	}
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(tests["First"].Records)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 
-	tableName := getTableName(string(store.Type()), table.GetName())
+	name := table.Location().Location()
 	config := TransformationConfig{
 		Type: SQLTransformation,
 		TargetTableID: ResourceID{
 			Name: firstTransformName,
 			Type: Transformation,
 		},
-		Query: fmt.Sprintf("SELECT entity, int, flt, str FROM %s", tableName),
+		Query: fmt.Sprintf("SELECT entity, int, flt, str FROM %s", name),
 		SourceMapping: []SourceMapping{
 			SourceMapping{
-				Template: tableName,
-				Source:   tableName,
+				Template: name,
+				Source:   name,
 			},
 		},
 	}
 	if err := store.CreateTransformation(config); err != nil {
 		t.Fatalf("Could not create transformation: %v", err)
 	}
-	rows, err := table.NumRows()
+
+	sized, ok := table.(dataset.SizedDataset)
+	if !ok {
+		t.Fatalf("Table does not implement SizedDataset interface")
+	}
+	rows, err := sized.Len()
 	if err != nil {
 		t.Fatalf("could not get NumRows of table: %v", err)
 	}
@@ -3050,20 +3127,25 @@ func testCreateResourceFromSource(t *testing.T, store OfflineStore) {
 		{"d", 4, "four", time.UnixMilli(3)},
 		{"e", 5, "five", time.UnixMilli(4)},
 	}
-	if err := table.WriteBatch(records); err != nil {
+	writableTable, ok := table.(dataset.WriteableDataset)
+	if !ok {
+		t.Fatalf("Table does not implement WritableDataset interface")
+	}
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(records)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 
 	var location pl.Location
+	name := table.Location().Location()
 	if store.Type() == pt.SparkOffline {
 		sparkStore := store.(*SparkOfflineStore)
-		fp, err := sparkStore.Store.CreateFilePath(table.GetName(), false)
+		fp, err := sparkStore.Store.CreateFilePath(name, false)
 		if err != nil {
 			t.Fatalf("Could not create file path: %v", err)
 		}
 		location = pl.NewFileLocation(fp)
 	} else {
-		location = pl.NewSQLLocation(table.GetName())
+		location = pl.NewSQLLocation(name)
 	}
 
 	featureID := ResourceID{
@@ -3097,7 +3179,8 @@ func testCreateResourceFromSource(t *testing.T, store OfflineStore) {
 		{"i", 9, "nine", time.UnixMilli(3)},
 		{"j", 10, "ten", time.UnixMilli(4)},
 	}
-	if err := table.WriteBatch(updatedRecords); err != nil {
+
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(updatedRecords)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 	err = store.DeleteMaterialization(mat.ID())
@@ -3108,7 +3191,11 @@ func testCreateResourceFromSource(t *testing.T, store OfflineStore) {
 	if err != nil {
 		t.Fatalf("Could not recreate materialization: %v", err)
 	}
-	expected, err := table.NumRows()
+	sized, ok := table.(dataset.SizedDataset)
+	if !ok {
+		t.Fatalf("Table does not implement SizedDataset interface")
+	}
+	expected, err := sized.Len()
 	if err != nil {
 		t.Fatalf("Could not get resource table rows: %v", err)
 	}
@@ -3152,20 +3239,26 @@ func testCreateResourceFromSourceNoTS(t *testing.T, store OfflineStore) {
 		{"d", 4, "four", true},
 		{"e", 5, "five", false},
 	}
-	if err := table.WriteBatch(records); err != nil {
+
+	writableTable, ok := table.(dataset.WriteableDataset)
+	if !ok {
+		t.Fatalf("Table does not implement WritableDataset interface")
+	}
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(records)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 
 	var location pl.Location
+	name := table.Location().Location()
 	if store.Type() == pt.SparkOffline {
 		sparkStore := store.(*SparkOfflineStore)
-		fp, err := sparkStore.Store.CreateFilePath(table.GetName(), false)
+		fp, err := sparkStore.Store.CreateFilePath(name, false)
 		if err != nil {
 			t.Fatalf("Could not create file path: %v", err)
 		}
 		location = pl.NewFileLocation(fp)
 	} else {
-		location = pl.NewSQLLocation(table.GetName())
+		location = pl.NewSQLLocation(name)
 	}
 
 	featureID := ResourceID{
@@ -3296,10 +3389,15 @@ func testCreatePrimaryFromNonExistentSource(t *testing.T, store OfflineStore) {
 	if err != nil {
 		t.Fatalf("Could not create primary table: %v", err)
 	}
-	tableName := fmt.Sprintf("%s_%s", "nonexistent", table.GetName())
+	tableName := fmt.Sprintf("%s_%s", "nonexistent", table.Location().Location())
 	var primaryErr error
 	if store.Type() == pt.SparkOffline {
-		sourceTablePath, err := table.(*FileStorePrimaryTable).GetSource()
+		// cast to correct location
+		location, ok := table.Location().(*pl.FileStoreLocation)
+		if !ok {
+			t.Fatalf("Table location is not a FileStoreLocation")
+		}
+		sourceTablePath := location.Filepath()
 		if err != nil {
 			t.Fatalf("Could not get source table path: %v", err)
 		}
@@ -3346,7 +3444,11 @@ func testCreatePrimaryFromSource(t *testing.T, store OfflineStore) {
 		{"d", 4, "four", time.UnixMilli(3)},
 		{"e", 5, "five", time.UnixMilli(4)},
 	}
-	if err := table.WriteBatch(records); err != nil {
+	writableTable, ok := table.(dataset.WriteableDataset)
+	if !ok {
+		t.Fatalf("Table does not implement WritableDataset interface")
+	}
+	if err := writableTable.WriteBatch(ctx, GenericRecordsToRows(records)); err != nil {
 		t.Fatalf("Could not write batch: %v", err)
 	}
 	primaryCopyID := ResourceID{
@@ -3358,17 +3460,19 @@ func testCreatePrimaryFromSource(t *testing.T, store OfflineStore) {
 	t.Log("Primary Name: ", primaryCopyID.Name)
 	// Need to sanitize name here b/c the the xxx-xxx format of the uuid. Cannot do it within
 	// register function because precreated tables do not necessarily use double quotes
-	tableName := table.GetName()
+	tableName := table.Location().Location()
 	t.Log("Table Name: ", tableName)
 	// Currently, the assumption is that a primary table will always have an absolute path
 	// to the source data file in its schema; to keep with this assumption until we determine
 	// a better approach (e.g. handling directories of primary sources), we will use the
 	// GetSource method on the FileStorePrimaryTable to get the absolute path to the source.
 	if store.Type() == pt.SparkOffline {
-		sourceTablePath, err := table.(*FileStorePrimaryTable).GetSource()
-		if err != nil {
-			t.Fatalf("Could not get source table path: %v", err)
+
+		location, ok := table.Location().(*pl.FileStoreLocation)
+		if !ok {
+			t.Fatalf("Table location is not a FileStoreLocation")
 		}
+		sourceTablePath := location.Filepath()
 
 		_, err = store.RegisterPrimaryFromSourceTable(primaryCopyID, pl.NewFileLocation(sourceTablePath))
 		if err != nil {
diff --git a/provider/spark.go b/provider/spark.go
index 1932d7900b..5880af80d1 100644
--- a/provider/spark.go
+++ b/provider/spark.go
@@ -656,12 +656,13 @@ func NewSparkExecutor(
 	}
 }
 
-func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	switch lt := tableLocation.(type) {
 	case *pl.SQLLocation:
 		return nil, fferr.NewInternalErrorf("SQLLocation not supported for primary table registration")
 	case *pl.FileStoreLocation:
-		return blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
+		primary, err := blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
+		return &PrimaryTableToDatasetAdapter{primary}, err
 	case *pl.CatalogLocation:
 		// TODO consider registering things in the catalog anyway?
 		return nil, nil
@@ -1171,7 +1172,7 @@ func (spark *SparkOfflineStore) UpdateTransformation(config TransformationConfig
 // **NOTE:** Unlike the pathway for registering a primary table from a data source that previously existed in the filestore, this
 // method controls the location of the data source that will be written to once the primary table (i.e. a file that simply holds the
 // fully qualified URL pointing to the source file), so it's important to consider what pattern we adopt here.
-func (spark *SparkOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (spark *SparkOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	if err := id.check(Primary); err != nil {
 		return nil, err
 	}
@@ -1198,7 +1199,7 @@ func (spark *SparkOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSc
 	if err != nil {
 		return nil, err
 	}
-	return &FileStorePrimaryTable{spark.Store, primaryTableFilepath, schema, false, id}, nil
+	return &PrimaryTableToDatasetAdapter{pt: &FileStorePrimaryTable{spark.Store, primaryTableFilepath, schema, false, id}}, nil
 }
 
 func (spark *SparkOfflineStore) GetPrimaryTable(id ResourceID, source metadata.SourceVariant) (dataset.Dataset, error) {
diff --git a/provider/spark_test.go b/provider/spark_test.go
index 05a35a4682..6017aa0075 100644
--- a/provider/spark_test.go
+++ b/provider/spark_test.go
@@ -431,7 +431,7 @@ func testRegisterPrimary(store *SparkOfflineStore) error {
 	if err != nil {
 		return err
 	}
-	if !reflect.DeepEqual(PrimaryTableToDatasetAdapter{table}, fetchedTableDs) {
+	if !reflect.DeepEqual(table, fetchedTableDs) {
 		return fmt.Errorf("Tables not equal")
 	}
 
diff --git a/provider/sql.go b/provider/sql.go
index 2dc2073cdf..f95b61f2f4 100644
--- a/provider/sql.go
+++ b/provider/sql.go
@@ -334,7 +334,7 @@ func (store *sqlOfflineStore) RegisterResourceFromSourceTable(id ResourceID, sch
 	}, nil
 }
 
-func (store *sqlOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (store *sqlOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	if err := id.check(Primary); err != nil {
 		return nil, err
 	}
@@ -364,16 +364,18 @@ func (store *sqlOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tabl
 		return nil, err
 	}
 
-	return &SqlPrimaryTable{
-		db:          dbConn,
-		name:        sqlLocation.Location(),
-		sqlLocation: sqlLocation,
-		schema:      TableSchema{Columns: columnNames},
-		query:       store.query,
+	return &PrimaryTableToDatasetAdapter{
+		&SqlPrimaryTable{
+			db:          dbConn,
+			name:        sqlLocation.Location(),
+			sqlLocation: sqlLocation,
+			schema:      TableSchema{Columns: columnNames},
+			query:       store.query,
+		},
 	}, nil
 }
 
-func (store *sqlOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (store *sqlOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	if err := id.check(Primary); err != nil {
 		return nil, err
 	}
@@ -397,7 +399,7 @@ func (store *sqlOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSche
 	if err != nil {
 		return nil, err
 	}
-	return table, nil
+	return &PrimaryTableToDatasetAdapter{table}, nil
 }
 
 func (store *sqlOfflineStore) newsqlPrimaryTable(db *sql.DB, name string, schema TableSchema) (*SqlPrimaryTable, error) {
diff --git a/provider/unit_test_store.go b/provider/unit_test_store.go
index 93be72b6cf..fa741df5c9 100644
--- a/provider/unit_test_store.go
+++ b/provider/unit_test_store.go
@@ -123,7 +123,7 @@ func (m MockUnitTestTable) Set(entity string, value interface{}) error {
 OFFLINE UNIT STORE
 */
 
-func (M MockUnitTestOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (PrimaryTable, error) {
+func (M MockUnitTestOfflineStore) CreatePrimaryTable(id ResourceID, schema TableSchema) (dataset.Dataset, error) {
 	return nil, nil
 }
 
@@ -203,7 +203,7 @@ func (M MockUnitTestOfflineStore) RegisterResourceFromSourceTable(id ResourceID,
 	return nil, nil
 }
 
-func (M MockUnitTestOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, stableLocation pl.Location) (PrimaryTable, error) {
+func (M MockUnitTestOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, stableLocation pl.Location) (dataset.Dataset, error) {
 	return nil, nil
 }
 
diff --git a/runner/copy_test.go b/runner/copy_test.go
index 281c72bd47..193edd1e4e 100644
--- a/runner/copy_test.go
+++ b/runner/copy_test.go
@@ -384,7 +384,7 @@ func (store *BrokenNumChunksOfflineStore) AsOfflineStore() (provider.OfflineStor
 	return store, nil
 }
 
-func (store *BrokenNumChunksOfflineStore) CreatePrimaryTable(id provider.ResourceID, schema provider.TableSchema) (provider.PrimaryTable, error) {
+func (store *BrokenNumChunksOfflineStore) CreatePrimaryTable(id provider.ResourceID, schema provider.TableSchema) (dataset.Dataset, error) {
 	return nil, nil
 }
 
@@ -395,7 +395,7 @@ func (store *BrokenNumChunksOfflineStore) GetPrimaryTable(id provider.ResourceID
 func (store *BrokenNumChunksOfflineStore) RegisterResourceFromSourceTable(id provider.ResourceID, schema provider.ResourceSchema, opts ...provider.ResourceOption) (provider.OfflineTable, error) {
 	return nil, nil
 }
-func (store *BrokenNumChunksOfflineStore) RegisterPrimaryFromSourceTable(id provider.ResourceID, tableLocation pl.Location) (provider.PrimaryTable, error) {
+func (store *BrokenNumChunksOfflineStore) RegisterPrimaryFromSourceTable(id provider.ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	return nil, nil
 }
 
@@ -708,7 +708,7 @@ func (m MockOfflineStore) AsOfflineStore() (provider.OfflineStore, error) {
 	return m, nil
 }
 
-func (m MockOfflineStore) CreatePrimaryTable(id provider.ResourceID, schema provider.TableSchema) (provider.PrimaryTable, error) {
+func (m MockOfflineStore) CreatePrimaryTable(id provider.ResourceID, schema provider.TableSchema) (dataset.Dataset, error) {
 	return nil, nil
 }
 
@@ -719,7 +719,7 @@ func (m MockOfflineStore) GetPrimaryTable(id provider.ResourceID, source metadat
 func (m MockOfflineStore) RegisterResourceFromSourceTable(id provider.ResourceID, schema provider.ResourceSchema, opts ...provider.ResourceOption) (provider.OfflineTable, error) {
 	return nil, nil
 }
-func (m MockOfflineStore) RegisterPrimaryFromSourceTable(id provider.ResourceID, tableLocation pl.Location) (provider.PrimaryTable, error) {
+func (m MockOfflineStore) RegisterPrimaryFromSourceTable(id provider.ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	return nil, nil
 }
 func (m MockOfflineStore) SupportsTransformationOption(opt provider.TransformationOptionType) (bool, error) {


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between eebb0e5384ee7323d37d306e77873a7185f5f549 and 17de0e14862615e0c8ee8f64f9f69347ac880e11.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `provider/bigquery.go` (4 hunks)
* `provider/clickhouse.go` (4 hunks)
* `provider/k8s.go` (2 hunks)
* `provider/offline.go` (2 hunks)
* `provider/spark.go` (3 hunks)
* `provider/sql.go` (3 hunks)
* `provider/unit_test_store.go` (2 hunks)
* `runner/copy_test.go` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>provider/clickhouse.go (4)</summary><blockquote>

<details>
<summary>provider/offline.go (3)</summary>

* `ResourceID` (87-90)
* `Dataset` (829-837)
* `PrimaryTableToDatasetAdapter` (770-772)

</details>
<details>
<summary>metadata/metadata.go (1)</summary>

* `ResourceID` (226-230)

</details>
<details>
<summary>provider/clickhouse/location.go (1)</summary>

* `Location` (18-20)

</details>
<details>
<summary>provider/dataset/dataset.go (1)</summary>

* `Dataset` (22-26)

</details>

</blockquote></details>
<details>
<summary>runner/copy_test.go (4)</summary><blockquote>

<details>
<summary>provider/offline.go (3)</summary>

* `ResourceID` (87-90)
* `TableSchema` (968-972)
* `Dataset` (829-837)

</details>
<details>
<summary>provider/dataset/dataset.go (1)</summary>

* `Dataset` (22-26)

</details>
<details>
<summary>client/src/featureform/serving.py (1)</summary>

* `Dataset` (438-776)

</details>
<details>
<summary>client/src/featureform/resources.py (1)</summary>

* `Location` (1627-1633)

</details>

</blockquote></details>
<details>
<summary>provider/offline.go (3)</summary><blockquote>

<details>
<summary>metadata/metadata.go (1)</summary>

* `ResourceID` (226-230)

</details>
<details>
<summary>provider/dataset/dataset.go (1)</summary>

* `Dataset` (22-26)

</details>
<details>
<summary>provider/location/location.go (1)</summary>

* `Location` (34-40)

</details>

</blockquote></details>

</details><details>
<summary>🪛 golangci-lint (1.64.8)</summary>

<details>
<summary>provider/k8s.go</summary>

545-545: multiple-value blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store) (value of type (PrimaryTable, error)) in single-value context

(typecheck)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (15)</summary>

* GitHub Check: Test All Clients (windows-latest, 3.12)
* GitHub Check: Test All Clients (windows-latest, 3.10)
* GitHub Check: Test All Clients (windows-latest, 3.9)
* GitHub Check: Test All Clients (macos-13, 3.12)
* GitHub Check: Test All Clients (macos-13, 3.11)
* GitHub Check: Test All Clients (macos-13, 3.10)
* GitHub Check: Test All Clients (macos-13, 3.9)
* GitHub Check: Test All Clients (ubuntu-latest, 3.12)
* GitHub Check: Test All Clients (ubuntu-latest, 3.11)
* GitHub Check: Test All Clients (ubuntu-latest, 3.10)
* GitHub Check: Test All Clients (ubuntu-latest, 3.9)
* GitHub Check: Run End-to-End Tests (pytests)
* GitHub Check: Run Auto Variant Behave Tests
* GitHub Check: Run Non Auto Variant Behave Tests
* GitHub Check: Run Go Tests

</details>

<details>
<summary>🔇 Additional comments (23)</summary><blockquote>

<details>
<summary>provider/unit_test_store.go (2)</summary>

`126-128`: **LGTM! Method return type updated to match interface changes.**

The `CreatePrimaryTable` method signature has been updated to return `dataset.Dataset` instead of `PrimaryTable` to align with interface changes across offline store implementations.

---

`206-208`: **LGTM! Method return type updated to match interface changes.**

The `RegisterPrimaryFromSourceTable` method signature has been updated to return `dataset.Dataset` instead of `PrimaryTable` to align with interface changes across offline store implementations.

</details>
<details>
<summary>provider/bigquery.go (2)</summary>

`962-985`: **LGTM! Method updated to return Dataset interface with appropriate error handling.**

The `RegisterPrimaryFromSourceTable` method now correctly returns a `dataset.Dataset` by wrapping the `bqPrimaryTable` in a `PrimaryTableToDatasetAdapter`. The error handling has been improved with explicit checks after calling `newBigQueryPrimaryTable`.

---

`1083-1116`: **LGTM! Method updated to return Dataset interface.**

The `CreatePrimaryTable` method now correctly returns a `dataset.Dataset` by wrapping the created table in a `PrimaryTableToDatasetAdapter`, maintaining the original functionality while adapting to the new interface requirements.

</details>
<details>
<summary>provider/clickhouse.go (2)</summary>

`746-793`: **LGTM! Method updated to return Dataset interface.**

The `RegisterPrimaryFromSourceTable` method now correctly returns a `dataset.Dataset` by wrapping the `clickhousePrimaryTable` in a `PrimaryTableToDatasetAdapter`. This implementation is consistent with other offline store providers.

---

`860-881`: **LGTM! Method updated to return Dataset interface.**

The `CreatePrimaryTable` method now correctly returns a `dataset.Dataset` by wrapping the created table in a `PrimaryTableToDatasetAdapter`, maintaining the original functionality while adapting to the new interface requirements.

</details>
<details>
<summary>provider/sql.go (4)</summary>

`337-337`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

---

`367-375`: **Implementation correctly wraps the primary table in an adapter.**

The primary table instance is now wrapped in a `PrimaryTableToDatasetAdapter` to adapt it to the `dataset.Dataset` interface while preserving existing functionality.

---

`378-378`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

---

`402-402`: **Implementation correctly wraps the primary table in an adapter.**

The primary table instance is now wrapped in a `PrimaryTableToDatasetAdapter` to adapt it to the `dataset.Dataset` interface while preserving existing functionality.

</details>
<details>
<summary>provider/spark.go (4)</summary>

`659-659`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

---

`664-666`: **Implementation correctly wraps the primary table in an adapter.**

The primary table instance is now wrapped in a `PrimaryTableToDatasetAdapter` to adapt it to the `dataset.Dataset` interface while preserving existing functionality.

---

`1175-1175`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

---

`1202-1202`: **Implementation correctly wraps the primary table in an adapter.**

The primary table instance is now wrapped in a `PrimaryTableToDatasetAdapter` to adapt it to the `dataset.Dataset` interface while preserving existing functionality.

</details>
<details>
<summary>provider/k8s.go (2)</summary>

`540-540`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

---

`876-877`: **Method signature updated to improve interface consistency.**

The return type has been changed from `PrimaryTable` to `dataset.Dataset` to align with a standardized interface across all offline store implementations.

</details>
<details>
<summary>runner/copy_test.go (4)</summary>

`387-389`: **Method signature return type updated from PrimaryTable to Dataset.**

The return type for `CreatePrimaryTable` in `BrokenNumChunksOfflineStore` has been updated to match the interface change in `OfflineStoreDataset` to return `dataset.Dataset` instead of `PrimaryTable`.

---

`398-400`: **Method signature return type updated from PrimaryTable to Dataset.**

The return type for `RegisterPrimaryFromSourceTable` in `BrokenNumChunksOfflineStore` has been updated to match the interface change in `OfflineStoreDataset` to return `dataset.Dataset` instead of `PrimaryTable`.

---

`711-713`: **Method signature return type updated from PrimaryTable to Dataset.**

The return type for `CreatePrimaryTable` in `MockOfflineStore` has been updated to match the interface change in `OfflineStoreDataset` to return `dataset.Dataset` instead of `PrimaryTable`.

---

`722-724`: **Method signature return type updated from PrimaryTable to Dataset.**

The return type for `RegisterPrimaryFromSourceTable` in `MockOfflineStore` has been updated to match the interface change in `OfflineStoreDataset` to return `dataset.Dataset` instead of `PrimaryTable`.

</details>
<details>
<summary>provider/offline.go (3)</summary>

`533-534`: **Interface update: Changed return type from PrimaryTable to Dataset.**

The return types for `CreatePrimaryTable` and `RegisterPrimaryFromSourceTable` have been updated in the `OfflineStoreDataset` interface to return `dataset.Dataset` instead of `PrimaryTable`. This creates a more consistent interface for dataset operations across the codebase.

---

`1160-1169`: **Implementation updated to use adapter pattern.**

The implementation of `RegisterPrimaryFromSourceTable` now properly returns a `PrimaryTableToDatasetAdapter` that wraps the `memoryPrimaryTable` to satisfy the updated interface return type of `dataset.Dataset`.

---

`1171-1174`: **Implementation updated to use adapter pattern.**

The implementation of `CreatePrimaryTable` now properly returns a `PrimaryTableToDatasetAdapter` that wraps the `memoryPrimaryTable` to satisfy the updated interface return type of `dataset.Dataset`.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T02:55:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 17de0e14862615e0c8ee8f64f9f69347ac880e11 and 6d59b1dce8fbcd7c14767e54c646235529143030.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `provider/correctness_test.go` (6 hunks)
* `provider/k8s.go` (2 hunks)
* `provider/offline_test.go` (20 hunks)
* `provider/spark_test.go` (1 hunks)
* `provider/sql.go` (3 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* provider/sql.go
* provider/k8s.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>provider/correctness_test.go (4)</summary><blockquote>

<details>
<summary>provider/dataset/dataset.go (3)</summary>

* `Dataset` (22-26)
* `SizedDataset` (37-40)
* `Iterator` (135-141)

</details>
<details>
<summary>provider/offline.go (3)</summary>

* `Dataset` (829-837)
* `GenericRecord` (721-721)
* `Value` (83-83)

</details>
<details>
<summary>logging/logging.go (1)</summary>

* `NewTestContext` (314-318)

</details>
<details>
<summary>fftypes/types.go (1)</summary>

* `Value` (315-319)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (16)</summary>

* GitHub Check: Test All Clients (windows-latest, 3.12)
* GitHub Check: Test All Clients (windows-latest, 3.10)
* GitHub Check: Test All Clients (windows-latest, 3.9)
* GitHub Check: Test All Clients (macos-13, 3.12)
* GitHub Check: Test All Clients (macos-13, 3.11)
* GitHub Check: Test All Clients (macos-13, 3.10)
* GitHub Check: Test All Clients (macos-13, 3.9)
* GitHub Check: Test All Clients (ubuntu-latest, 3.12)
* GitHub Check: Test All Clients (ubuntu-latest, 3.11)
* GitHub Check: Test All Clients (ubuntu-latest, 3.10)
* GitHub Check: Test All Clients (ubuntu-latest, 3.9)
* GitHub Check: Run Metadata Client Integration Tests
* GitHub Check: Run End-to-End Tests (pytests)
* GitHub Check: Run Auto Variant Behave Tests
* GitHub Check: Run Non Auto Variant Behave Tests
* GitHub Check: Run Go Tests

</details>

<details>
<summary>🔇 Additional comments (16)</summary><blockquote>

<details>
<summary>provider/correctness_test.go (4)</summary>

`23-23`: **Appropriate addition of logging import.**

Adding the logging package import is necessary for creating test contexts in the new verifyDataset function.

---

`1596-1615`: **Good function signature and error handling in verifyDataset.**

The verifyDataset function properly replaces verifyPrimaryTable, adapting to the broader change where methods now return dataset.Dataset instead of PrimaryTable. The type assertion to dataset.SizedDataset and subsequent error handling is appropriate.

---

`1614-1619`: **Appropriate use of logging.NewTestContext for iterator creation.**

The code correctly uses the logging package to create a test context for the iterator, which is a good practice for testing.

---

`1681-1681`: **Consistent replacement of verifyPrimaryTable with verifyDataset.**

All calls to the old verifyPrimaryTable function have been consistently updated to use the new verifyDataset function, maintaining the functionality while adapting to the interface change.



Also applies to: 1707-1707, 1753-1754, 1816-1817

</details>
<details>
<summary>provider/spark_test.go (1)</summary>

`434-434`: **Test assertion updated to align with interface return type changes**

The test now directly compares `table` with `fetchedTableDs` without any conversion, which aligns with the broader refactoring where `RegisterPrimaryFromSourceTable` now returns a `dataset.Dataset` instead of a `PrimaryTable`.

</details>
<details>
<summary>provider/offline_test.go (11)</summary>

`207-214`: **Use of `table.Location().Location()` for table identification looks consistent.**  
No issues found; this aligns well with the shift to location-based naming for dataset references.

---

`1931-1938`: **Consistent migration to location-based naming.**  
This change mirrors the earlier pattern of extracting the table name from `table.Location()`.

---

`1950-1958`: **Correct casting to `dataset.WriteableDataset`.**  
Detecting the writable interface and calling `WriteBatch` is aligned with the new dataset approach.

---

`2079-2083`: **Good check for `dataset.WriteableDataset` interface.**  
Properly ensures the table supports write operations.

---

`2148-2152`: **Appropriate cast to `dataset.SizedDataset`.**  
Verifying the ability to call `Len()` is consistent with the dataset interface.

---

`2218-2222`: **Definition of `updatedRecords` and `Config` appears correct.**  
This seamlessly integrates with the updated transformation flow.

---

`2253-2254`: **`WriteBatch` call is aligned with the new approach.**  
No issues found; error handling is straightforward.

---

`2258-2259`: **Location-based name retrieval is consistent here as well.**  
Maintains uniform usage of `modifyTransformationConfig`.

---

`2273-2275`: **Correct usage of `sized.Len()` for row count.**  
Appropriate error handling present.

---

`2312-2319`: **Revised assignment to `name` and usage in `pb.SourceVariant`.**  
Matches the design of storing location in both fields for clarity.

---

`2336-2341`: **Ensuring updated records are written before transformation update.**  
The sequence and error handling are correctly implemented.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T04:41:25Z
user: login: simba-git
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:23:25Z
user: login: aolfat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:53:14Z

Review comments:
diff_hunk: @@ -537,12 +537,12 @@ func blobRegisterResourceFromSourceTable(id ResourceID, sourceSchema ResourceSch
 	return &BlobOfflineTable{schema: sourceSchema, store: store}, nil
 }
 
-func (k8s *K8sOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (k8s *K8sOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	fileStoreLocation, isFileStoreLocation := tableLocation.(*pl.FileStoreLocation)
 	if !isFileStoreLocation {
 		return nil, fferr.NewInternalError(fmt.Errorf("location is not a FileStoreLocation"))
 	}
-	return blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)
+	return &PrimaryTableToDatasetAdapter{pt: blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)}, nil
 }
path: provider/k8s.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix error handling in blobRegisterPrimary call.**

The `blobRegisterPrimary` function returns both a `PrimaryTable` and an `error`, but the code ignores the error return value. This could lead to silent failures if the primary table registration fails.


Apply this fix to properly handle the error:

```diff
-	return &PrimaryTableToDatasetAdapter{pt: blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)}, nil
+	primary, err := blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)
+	if err != nil {
+		return nil, err
+	}
+	return &PrimaryTableToDatasetAdapter{pt: primary}, nil
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	}
	primary, err := blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store)
	if err != nil {
		return nil, err
	}
	return &PrimaryTableToDatasetAdapter{pt: primary}, nil
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

545-545: multiple-value blobRegisterPrimary(id, *fileStoreLocation, k8s.logger, k8s.store) (value of type (PrimaryTable, error)) in single-value context

(typecheck)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 6d59b1d
created_at: 2025-04-15T02:55:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1592,6 +1593,49 @@ func verifyPrimaryTable(t *testing.T, primary PrimaryTable, records []GenericRec
 	}
 }
 
+func verifyDataset(t *testing.T, primary dataset.Dataset, records []GenericRecord) {
+	t.Helper()
+
+	// cast to sized
+	sDs, ok := primary.(dataset.SizedDataset)
+	if !ok {
+		t.Fatalf("expected dataset to be sized")
+	}
+
+	numRows, err := sDs.Len()
+	if err != nil {
+		t.Fatalf("could not get number of rows: %v", err)
+	}
+
+	if numRows == 0 {
+		t.Fatalf("expected more than 0 rows")
+	}
+
+	ctx := logging.NewTestContext(t)
+	iterator, err := sDs.Iterator(ctx, 100)
+	if err != nil {
+		t.Fatalf("Could not get generic iterator: %v", err)
+	}
+
+	i := 0
+	for iterator.Next() {
+		for j, v := range iterator.Values() {
+			// NOTE: we're handling float64 differently here given the values returned by Snowflake have less precision
+			// and therefore are not equal unless we round them; if tests require handling of other types, we can add
+			// additional cases here, otherwise the default case will cover all other types
+			switch v.Value.(type) {
+			case float64:
+				assert.True(t, floatsAreClose(v.Value.(float64), records[i][j].(float64), floatTolerance), "expected same values")
+			case time.Time:
+				assert.Equal(t, records[i][j].(time.Time).Truncate(time.Microsecond), v.Value.(time.Time).Truncate(time.Microsecond), "expected same values")
+			default:
+				assert.Equal(t, v, records[i][j], "expected same values")
+			}
+		}
+		i++
+	}
+}
path: provider/correctness_test.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Thorough value comparison logic in verifyDataset.**

The iteration logic correctly handles different data types, including special handling for float64 (using tolerance check) and time.Time (truncating to microseconds) values. However, there's a potential issue in the default equality check.


On line 1632, there's an incorrect equality comparison:
```diff
-				assert.Equal(t, v, records[i][j], "expected same values")
+				assert.Equal(t, v.Value, records[i][j], "expected same values")
```

The code is comparing the entire Value struct with the raw value in records, but should compare only the Value.Value field like in the other type-specific cases.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	i := 0
	for iterator.Next() {
		for j, v := range iterator.Values() {
			// NOTE: we're handling float64 differently here given the values returned by Snowflake have less precision
			// and therefore are not equal unless we round them; if tests require handling of other types, we can add
			// additional cases here, otherwise the default case will cover all other types
			switch v.Value.(type) {
			case float64:
				assert.True(t, floatsAreClose(v.Value.(float64), records[i][j].(float64), floatTolerance), "expected same values")
			case time.Time:
				assert.Equal(t, records[i][j].(time.Time).Truncate(time.Microsecond), v.Value.(time.Time).Truncate(time.Microsecond), "expected same values")
			default:
-				assert.Equal(t, v, records[i][j], "expected same values")
+				assert.Equal(t, v.Value, records[i][j], "expected same values")
			}
		}
		i++
	}
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 661a430
created_at: 2025-04-15T04:41:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -656,12 +656,13 @@ func NewSparkExecutor(
 	}
 }
 
-func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	switch lt := tableLocation.(type) {
 	case *pl.SQLLocation:
 		return nil, fferr.NewInternalErrorf("SQLLocation not supported for primary table registration")
 	case *pl.FileStoreLocation:
-		return blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
+		primary, err := blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
path: provider/spark.go
user: login: simba-git
body: nit, I think return nil, err if this err is real
created_at: 2025-04-15T17:22:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -431,7 +431,7 @@ func testRegisterPrimary(store *SparkOfflineStore) error {
 	if err != nil {
 		return err
 	}
-	if !reflect.DeepEqual(PrimaryTableToDatasetAdapter{table}, fetchedTableDs) {
+	if !reflect.DeepEqual(table, fetchedTableDs) {
path: provider/spark_test.go
user: login: simba-git
body: Q: would validateDataset or w/e work here?
created_at: 2025-04-15T17:23:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -656,12 +656,13 @@ func NewSparkExecutor(
 	}
 }
 
-func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (PrimaryTable, error) {
+func (spark *SparkOfflineStore) RegisterPrimaryFromSourceTable(id ResourceID, tableLocation pl.Location) (dataset.Dataset, error) {
 	switch lt := tableLocation.(type) {
 	case *pl.SQLLocation:
 		return nil, fferr.NewInternalErrorf("SQLLocation not supported for primary table registration")
 	case *pl.FileStoreLocation:
-		return blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
+		primary, err := blobRegisterPrimary(id, *lt, spark.Logger.SugaredLogger, spark.Store)
path: provider/spark.go
user: login: aolfat
body: ok i'll fix these in a follow up
created_at: 2025-04-15T17:53:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-15T02:50:22Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change updates the return types of the `RegisterPrimaryFromSourceTable` and `CreatePrimaryTable` methods across all offline store implementations to return `dataset.Dataset` instead of `PrimaryTable`. Each method now wraps the original `PrimaryTable` instance in a `PrimaryTableToDatasetAdapter` before returning, ensuring compatibility with the `dataset.Dataset` interface. The affected types include BigQuery, ClickHouse, K8s, Spark, SQL, in-memory, unit test, and mock offline stores. Method signatures and interface declarations are updated accordingly, with internal logic and control flow remaining unchanged except for the adapter wrapping.

## Changes

| File(s)                                                                                      | Change Summary                                                                                                                        |
|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| provider/bigquery.go, provider/clickhouse.go, provider/k8s.go, provider/spark.go, provider/sql.go | Updated `RegisterPrimaryFromSourceTable` and `CreatePrimaryTable` methods to return `dataset.Dataset` instead of `PrimaryTable`, wrapping the original table in a `PrimaryTableToDatasetAdapter`. Added explicit error handling in BigQuery and K8s implementations. |
| provider/offline.go                                                                          | Updated `OfflineStoreDataset` interface and `memoryOfflineStore` implementation to use `dataset.Dataset` as the return type for relevant methods, with adapter wrapping. |
| provider/unit_test_store.go, runner/copy_test.go                                             | Updated mock/unit test offline stores' method signatures to return `dataset.Dataset` instead of `PrimaryTable`; implementations return `nil, nil`.                      |
| provider/correctness_test.go                                                                 | Added `verifyDataset` helper function replacing `verifyPrimaryTable` to validate dataset contents in tests, adapting to `dataset.Dataset` interface and handling types precisely. |
| provider/offline_test.go                                                                     | Replaced calls to `table.GetName()` with `table.Location().Location()` and changed `WriteBatch` and `NumRows` calls to use `dataset.WriteableDataset` and `dataset.SizedDataset` interfaces with appropriate type assertions and context usage. |
| provider/spark_test.go                                                                       | Modified test assertion to compare registered table directly with fetched dataset without wrapping in adapter.                      |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Caller
    participant OfflineStore
    participant PrimaryTable
    participant Adapter as PrimaryTableToDatasetAdapter

    Caller->>OfflineStore: RegisterPrimaryFromSourceTable(...) or CreatePrimaryTable(...)
    OfflineStore->>PrimaryTable: Create or register primary table
    OfflineStore->>Adapter: Wrap PrimaryTable in Adapter
    Adapter-->>OfflineStore: Adapter instance
    OfflineStore-->>Caller: Return Adapter (as dataset.Dataset)
```

## Possibly related PRs

- [featureform/featureform#1627](https://github.com/featureform/featureform/pull/1627): Also modifies `bqOfflineStore` methods to return `dataset.Dataset` via adapter wrapping and adds explicit error handling.
- [featureform/featureform#1611](https://github.com/featureform/featureform/pull/1611): Introduces SQL datasets and type converters, relating to adapting primary tables to dataset interfaces and iterators.

## Poem

> A hop, a skip, a type returned anew,  
> Now datasets everywhere, in stores old and true.  
> Adapters abound, wrapping tables with care,  
> So every bunny’s query finds datasets to share.  
> With signatures changed and code made neat,  
> The fields are aligned for a dataset treat!  
> 🐇✨

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6d59b1dce8fbcd7c14767e54c646235529143030 and 661a4307936022724f2ba9df3626f6b894fa3b6c.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `provider/correctness_test.go` (6 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* provider/correctness_test.go

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (16)</summary>

* GitHub Check: Test All Clients (windows-latest, 3.12)
* GitHub Check: Test All Clients (windows-latest, 3.10)
* GitHub Check: Test All Clients (windows-latest, 3.9)
* GitHub Check: Test All Clients (macos-13, 3.12)
* GitHub Check: Test All Clients (macos-13, 3.11)
* GitHub Check: Test All Clients (macos-13, 3.10)
* GitHub Check: Test All Clients (macos-13, 3.9)
* GitHub Check: Test All Clients (ubuntu-latest, 3.12)
* GitHub Check: Test All Clients (ubuntu-latest, 3.11)
* GitHub Check: Test All Clients (ubuntu-latest, 3.10)
* GitHub Check: Test All Clients (ubuntu-latest, 3.9)
* GitHub Check: Run End-to-End Tests (pytests)
* GitHub Check: Run Metadata Client Integration Tests
* GitHub Check: Run Non Auto Variant Behave Tests
* GitHub Check: Run Go Tests
* GitHub Check: Run Auto Variant Behave Tests

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=featureform/featureform&utm_content=1632):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-15T02:53:43Z
body: ## [Codecov](https://app.codecov.io/gh/featureform/featureform/pull/1632?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) Report
Attention: Patch coverage is `32.65306%` with `33 lines` in your changes missing coverage. Please review.
> Project coverage is 48.29%. Comparing base [(`c2e9f19`)](https://app.codecov.io/gh/featureform/featureform/commit/c2e9f19e5aa186f8e5249e2f1e084f3076506130?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) to head [(`661a430`)](https://app.codecov.io/gh/featureform/featureform/commit/661a4307936022724f2ba9df3626f6b894fa3b6c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform).
> Report is 7 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/featureform/featureform/pull/1632?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) | Patch % | Lines |
|---|---|---|
| [provider/clickhouse.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Fclickhouse.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvY2xpY2tob3VzZS5nbw==) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |
| [provider/bigquery.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Fbigquery.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvYmlncXVlcnkuZ28=) | 0.00% | [9 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |
| [provider/k8s.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Fk8s.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvazhzLmdv) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |
| [provider/spark.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Fspark.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvc3BhcmsuZ28=) | 60.00% | [2 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |
| [provider/sql.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Fsql.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvc3FsLmdv) | 81.81% | [2 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |
| [provider/unit\_test\_store.go](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&filepath=provider%2Funit_test_store.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform#diff-cHJvdmlkZXIvdW5pdF90ZXN0X3N0b3JlLmdv) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/featureform/featureform/pull/1632?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1632      +/-   ##
==========================================
+ Coverage   45.56%   48.29%   +2.73%     
==========================================
  Files         189      223      +34     
  Lines       39436    46837    +7401     
  Branches        0      781     +781     
==========================================
+ Hits        17968    22621    +4653     
- Misses      19707    22252    +2545     
- Partials     1761     1964     +203     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/featureform/featureform/pull/1632?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=featureform).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE