1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.data.adselection; 18 19 import android.adservices.adselection.ReportInteractionRequest; 20 import android.net.Uri; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 import androidx.room.Dao; 25 import androidx.room.Insert; 26 import androidx.room.OnConflictStrategy; 27 import androidx.room.Query; 28 import androidx.room.Transaction; 29 30 import com.android.adservices.LoggerFactory; 31 32 import java.time.Instant; 33 import java.util.List; 34 35 /** 36 * Data Access Object interface for access to the local AdSelection data storage. 37 * 38 * <p>Annotation will generate Room based SQLite Dao implementation. 39 */ 40 @Dao 41 public abstract class AdSelectionEntryDao { 42 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 43 /** 44 * Add a new successful ad selection entry into the table ad_selection. 45 * 46 * @param adSelection is the AdSelection to add to the table ad_selection if the ad_selection_id 47 * not exists. 48 */ 49 // TODO(b/230568647): retry adSelectionId generation in case of collision 50 @Insert(onConflict = OnConflictStrategy.ABORT) persistAdSelection(DBAdSelection adSelection)51 public abstract void persistAdSelection(DBAdSelection adSelection); 52 53 /** 54 * Write a buyer decision logic entry into the table buyer_decision_logic. 55 * 56 * @param buyerDecisionLogic is the BuyerDecisionLogic to write to table buyer_decision_logic. 57 */ 58 @Insert(onConflict = OnConflictStrategy.REPLACE) persistBuyerDecisionLogic(DBBuyerDecisionLogic buyerDecisionLogic)59 public abstract void persistBuyerDecisionLogic(DBBuyerDecisionLogic buyerDecisionLogic); 60 61 /** 62 * Add an ad selection override into the table ad_selection_overrides 63 * 64 * @param adSelectionOverride is the AdSelectionOverride to add to table ad_selection_overrides. 65 * If a {@link DBAdSelectionOverride} object with the {@code adSelectionConfigId} already 66 * exists, this will replace the existing object. 67 */ 68 @Insert(onConflict = OnConflictStrategy.REPLACE) persistAdSelectionOverride(DBAdSelectionOverride adSelectionOverride)69 public abstract void persistAdSelectionOverride(DBAdSelectionOverride adSelectionOverride); 70 71 /** 72 * Add an ad selection override for Buyers' decision logic 73 * 74 * @param buyersDecisionLogicOverride is an override for the ad_selection_buyer_logic_overrides 75 * If a {@link DBBuyerDecisionOverride} object with the {@code adSelectionConfigId} already 76 * exists, this will replace the existing object. 77 */ 78 @Insert(onConflict = OnConflictStrategy.REPLACE) persistBuyersDecisionLogicOverride( List<DBBuyerDecisionOverride> buyersDecisionLogicOverride)79 public abstract void persistBuyersDecisionLogicOverride( 80 List<DBBuyerDecisionOverride> buyersDecisionLogicOverride); 81 82 /** 83 * Adds a list of registered ad interactions to the table registered_ad_interactions 84 * 85 * <p>This method is not meant to be used on its own, since it doesn't take into account the 86 * maximum size of {@code registered_ad_interactions}. Use {@link 87 * #safelyInsertRegisteredAdInteractions(long, List, long, long, int)} instead. 88 * 89 * @param registeredAdInteractions is the list of {@link DBRegisteredAdInteraction} objects to 90 * write to the table registered_ad_interactions. 91 */ 92 @Insert(onConflict = OnConflictStrategy.REPLACE) persistDBRegisteredAdInteractions( List<DBRegisteredAdInteraction> registeredAdInteractions)93 protected abstract void persistDBRegisteredAdInteractions( 94 List<DBRegisteredAdInteraction> registeredAdInteractions); 95 96 /** 97 * Checks if there is a row in the ad selection data with the unique key ad_selection_id 98 * 99 * @param adSelectionId which is the key to query the corresponding ad selection data. 100 * @return true if row exists, false otherwise 101 */ 102 @Query( 103 "SELECT EXISTS(SELECT 1 FROM ad_selection WHERE ad_selection_id = :adSelectionId LIMIT" 104 + " 1)") doesAdSelectionIdExist(long adSelectionId)105 public abstract boolean doesAdSelectionIdExist(long adSelectionId); 106 107 /** 108 * Checks if there is a row in the buyer decision logic data with the unique key 109 * bidding_logic_uri 110 * 111 * @param biddingLogicUri which is the key to query the corresponding buyer decision logic data. 112 * @return true if row exists, false otherwise 113 */ 114 @Query( 115 "SELECT EXISTS(SELECT 1 FROM buyer_decision_logic WHERE bidding_logic_uri =" 116 + " :biddingLogicUri LIMIT 1)") doesBuyerDecisionLogicExist(Uri biddingLogicUri)117 public abstract boolean doesBuyerDecisionLogicExist(Uri biddingLogicUri); 118 119 /** 120 * Checks if there is a row in the ad selection override data with the unique key 121 * ad_selection_config_id 122 * 123 * @param adSelectionConfigId which is the key to query the corresponding ad selection override 124 * data. 125 * @return true if row exists, false otherwise 126 */ 127 @Query( 128 "SELECT EXISTS(SELECT 1 FROM ad_selection_overrides WHERE ad_selection_config_id =" 129 + " :adSelectionConfigId AND app_package_name = :appPackageName LIMIT 1)") doesAdSelectionOverrideExistForPackageName( String adSelectionConfigId, String appPackageName)130 public abstract boolean doesAdSelectionOverrideExistForPackageName( 131 String adSelectionConfigId, String appPackageName); 132 133 /** 134 * Checks if there is a row in the registered_ad_interactions table that matches the primary key 135 * combination of adSelectionId, interactionKey, and destination 136 * 137 * @param adSelectionId serves as the primary key denoting the ad selection process this entry 138 * id associated with 139 * @param interactionKey the interaction key 140 * @param destination denotes buyer, seller, etc. 141 */ 142 @Query( 143 "SELECT EXISTS(SELECT 1 FROM registered_ad_interactions WHERE ad_selection_id =" 144 + " :adSelectionId AND interaction_key = :interactionKey AND destination" 145 + " = :destination LIMIT 1)") doesRegisteredAdInteractionExist( long adSelectionId, String interactionKey, @ReportInteractionRequest.ReportingDestination int destination)146 public abstract boolean doesRegisteredAdInteractionExist( 147 long adSelectionId, 148 String interactionKey, 149 @ReportInteractionRequest.ReportingDestination int destination); 150 151 /** 152 * Get the ad selection entry by its unique key ad_selection_id. 153 * 154 * @param adSelectionId which is the key to query the corresponding ad selection entry. 155 * @return an {@link DBAdSelectionEntry} if exists. 156 */ 157 @Query( 158 "SELECT ad_selection.ad_selection_id as ad_selection_id," 159 + " ad_selection.custom_audience_signals_owner as custom_audience_signals_owner," 160 + " ad_selection.custom_audience_signals_buyer as custom_audience_signals_buyer," 161 + " ad_selection.custom_audience_signals_name as custom_audience_signals_name," 162 + " ad_selection.custom_audience_signals_activation_time as" 163 + " custom_audience_signals_activation_time," 164 + " ad_selection.custom_audience_signals_expiration_time as" 165 + " custom_audience_signals_expiration_time," 166 + " ad_selection.custom_audience_signals_user_bidding_signals as" 167 + " custom_audience_signals_user_bidding_signals, ad_selection.contextual_signals" 168 + " as contextual_signals,ad_selection.winning_ad_render_uri as" 169 + " winning_ad_render_uri,ad_selection.winning_ad_bid as" 170 + " winning_ad_bid,ad_selection.creation_timestamp as" 171 + " creation_timestamp,buyer_decision_logic.buyer_decision_logic_js as" 172 + " buyer_decision_logic_js, ad_selection.bidding_logic_uri as bidding_logic_uri" 173 + " FROM ad_selection LEFT JOIN buyer_decision_logic ON" 174 + " ad_selection.bidding_logic_uri = buyer_decision_logic.bidding_logic_uri WHERE" 175 + " ad_selection.ad_selection_id = :adSelectionId") getAdSelectionEntityById(long adSelectionId)176 public abstract DBAdSelectionEntry getAdSelectionEntityById(long adSelectionId); 177 178 /** 179 * Get the ad selection entries with a batch of ad_selection_ids. 180 * 181 * @param adSelectionIds are the list of keys to query the corresponding ad selection entries. 182 * @return ad selection entries if exists. 183 */ 184 @Query( 185 "SELECT ad_selection.ad_selection_id AS" 186 + " ad_selection_id,ad_selection.custom_audience_signals_owner as" 187 + " custom_audience_signals_owner, ad_selection.custom_audience_signals_buyer as" 188 + " custom_audience_signals_buyer, ad_selection.custom_audience_signals_name as" 189 + " custom_audience_signals_name," 190 + " ad_selection.custom_audience_signals_activation_time as" 191 + " custom_audience_signals_activation_time," 192 + " ad_selection.custom_audience_signals_expiration_time as" 193 + " custom_audience_signals_expiration_time," 194 + " ad_selection.custom_audience_signals_user_bidding_signals as" 195 + " custom_audience_signals_user_bidding_signals, ad_selection.contextual_signals" 196 + " AS contextual_signals,ad_selection.winning_ad_render_uri AS" 197 + " winning_ad_render_uri,ad_selection.winning_ad_bid AS winning_ad_bid," 198 + " ad_selection.creation_timestamp as creation_timestamp," 199 + " buyer_decision_logic.buyer_decision_logic_js AS buyer_decision_logic_js," 200 + " ad_selection.bidding_logic_uri AS bidding_logic_uri FROM ad_selection LEFT" 201 + " JOIN buyer_decision_logic ON ad_selection.bidding_logic_uri =" 202 + " buyer_decision_logic.bidding_logic_uri WHERE ad_selection.ad_selection_id IN" 203 + " (:adSelectionIds) ") getAdSelectionEntities(List<Long> adSelectionIds)204 public abstract List<DBAdSelectionEntry> getAdSelectionEntities(List<Long> adSelectionIds); 205 206 /** 207 * Get the ad selection entries with a batch of ad_selection_ids. 208 * 209 * @param adSelectionIds are the list of keys to query the corresponding ad selection entries. 210 * @return ad selection entries if exists. 211 */ 212 @Query( 213 "SELECT ad_selection.ad_selection_id AS" 214 + " ad_selection_id,ad_selection.custom_audience_signals_owner as" 215 + " custom_audience_signals_owner, ad_selection.custom_audience_signals_buyer as" 216 + " custom_audience_signals_buyer, ad_selection.custom_audience_signals_name as" 217 + " custom_audience_signals_name," 218 + " ad_selection.custom_audience_signals_activation_time as" 219 + " custom_audience_signals_activation_time," 220 + " ad_selection.custom_audience_signals_expiration_time as" 221 + " custom_audience_signals_expiration_time," 222 + " ad_selection.custom_audience_signals_user_bidding_signals as" 223 + " custom_audience_signals_user_bidding_signals, ad_selection.contextual_signals" 224 + " AS contextual_signals,ad_selection.winning_ad_render_uri AS" 225 + " winning_ad_render_uri,ad_selection.winning_ad_bid AS winning_ad_bid," 226 + " ad_selection.creation_timestamp as creation_timestamp," 227 + " buyer_decision_logic.buyer_decision_logic_js AS buyer_decision_logic_js," 228 + " ad_selection.bidding_logic_uri AS bidding_logic_uri FROM ad_selection LEFT" 229 + " JOIN buyer_decision_logic ON ad_selection.bidding_logic_uri =" 230 + " buyer_decision_logic.bidding_logic_uri WHERE ad_selection.ad_selection_id IN" 231 + " (:adSelectionIds) AND ad_selection.caller_package_name = :callerPackageName") getAdSelectionEntities( List<Long> adSelectionIds, String callerPackageName)232 public abstract List<DBAdSelectionEntry> getAdSelectionEntities( 233 List<Long> adSelectionIds, String callerPackageName); 234 235 /** 236 * Get ad selection JS override by its unique key and the package name of the app that created 237 * the override. 238 * 239 * @return ad selection override result if exists. 240 */ 241 @Query( 242 "SELECT decision_logic FROM ad_selection_overrides WHERE ad_selection_config_id =" 243 + " :adSelectionConfigId AND app_package_name = :appPackageName") 244 @Nullable getDecisionLogicOverride( String adSelectionConfigId, String appPackageName)245 public abstract String getDecisionLogicOverride( 246 String adSelectionConfigId, String appPackageName); 247 248 /** 249 * Get ad selection trusted scoring signals override by its unique key and the package name of 250 * the app that created the override. 251 * 252 * @return ad selection override result if exists. 253 */ 254 @Query( 255 "SELECT trusted_scoring_signals FROM ad_selection_overrides WHERE" 256 + " ad_selection_config_id = :adSelectionConfigId AND app_package_name =" 257 + " :appPackageName") 258 @Nullable getTrustedScoringSignalsOverride( String adSelectionConfigId, String appPackageName)259 public abstract String getTrustedScoringSignalsOverride( 260 String adSelectionConfigId, String appPackageName); 261 262 /** 263 * Get ad selection buyer decision logic override by its unique key and the package name of the 264 * app that created the override. 265 * 266 * @return ad selection override result if exists. 267 */ 268 @Query( 269 "SELECT * FROM ad_selection_buyer_logic_overrides WHERE" 270 + " ad_selection_config_id = :adSelectionConfigId AND app_package_name =" 271 + " :appPackageName") 272 @Nullable getBuyersDecisionLogicOverride( String adSelectionConfigId, String appPackageName)273 public abstract List<DBBuyerDecisionOverride> getBuyersDecisionLogicOverride( 274 String adSelectionConfigId, String appPackageName); 275 276 /** 277 * Gets the interaction reporting uri that was registered with the primary key combination of 278 * {@code adSelectionId}, {@code interactionKey}, and {@code destination}. 279 * 280 * @return interaction reporting uri if exists. 281 */ 282 @Query( 283 "SELECT interaction_reporting_uri FROM registered_ad_interactions WHERE" 284 + " ad_selection_id = :adSelectionId AND interaction_key = :interactionKey AND" 285 + " destination = :destination") 286 @Nullable getRegisteredAdInteractionUri( long adSelectionId, String interactionKey, @ReportInteractionRequest.ReportingDestination int destination)287 public abstract Uri getRegisteredAdInteractionUri( 288 long adSelectionId, 289 String interactionKey, 290 @ReportInteractionRequest.ReportingDestination int destination); 291 292 /** 293 * Gets the {@link DBAdSelectionHistogramInfo} representing the histogram information associated 294 * with a given ad selection. 295 * 296 * @return a {@link DBAdSelectionHistogramInfo} containing the histogram info associated with 297 * the ad selection, or {@code null} if no match is found 298 */ 299 @Query( 300 "SELECT custom_audience_signals_buyer, ad_counter_keys FROM ad_selection " 301 + "WHERE ad_selection_id = :adSelectionId " 302 + "AND caller_package_name = :callerPackageName") 303 @Nullable getAdSelectionHistogramInfo( long adSelectionId, @NonNull String callerPackageName)304 public abstract DBAdSelectionHistogramInfo getAdSelectionHistogramInfo( 305 long adSelectionId, @NonNull String callerPackageName); 306 307 /** 308 * Clean up expired adSelection entries if it is older than the given timestamp. If 309 * creation_timestamp < expirationTime, the ad selection entry will be removed from the 310 * ad_selection table. 311 * 312 * @param expirationTime is the cutoff time to expire the AdSelectionEntry. 313 */ 314 @Query("DELETE FROM ad_selection WHERE creation_timestamp < :expirationTime") removeExpiredAdSelection(Instant expirationTime)315 public abstract void removeExpiredAdSelection(Instant expirationTime); 316 317 /** 318 * Clean up selected ad selection data entry data in batch by their ad_selection_ids. 319 * 320 * @param adSelectionIds is the list of adSelectionIds to identify the data entries to be 321 * removed from ad_selection and buyer_decision_logic tables. 322 */ 323 @Query("DELETE FROM ad_selection WHERE ad_selection_id IN (:adSelectionIds)") removeAdSelectionEntriesByIds(List<Long> adSelectionIds)324 public abstract void removeAdSelectionEntriesByIds(List<Long> adSelectionIds); 325 326 /** 327 * Clean up selected ad selection override data by its {@code adSelectionConfigId} 328 * 329 * @param adSelectionConfigId is the {@code adSelectionConfigId} to identify the data entries to 330 * be removed from the ad_selection_overrides table. 331 */ 332 @Query( 333 "DELETE FROM ad_selection_overrides WHERE ad_selection_config_id = :adSelectionConfigId" 334 + " AND app_package_name = :appPackageName") removeAdSelectionOverrideByIdAndPackageName( String adSelectionConfigId, String appPackageName)335 public abstract void removeAdSelectionOverrideByIdAndPackageName( 336 String adSelectionConfigId, String appPackageName); 337 338 /** 339 * Clean up buyer decision logic override data by its {@code adSelectionConfigId} 340 * 341 * @param adSelectionConfigId is the {@code adSelectionConfigId} to identify the data entries to 342 * be removed from the ad_selection_overrides table. 343 */ 344 @Query( 345 "DELETE FROM ad_selection_buyer_logic_overrides WHERE ad_selection_config_id = " 346 + ":adSelectionConfigId AND app_package_name = :appPackageName") removeBuyerDecisionLogicOverrideByIdAndPackageName( String adSelectionConfigId, String appPackageName)347 public abstract void removeBuyerDecisionLogicOverrideByIdAndPackageName( 348 String adSelectionConfigId, String appPackageName); 349 350 /** 351 * Clean up buyer_decision_logic entries in batch if the bidding_logic_uri no longer exists in 352 * the table ad_selection. 353 */ 354 @Query( 355 "DELETE FROM buyer_decision_logic WHERE bidding_logic_uri NOT IN " 356 + "( SELECT DISTINCT bidding_logic_uri " 357 + "FROM ad_selection " 358 + "WHERE bidding_logic_uri is NOT NULL)") removeExpiredBuyerDecisionLogic()359 public abstract void removeExpiredBuyerDecisionLogic(); 360 361 /** Clean up all ad selection override data */ 362 @Query("DELETE FROM ad_selection_overrides WHERE app_package_name = :appPackageName") removeAllAdSelectionOverrides(String appPackageName)363 public abstract void removeAllAdSelectionOverrides(String appPackageName); 364 365 /** Clean up all buyers' decision logic data */ 366 @Query( 367 "DELETE FROM ad_selection_buyer_logic_overrides WHERE app_package_name =" 368 + " :appPackageName") removeAllBuyerDecisionOverrides(String appPackageName)369 public abstract void removeAllBuyerDecisionOverrides(String appPackageName); 370 371 /** 372 * Checks if there is a row in the ad selection data with the unique combination of 373 * ad_selection_id and caller_package_name 374 * 375 * @param adSelectionId which is the key to query the corresponding ad selection data. 376 * @param callerPackageName the caller's package name, to be verified against the 377 * calling_package_name that exists in the ad_selection_entry 378 * @return true if row exists, false otherwise 379 */ 380 @Query( 381 "SELECT EXISTS(SELECT 1 FROM ad_selection WHERE ad_selection_id = :adSelectionId" 382 + " AND caller_package_name = :callerPackageName LIMIT" 383 + " 1)") doesAdSelectionMatchingCallerPackageNameExist( long adSelectionId, String callerPackageName)384 public abstract boolean doesAdSelectionMatchingCallerPackageNameExist( 385 long adSelectionId, String callerPackageName); 386 387 /** 388 * Add an ad selection from outcomes override into the table 389 * ad_selection_from_outcomes_overrides 390 * 391 * @param adSelectionFromOutcomesOverride is the AdSelectionFromOutcomesOverride to add to table 392 * ad_selection_overrides. If a {@link DBAdSelectionFromOutcomesOverride} object with the 393 * {@code adSelectionConfigFromOutcomesId} already exists, this will replace the existing 394 * object. 395 */ 396 @Insert(onConflict = OnConflictStrategy.REPLACE) persistAdSelectionFromOutcomesOverride( DBAdSelectionFromOutcomesOverride adSelectionFromOutcomesOverride)397 public abstract void persistAdSelectionFromOutcomesOverride( 398 DBAdSelectionFromOutcomesOverride adSelectionFromOutcomesOverride); 399 400 /** 401 * Checks if there is a row in the ad selection override data with the unique key 402 * ad_selection_from_outcomes_config_id 403 * 404 * @param adSelectionFromOutcomesConfigId which is the key to query the corresponding ad 405 * selection override data. 406 * @return true if row exists, false otherwise 407 */ 408 @Query( 409 "SELECT EXISTS(SELECT 1 FROM ad_selection_from_outcomes_overrides WHERE " 410 + "ad_selection_from_outcomes_config_id = " 411 + ":adSelectionFromOutcomesConfigId AND app_package_name = :appPackageName " 412 + "LIMIT 1)") doesAdSelectionFromOutcomesOverrideExistForPackageName( String adSelectionFromOutcomesConfigId, String appPackageName)413 public abstract boolean doesAdSelectionFromOutcomesOverrideExistForPackageName( 414 String adSelectionFromOutcomesConfigId, String appPackageName); 415 416 /** 417 * Get ad selection from outcomes selection logic JS override by its unique key and the package 418 * name of the app that created the override. 419 * 420 * @return ad selection override result if exists. 421 */ 422 @Query( 423 "SELECT selection_logic_js FROM ad_selection_from_outcomes_overrides WHERE " 424 + "ad_selection_from_outcomes_config_id = :adSelectionFromOutcomesConfigId " 425 + "AND app_package_name = :appPackageName") 426 @Nullable getSelectionLogicOverride( String adSelectionFromOutcomesConfigId, String appPackageName)427 public abstract String getSelectionLogicOverride( 428 String adSelectionFromOutcomesConfigId, String appPackageName); 429 430 /** 431 * Get ad selection from outcomes signals override by its unique key and the package name of the 432 * app that created the override. 433 * 434 * @return ad selection from outcomes override result if exists. 435 */ 436 @Query( 437 "SELECT selection_signals FROM ad_selection_from_outcomes_overrides WHERE" 438 + " ad_selection_from_outcomes_config_id = :adSelectionFromOutcomesConfigId " 439 + "AND app_package_name = :appPackageName") 440 @Nullable getSelectionSignalsOverride( String adSelectionFromOutcomesConfigId, String appPackageName)441 public abstract String getSelectionSignalsOverride( 442 String adSelectionFromOutcomesConfigId, String appPackageName); 443 444 /** 445 * Clean up selected ad selection from outcomes override data by its {@code 446 * adSelectionFromOutcomesConfigId} 447 * 448 * @param adSelectionFromOutcomesConfigId is to identify the data entries to be removed from the 449 * ad_selection_overrides table. 450 */ 451 @Query( 452 "DELETE FROM ad_selection_from_outcomes_overrides WHERE " 453 + "ad_selection_from_outcomes_config_id = :adSelectionFromOutcomesConfigId AND " 454 + "app_package_name = :appPackageName") removeAdSelectionFromOutcomesOverrideByIdAndPackageName( String adSelectionFromOutcomesConfigId, String appPackageName)455 public abstract void removeAdSelectionFromOutcomesOverrideByIdAndPackageName( 456 String adSelectionFromOutcomesConfigId, String appPackageName); 457 458 /** Clean up all ad selection from outcomes override data */ 459 @Query( 460 "DELETE FROM ad_selection_from_outcomes_overrides WHERE app_package_name = " 461 + ":appPackageName") removeAllAdSelectionFromOutcomesOverrides(String appPackageName)462 public abstract void removeAllAdSelectionFromOutcomesOverrides(String appPackageName); 463 464 /** 465 * Clean up registered_ad_interaction entries in batch if the {@code adSelectionId} no longer 466 * exists in the table ad_selection. 467 */ 468 @Query( 469 "DELETE FROM registered_ad_interactions WHERE ad_selection_id NOT IN " 470 + "( SELECT DISTINCT ad_selection_id " 471 + "FROM ad_selection " 472 + "WHERE ad_selection_id is NOT NULL)") removeExpiredRegisteredAdInteractions()473 public abstract void removeExpiredRegisteredAdInteractions(); 474 475 /** Returns total size of the {@code registered_ad_interaction} table. */ 476 @Query("SELECT COUNT(*) FROM registered_ad_interactions") getTotalNumRegisteredAdInteractions()477 public abstract long getTotalNumRegisteredAdInteractions(); 478 479 /** 480 * Returns total number of the {@code registered_ad_interaction}s that match a given {@code 481 * adSelectionId} and {@code reportingDestination}. 482 */ 483 @Query( 484 "SELECT COUNT(*) FROM registered_ad_interactions WHERE ad_selection_id =" 485 + " :adSelectionId AND destination = :reportingDestination") getNumRegisteredAdInteractionsPerAdSelectionAndDestination( long adSelectionId, @ReportInteractionRequest.ReportingDestination int reportingDestination)486 public abstract long getNumRegisteredAdInteractionsPerAdSelectionAndDestination( 487 long adSelectionId, 488 @ReportInteractionRequest.ReportingDestination int reportingDestination); 489 490 /** 491 * Inserts a list of {@link DBRegisteredAdInteraction}s into the database, enforcing these 492 * limitations: 493 * 494 * <p>We will not allow the total size of the {@code registered_ad_interaction} to exceed {@code 495 * maxTotalNumRegisteredInteractions} 496 * 497 * <p>We will not allow the number of registered ad interactions {@code adSelectionId} and 498 * {@code reportingDestination} to exceed {@code maxPerDestinationNumRegisteredInteractions}. 499 * 500 * <p>This transaction is separate in order to minimize the critical region while locking the 501 * database. 502 */ 503 @Transaction safelyInsertRegisteredAdInteractions( long adSelectionId, @NonNull List<DBRegisteredAdInteraction> registeredAdInteractions, long maxTotalNumRegisteredInteractions, long maxPerDestinationNumRegisteredInteractions, int reportingDestination)504 public void safelyInsertRegisteredAdInteractions( 505 long adSelectionId, 506 @NonNull List<DBRegisteredAdInteraction> registeredAdInteractions, 507 long maxTotalNumRegisteredInteractions, 508 long maxPerDestinationNumRegisteredInteractions, 509 int reportingDestination) { 510 long currentNumRegisteredInteractions = getTotalNumRegisteredAdInteractions(); 511 512 if (currentNumRegisteredInteractions >= maxTotalNumRegisteredInteractions) { 513 sLogger.v("Registered Ad Interaction max table size reached! Skipping entire list."); 514 return; 515 } 516 517 long currentNumRegisteredInteractionsPerDestination = 518 getNumRegisteredAdInteractionsPerAdSelectionAndDestination( 519 adSelectionId, reportingDestination); 520 521 if (currentNumRegisteredInteractionsPerDestination 522 >= maxPerDestinationNumRegisteredInteractions) { 523 sLogger.v( 524 "Maximum number of Registered Ad Interactions for this adSelectionId and" 525 + " reportingDestination reached! Skipping entire list."); 526 return; 527 } 528 529 long numAvailableRowsInTable = 530 Math.max(0, maxTotalNumRegisteredInteractions - currentNumRegisteredInteractions); 531 532 long numAvailableRowsInTablePerAdSelectionIdAndDestination = 533 Math.max( 534 0, 535 maxPerDestinationNumRegisteredInteractions 536 - currentNumRegisteredInteractionsPerDestination); 537 538 int numEntriesToCommit = 539 (int) 540 Math.min( 541 numAvailableRowsInTablePerAdSelectionIdAndDestination, 542 Math.min(registeredAdInteractions.size(), numAvailableRowsInTable)); 543 List<DBRegisteredAdInteraction> registeredAdInteractionsToCommit = 544 registeredAdInteractions.subList(0, numEntriesToCommit); 545 546 persistDBRegisteredAdInteractions(registeredAdInteractionsToCommit); 547 } 548 } 549