0
Files
src/sql/statement.h
Vikram Pasupathy 2cd0591870 Implement new schema and time based removal into CdmStorageDatabase
A new schema is introduced into CdmStorageDatabase to add last_modified
and file_size to be able to support time-based deletion and time-based
usage calculation.

Methods were implemented to delete by time frame, by storage key, and
to calculate file size using SQL queries for time frames by storing
the file size into the CdmStorageDatabase to be a one time operation.

This CL also provides the functionality that automatically deletes the
database if a Delete function for a subset (i.e file, storage key,
time-frame) ends up removing entries such that there are no more entries
in the database.

An SQL statement is used to alter the version1 database so that we don't
run into crashes when a user that already has the CdmStorageDatabase
v1.0 in their profile runs into queries that are made for the v2.0, such
as writing into the column file_size, since file_size is only in v2.0.

Testing: unittests +

Manual Testing Steps for the Schema Alter code:
1. Build chrome on main branch.
2. Go to chrome://flags and enable kCdmStorageDatabase and
kCdmStorageDatabaseMigration.
3. Open a new profile, name it CdmStorageDatabase (or anything you want)
4. Play this website: https://integration.staging.widevine.com/player?contentUrl=https://storage.googleapis.com/wvtemp/hmchen/poc/tears_hd_cenc.mpd&proxyServerUrl=https://proxy.uat.widevine.com/proxy?video_id=GTS_CAN_PLAY_CLIENT_TOKEN%26provider=widevine_test%26full_response=true&persistentStateRequired=true
5. Log the kVersionNumber (should be 1) (I did this through LOG(ERROR)).
6. A value should be written.
7. Quit Chrome.
8. Switch your branch to these changes using git and build chrome.
9. Open chrome, verify the same flags are still enabled, and open
the same profile you used as before.
10. Play same website as step .
11. log the kVersionNumber (should be 2) (I did this through LOG(ERROR))
12. View in chrome://histograms/Media.EME that the
IncompatibleDatabaseDetected histogram was triggered.
13. Verify that playback plays continuously and Chrome does not crash.

OBSOLETE_HISTOGRAMS= Media.EME.CdmStorageManager.DeleteDatabaseError.Incognito, Media.EME.CdmStorageManager.DeleteDatabaseError.NonIncognito.

Bug: 1454512
Change-Id: I64f9dff580114545ececf5d7b4f03124b1f55192
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4994868
Reviewed-by: Evan Stade <estade@chromium.org>
Reviewed-by: Evan Liu <evliu@google.com>
Reviewed-by: John Rummell <jrummell@chromium.org>
Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1242731}
2024-01-04 01:06:06 +00:00

