|
59 | 59 | COMPLETED_ASSET_CHECK_EXECUTION_RECORD_STATUSES, |
60 | 60 | AssetCheckExecutionRecord, |
61 | 61 | AssetCheckExecutionRecordStatus, |
| 62 | + AssetCheckPartitionInfo, |
62 | 63 | ) |
63 | 64 | from dagster._core.storage.dagster_run import DagsterRunStatsSnapshot |
64 | 65 | from dagster._core.storage.event_log.base import ( |
@@ -3008,6 +3009,7 @@ def _store_asset_check_evaluation_planned( |
3008 | 3009 | execution_status=AssetCheckExecutionRecordStatus.PLANNED.value, |
3009 | 3010 | evaluation_event=serialize_value(event), |
3010 | 3011 | evaluation_event_timestamp=self._event_insert_timestamp(event), |
| 3012 | + evaluation_event_storage_id=event_id, |
3011 | 3013 | ) |
3012 | 3014 | for partition_key in partition_keys |
3013 | 3015 | ] |
@@ -3232,6 +3234,135 @@ def get_latest_asset_check_execution_by_key( |
3232 | 3234 | results[check_key] = AssetCheckExecutionRecord.from_db_row(row, key=check_key) |
3233 | 3235 | return results |
3234 | 3236 |
|
| 3237 | + def _get_asset_check_partition_info_for_key( |
| 3238 | + self, |
| 3239 | + check_key: AssetCheckKey, |
| 3240 | + after_storage_id: Optional[int], |
| 3241 | + partition_keys: Optional[Sequence[str]], |
| 3242 | + latest_unpartitioned_materialization_storage_ids: Mapping[AssetKey, int], |
| 3243 | + ) -> Sequence[AssetCheckPartitionInfo]: |
| 3244 | + # Build the base filter conditions |
| 3245 | + filter_conditions = [ |
| 3246 | + AssetCheckExecutionsTable.c.asset_key == check_key.asset_key.to_string(), |
| 3247 | + AssetCheckExecutionsTable.c.check_name == check_key.name, |
| 3248 | + # Historical records may have NULL in the evaluation_event_storage_id column for |
| 3249 | + # PLANNED events |
| 3250 | + AssetCheckExecutionsTable.c.evaluation_event_storage_id.isnot(None), |
| 3251 | + ] |
| 3252 | + if partition_keys is not None: |
| 3253 | + filter_conditions.append(AssetCheckExecutionsTable.c.partition.in_(partition_keys)) |
| 3254 | + |
| 3255 | + # Subquery to find the max id for each partition |
| 3256 | + latest_check_ids_subquery = db_subquery( |
| 3257 | + db_select( |
| 3258 | + [ |
| 3259 | + db.func.max(AssetCheckExecutionsTable.c.id).label("id"), |
| 3260 | + AssetCheckExecutionsTable.c.partition.label("partition"), |
| 3261 | + ] |
| 3262 | + ) |
| 3263 | + .where(db.and_(*filter_conditions)) |
| 3264 | + .group_by(AssetCheckExecutionsTable.c.partition), |
| 3265 | + "latest_check_ids_subquery", |
| 3266 | + ) |
| 3267 | + |
| 3268 | + # Subquery to find the latest materialization storage id for each partition of the |
| 3269 | + # target asset. Note: we don't filter by after_storage_id here because we always want |
| 3270 | + # to return the latest materialization storage id, even if it's older than after_storage_id. |
| 3271 | + latest_materialization_ids_subquery = self._latest_event_ids_by_partition_subquery( |
| 3272 | + check_key.asset_key, |
| 3273 | + [DagsterEventType.ASSET_MATERIALIZATION], |
| 3274 | + asset_partitions=partition_keys, |
| 3275 | + ) |
| 3276 | + |
| 3277 | + # Main query to get all columns for the latest records, joined with latest |
| 3278 | + # materialization storage ids |
| 3279 | + query = db_select( |
| 3280 | + [ |
| 3281 | + AssetCheckExecutionsTable.c.id, |
| 3282 | + AssetCheckExecutionsTable.c.partition, |
| 3283 | + AssetCheckExecutionsTable.c.execution_status, |
| 3284 | + AssetCheckExecutionsTable.c.evaluation_event_storage_id, |
| 3285 | + AssetCheckExecutionsTable.c.materialization_event_storage_id, |
| 3286 | + AssetCheckExecutionsTable.c.run_id, |
| 3287 | + latest_materialization_ids_subquery.c.id.label("latest_materialization_storage_id"), |
| 3288 | + ] |
| 3289 | + ).select_from( |
| 3290 | + AssetCheckExecutionsTable.join( |
| 3291 | + latest_check_ids_subquery, |
| 3292 | + AssetCheckExecutionsTable.c.id == latest_check_ids_subquery.c.id, |
| 3293 | + ).join( |
| 3294 | + latest_materialization_ids_subquery, |
| 3295 | + AssetCheckExecutionsTable.c.partition |
| 3296 | + == latest_materialization_ids_subquery.c.partition, |
| 3297 | + isouter=True, |
| 3298 | + ) |
| 3299 | + ) |
| 3300 | + |
| 3301 | + # these filters are applied to the main query rather than the individual subqueries to ensure |
| 3302 | + # we don't miss records that only have a new materialization or a new check execution but not both |
| 3303 | + if after_storage_id is not None: |
| 3304 | + query = query.where( |
| 3305 | + db.or_( |
| 3306 | + AssetCheckExecutionsTable.c.evaluation_event_storage_id > after_storage_id, |
| 3307 | + latest_materialization_ids_subquery.c.id > after_storage_id, |
| 3308 | + ) |
| 3309 | + ) |
| 3310 | + |
| 3311 | + with self.index_connection() as conn: |
| 3312 | + rows = db_fetch_mappings(conn, query) |
| 3313 | + |
| 3314 | + return [ |
| 3315 | + AssetCheckPartitionInfo( |
| 3316 | + check_key=check_key, |
| 3317 | + partition_key=row["partition"], |
| 3318 | + latest_execution_status=AssetCheckExecutionRecordStatus(row["execution_status"]), |
| 3319 | + latest_target_materialization_storage_id=row["materialization_event_storage_id"], |
| 3320 | + latest_planned_run_id=row["run_id"], |
| 3321 | + latest_check_event_storage_id=row["evaluation_event_storage_id"], |
| 3322 | + latest_materialization_storage_id=max( |
| 3323 | + filter( |
| 3324 | + None, |
| 3325 | + [ |
| 3326 | + row["latest_materialization_storage_id"], |
| 3327 | + latest_unpartitioned_materialization_storage_ids.get( |
| 3328 | + check_key.asset_key |
| 3329 | + ), |
| 3330 | + ], |
| 3331 | + ), |
| 3332 | + default=None, |
| 3333 | + ), |
| 3334 | + ) |
| 3335 | + for row in rows |
| 3336 | + ] |
| 3337 | + |
| 3338 | + def get_asset_check_partition_info( |
| 3339 | + self, |
| 3340 | + keys: Sequence[AssetCheckKey], |
| 3341 | + after_storage_id: Optional[int] = None, |
| 3342 | + partition_keys: Optional[Sequence[str]] = None, |
| 3343 | + ) -> Sequence[AssetCheckPartitionInfo]: |
| 3344 | + check.list_param(keys, "keys", of_type=AssetCheckKey) |
| 3345 | + check.opt_int_param(after_storage_id, "after_storage_id") |
| 3346 | + |
| 3347 | + infos = [] |
| 3348 | + latest_unpartitioned_materialization_storage_ids = ( |
| 3349 | + self._get_latest_unpartitioned_materialization_storage_ids( |
| 3350 | + list(set(key.asset_key for key in keys)) |
| 3351 | + ) |
| 3352 | + ) |
| 3353 | + # the inner query is not feasible to join in a single query because the latest materialization ids subquery, |
| 3354 | + # so for now we fetch the info for each key separately |
| 3355 | + for key in keys: |
| 3356 | + infos.extend( |
| 3357 | + self._get_asset_check_partition_info_for_key( |
| 3358 | + key, |
| 3359 | + after_storage_id, |
| 3360 | + partition_keys, |
| 3361 | + latest_unpartitioned_materialization_storage_ids, |
| 3362 | + ) |
| 3363 | + ) |
| 3364 | + return infos |
| 3365 | + |
3235 | 3366 | @property |
3236 | 3367 | def supports_asset_checks(self): # pyright: ignore[reportIncompatibleMethodOverride] |
3237 | 3368 | return self.has_table(AssetCheckExecutionsTable.name) |
|
0 commit comments