3232from feast .feature_service import FeatureService
3333from feast .feature_view import FeatureView
3434from feast .infra .infra_object import Infra
35- from feast .protos .feast .core .InfraObject_pb2 import Infra as InfraProto
3635from feast .on_demand_feature_view import OnDemandFeatureView
3736from feast .protos .feast .core .DataSource_pb2 import DataSource as DataSourceProto
3837from feast .protos .feast .core .Entity_pb2 import Entity as EntityProto
149148)
150149
151150
151+ feast_metadata = Table (
152+ "feast_metadata" ,
153+ metadata ,
154+ Column ("metadata_key" , String (50 ), primary_key = True ),
155+ Column ("metadata_value" , String (50 ), nullable = False ),
156+ Column ("last_updated_timestamp" , BigInteger , nullable = False ),
157+ )
158+
159+
152160class SqlRegistry (BaseRegistry ):
153161 def __init__ (
154162 self , registry_config : Optional [RegistryConfig ], repo_path : Optional [Path ]
@@ -466,11 +474,16 @@ def apply_materialization(
466474 raise ValueError (
467475 f"Cannot apply materialization for feature { feature_view .name } of type { python_class } "
468476 )
469- fv : Union [FeatureView , StreamFeatureView ] = self ._get_object (table , feature_view .name , project , proto_class ,
470- python_class ,
471- "feature_view_name" ,
472- "feature_view_proto" ,
473- FeatureViewNotFoundException )
477+ fv : Union [FeatureView , StreamFeatureView ] = self ._get_object (
478+ table ,
479+ feature_view .name ,
480+ project ,
481+ proto_class ,
482+ python_class ,
483+ "feature_view_name" ,
484+ "feature_view_proto" ,
485+ FeatureViewNotFoundException ,
486+ )
474487 fv .materialization_intervals .append ((start_date , end_date ))
475488 self ._apply_object (table , "feature_view_name" , fv , "feature_view_proto" )
476489
@@ -571,7 +584,7 @@ def get_user_metadata(
571584 def proto (self ) -> RegistryProto :
572585 r = RegistryProto ()
573586 project = ""
574- # TODO(achal): Support Infra object, and last_updated_timestamp.
587+ # TODO(achal): Support last_updated_timestamp.
575588 for lister , registry_proto_field in [
576589 (self .list_entities , r .entities ),
577590 (self .list_feature_views , r .feature_views ),
@@ -587,16 +600,18 @@ def proto(self) -> RegistryProto:
587600 if objs :
588601 registry_proto_field .extend ([obj .to_proto () for obj in objs ])
589602
603+ r .infra .CopyFrom (self .get_infra (project ).to_proto ())
604+ last_update_timestamp = self ._get_last_updated_metadata ()
605+ if last_update_timestamp :
606+ r .last_updated .FromDatetime (last_update_timestamp )
607+
590608 return r
591609
592610 def commit (self ):
593611 # This method is a no-op since we're always writing values eagerly to the db.
594612 pass
595613
596- def _apply_object (
597- self , table , id_field_name , obj , proto_field_name ,
598- name = None
599- ):
614+ def _apply_object (self , table , id_field_name , obj , proto_field_name , name = None ):
600615 name = name or obj .name
601616 with self .engine .connect () as conn :
602617 stmt = select (table ).where (getattr (table .c , id_field_name ) == name )
@@ -625,13 +640,15 @@ def _apply_object(
625640 }
626641 insert_stmt = insert (table ).values (values ,)
627642 conn .execute (insert_stmt )
643+ self ._set_last_updated_metadata (update_datetime )
628644
629645 def _delete_object (self , table , name , project , id_field_name , not_found_exception ):
630646 with self .engine .connect () as conn :
631647 stmt = delete (table ).where (getattr (table .c , id_field_name ) == name )
632648 rows = conn .execute (stmt )
633649 if rows .rowcount < 1 and not_found_exception :
634650 raise not_found_exception (name , project )
651+ self ._set_last_updated_metadata (datetime .utcnow ())
635652 return rows .rowcount
636653
637654 def _get_object (
@@ -665,3 +682,40 @@ def _list_objects(self, table, proto_class, python_class, proto_field_name):
665682 for row in rows
666683 ]
667684 return []
685+
686+ def _set_last_updated_metadata (self , last_updated : datetime ):
687+ with self .engine .connect () as conn :
688+ stmt = select (feast_metadata ).where (
689+ feast_metadata .c .metadata_key == "last_updated_timestamp"
690+ )
691+ row = conn .execute (stmt ).first ()
692+
693+ update_time = int (last_updated .timestamp ())
694+
695+ values = {
696+ "metadata_key" : "last_updated_timestamp" ,
697+ "metadata_value" : f"{ update_time } " ,
698+ "last_updated_timestamp" : update_time ,
699+ }
700+ if row :
701+ update_stmt = (
702+ update (feast_metadata )
703+ .where (feast_metadata .c .metadata_key == "last_updated_timestamp" )
704+ .values (values )
705+ )
706+ conn .execute (update_stmt )
707+ else :
708+ insert_stmt = insert (feast_metadata ).values (values ,)
709+ conn .execute (insert_stmt )
710+
711+ def _get_last_updated_metadata (self ):
712+ with self .engine .connect () as conn :
713+ stmt = select (feast_metadata ).where (
714+ feast_metadata .c .metadata_key == "last_updated_timestamp"
715+ )
716+ row = conn .execute (stmt ).first ()
717+ if not row :
718+ return None
719+ update_time = int (row ["last_updated_timestamp" ])
720+
721+ return datetime .utcfromtimestamp (update_time )
0 commit comments