301 lines
12 KiB
C++

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SQL_STATEMENT_H_
#define SQL_STATEMENT_H_
#include <stdint.h>
#include <string>
#include <vector>
#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/dcheck_is_on.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/strings/string_piece.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "sql/database.h"
namespace sql {
enum class SqliteResultCode : int;
// Possible return values from ColumnType in a statement. These should match
// the values in sqlite3.h.
enum class ColumnType {
kInteger = 1,
kFloat = 2,
kText = 3,
kBlob = 4,
kNull = 5,
};
// Compiles and executes SQL statements.
//
// This class is not thread-safe. An instance must be accessed from a single
// sequence. This is enforced in DCHECK-enabled builds.
//
// Normal usage:
// sql::Statement s(connection_.GetUniqueStatement(...));
// s.BindInt(0, a);
// if (s.Step())
// return s.ColumnString(0);
//
// If there are errors getting the statement, the statement will be inert; no
// mutating or database-access methods will work. If you need to check for
// validity, use:
// if (!s.is_valid())
// return false;
//
// Step() and Run() just return true to signal success. If you want to handle
// specific errors such as database corruption, install an error handler in
// in the connection object using set_error_delegate().
class COMPONENT_EXPORT(SQL) Statement {
public:
// Utility function that returns what //sql code encodes the 'time' value as
// in a database when using BindTime
static int64_t TimeToSqlValue(base::Time time);
// Creates an uninitialized statement. The statement will be invalid until
// you initialize it via Assign.
Statement();
explicit Statement(scoped_refptr<Database::StatementRef> ref);
Statement(const Statement&) = delete;
Statement& operator=(const Statement&) = delete;
Statement(Statement&&) = delete;
Statement& operator=(Statement&&) = delete;
~Statement();
// Initializes this object with the given statement, which may or may not
// be valid. Use is_valid() to check if it's OK.
void Assign(scoped_refptr<Database::StatementRef> ref);
// Resets the statement to an uninitialized state corresponding to
// the default constructor, releasing the StatementRef.
void Clear();
// Returns true if the statement can be executed. All functions can still
// be used if the statement is invalid, but they will return failure or some
// default value. This is because the statement can become invalid in the
// middle of executing a command if there is a serious error and the database
// has to be reset.
bool is_valid() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return ref_->is_valid();
}
// Running -------------------------------------------------------------------
// Executes the statement, returning true on success. This is like Step but
// for when there is no output, like an INSERT statement.
bool Run();
// Executes the statement, returning true if there is a row of data returned.
// You can keep calling Step() until it returns false to iterate through all
// the rows in your result set.
//
// When Step returns false, the result is either that there is no more data
// or there is an error. This makes it most convenient for loop usage. If you
// need to disambiguate these cases, use Succeeded().
//
// Typical example:
// while (s.Step()) {
// ...
// }
// return s.Succeeded();
bool Step();
// Resets the statement to its initial condition. This includes any current
// result row, and also the bound variables if the |clear_bound_vars| is true.
void Reset(bool clear_bound_vars);
// Returns true if the last executed thing in this statement succeeded. If
// there was no last executed thing or the statement is invalid, this will
// return false.
bool Succeeded() const;
// Binding -------------------------------------------------------------------
// These all take a 0-based parameter index and return true on success.
// strings there may be out of memory.
void BindNull(int param_index);
void BindBool(int param_index, bool val);
void BindInt(int param_index, int val);
void BindInt(int param_index,
int64_t val) = delete; // Call BindInt64() instead.
void BindInt64(int param_index, int64_t val);
void BindDouble(int param_index, double val);
void BindCString(int param_index, const char* val);
void BindString(int param_index, base::StringPiece val);
// If you need to store (potentially invalid) UTF-16 strings losslessly,
// store them as BLOBs instead. `BindBlob()` has an overload for this purpose.
void BindString16(int param_index, base::StringPiece16 value);
void BindBlob(int param_index, base::span<const uint8_t> value);
// Overload that makes it easy to pass in std::string values.
void BindBlob(int param_index, base::span<const char> value) {
BindBlob(param_index, base::as_bytes(base::make_span(value)));
}
// Overload that makes it easy to pass in std::u16string values.
void BindBlob(int param_index, base::span<const char16_t> value) {
BindBlob(param_index, base::as_bytes(base::make_span(value)));
}
// Conforms with base::Time serialization recommendations.
//
// This is equivalent to the following snippets, which should be replaced.
// * BindInt64(col, val.ToInternalValue())
// * BindInt64(col, val.ToDeltaSinceWindowsEpoch().InMicroseconds())
//
// Features that serialize base::Time in other ways, such as ToTimeT() or
// InMillisecondsSinceUnixEpoch(), will require a database migration to be
// converted to this (recommended) serialization method.
//
// TODO(crbug.com/1195962): Migrate all time serialization to this method, and
// then remove the migration details above.
void BindTime(int param_index, base::Time time);
// Conforms with base::TimeDelta serialization recommendations.
//
// This is equivalent to the following snippets, which should be replaced.
// * BindInt64(col, delta.ToInternalValue())
// * BindInt64(col, delta.InMicroseconds())
//
// TODO(crbug.com/1402777): Migrate all TimeDelta serialization to this method
// and remove the migration details above.
void BindTimeDelta(int param_index, base::TimeDelta delta);
// Retrieving ----------------------------------------------------------------
// Returns the number of output columns in the result.
int ColumnCount() const;
// Returns the type associated with the given column.
//
// Watch out: the type may be undefined if you've done something to cause a
// "type conversion." This means requesting the value of a column of a type
// where that type is not the native type. For safety, call ColumnType only
// on a column before getting the value out in any way.
ColumnType GetColumnType(int col);
// These all take a 0-based argument index.
bool ColumnBool(int column_index);
int ColumnInt(int column_index);
int64_t ColumnInt64(int column_index);
double ColumnDouble(int column_index);
std::string ColumnString(int column_index);
// If you need to store and retrieve (potentially invalid) UTF-16 strings
// losslessly, store them as BLOBs instead. They may be retrieved with
// `ColumnBlobAsString16()`.
std::u16string ColumnString16(int column_index);
// Conforms with base::Time serialization recommendations.
//
// This is equivalent to the following snippets, which should be replaced.
// * base::Time::FromInternalValue(ColumnInt64(col))
// * base::Time::FromDeltaSinceWindowsEpoch(
// base::Microseconds(ColumnInt64(col)))
//
// TODO(crbug.com/1195962): Migrate all time serialization to this method, and
// then remove the migration details above.
base::Time ColumnTime(int column_index);
// Conforms with base::TimeDelta deserialization recommendations.
//
// This is equivalent to the following snippets, which should be replaced.
// * base::TimeDelta::FromInternalValue(ColumnInt64(column_index))
//
// TODO(crbug.com/1402777): Migrate all TimeDelta serialization to this method
// and remove the migration details above.
base::TimeDelta ColumnTimeDelta(int column_index);
// Returns a span pointing to a buffer containing the blob data.
//
// The span's contents should be copied to a caller-owned buffer immediately.
// Any method call on the Statement may invalidate the span.
//
// The span will be empty (and may have a null data) if the underlying blob is
// empty. Code that needs to distinguish between empty blobs and NULL should
// call GetColumnType() before calling ColumnBlob().
base::span<const uint8_t> ColumnBlob(int column_index);
bool ColumnBlobAsString(int column_index, std::string* result);
bool ColumnBlobAsString16(int column_index, std::u16string* result);
bool ColumnBlobAsVector(int column_index, std::vector<char>* result);
bool ColumnBlobAsVector(int column_index, std::vector<uint8_t>* result);
// Diagnostics --------------------------------------------------------------
// Returns the original text of a SQL statement WITHOUT any bound values.
// Intended for logging in case of failures. Note that DOES NOT return any
// bound values, because that would cause a privacy / PII issue for logging.
std::string GetSQLStatement();
private:
friend class Database;
// Checks SQLite result codes and handles any errors.
//
// Returns `sqlite_result_code`. This gives callers the convenience of writing
// "return CheckSqliteResultCode(sqlite_result_code)" and gives the compiler
// the opportunity of doing tail call optimization (TCO) on the code above.
//
// This method reports error codes to the associated Database, and updates
// internal state to reflect whether the statement succeeded or not.
SqliteResultCode CheckSqliteResultCode(SqliteResultCode sqlite_result_code);
// Should be called by all mutating methods to check that the statement is
// valid. Returns true if the statement is valid. DCHECKS and returns false
// if it is not.
// The reason for this is to handle two specific cases in which a Statement
// may be invalid. The first case is that the programmer made an SQL error.
// Those cases need to be DCHECKed so that we are guaranteed to find them
// before release. The second case is that the computer has an error (probably
// out of disk space) which is prohibiting the correct operation of the
// database. Our testing apparatus should not exhibit this defect, but release
// situations may. Therefore, the code is handling disjoint situations in
// release and test. In test, we're ensuring correct SQL. In release, we're
// ensuring that contracts are honored in error edge cases.
bool CheckValid() const;
// Helper for Run() and Step(), calls sqlite3_step() and returns the checked
// value from it.
SqliteResultCode StepInternal();
// Retrieve and log the count of VM steps required to execute the query.
void ReportQueryExecutionMetrics() const;
// The actual sqlite statement. This may be unique to us, or it may be cached
// by the Database, which is why it's ref-counted. This pointer is
// guaranteed non-null.
scoped_refptr<Database::StatementRef> ref_
GUARDED_BY_CONTEXT(sequence_checker_);
// See Succeeded() for what this holds.
bool succeeded_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
#if DCHECK_IS_ON()
// Used to DCHECK() that Bind*() is called before Step() or Run() are called.
bool step_called_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
bool run_called_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
#endif // DCHECK_IS_ON()
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace sql
#endif // SQL_STATEMENT_H_