| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sql/recover_module/parsing.h" |
| |
| #include <cstddef> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "base/optional.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_piece.h" |
| #include "sql/recover_module/record.h" |
| |
| namespace sql { |
| namespace recover { |
| |
| namespace { |
| |
| // This module defines whitespace as space (0x20). |
| constexpr bool IsWhiteSpace(char character) { |
| return (character == ' '); |
| } |
| |
| // Splits a token out of a SQL string representing a column type. |
| // |
| // Tokens are separated by space (0x20) characters. |
| // |
| // The SQL must not start with a space character. |
| // |
| // Returns the token and the rest of the SQL string. Consumes the space after |
| // the returned token -- the rest of the SQL string will not start with space. |
| std::pair<base::StringPiece, base::StringPiece> SplitToken( |
| base::StringPiece sql) { |
| DCHECK(sql.empty() || !IsWhiteSpace(sql[0])); |
| |
| size_t token_end = 0; |
| while (token_end < sql.length() && !IsWhiteSpace(sql[token_end])) |
| ++token_end; |
| |
| size_t split = token_end; |
| while (split < sql.length() && IsWhiteSpace(sql[split])) |
| ++split; |
| |
| return {sql.substr(0, token_end), sql.substr(split)}; |
| } |
| |
| // Column types. |
| constexpr base::StringPiece kIntegerSql("INTEGER"); |
| constexpr base::StringPiece kFloatSql("FLOAT"); |
| constexpr base::StringPiece kTextSql("TEXT"); |
| constexpr base::StringPiece kBlobSql("BLOB"); |
| constexpr base::StringPiece kNumericSql("NUMERIC"); |
| constexpr base::StringPiece kRowidSql("ROWID"); |
| constexpr base::StringPiece kAnySql("ANY"); |
| |
| // SQL keywords recognized by the recovery module. |
| constexpr base::StringPiece kStrictSql("STRICT"); |
| constexpr base::StringPiece kNonNullSql1("NOT"); |
| constexpr base::StringPiece kNonNullSql2("NULL"); |
| |
| base::Optional<ModuleColumnType> ParseColumnType( |
| base::StringPiece column_type_sql) { |
| if (column_type_sql == kIntegerSql) |
| return ModuleColumnType::kInteger; |
| if (column_type_sql == kFloatSql) |
| return ModuleColumnType::kFloat; |
| if (column_type_sql == kTextSql) |
| return ModuleColumnType::kText; |
| if (column_type_sql == kBlobSql) |
| return ModuleColumnType::kBlob; |
| if (column_type_sql == kNumericSql) |
| return ModuleColumnType::kNumeric; |
| if (column_type_sql == kRowidSql) |
| return ModuleColumnType::kRowId; |
| if (column_type_sql == kAnySql) |
| return ModuleColumnType::kAny; |
| |
| return base::nullopt; |
| } |
| |
| // Returns a view into a SQL string representing the column type. |
| // |
| // The backing string is guaranteed to live for as long as the process runs. |
| base::StringPiece ColumnTypeToSql(ModuleColumnType column_type) { |
| switch (column_type) { |
| case ModuleColumnType::kInteger: |
| return kIntegerSql; |
| case ModuleColumnType::kFloat: |
| return kFloatSql; |
| case ModuleColumnType::kText: |
| return kTextSql; |
| case ModuleColumnType::kBlob: |
| return kBlobSql; |
| case ModuleColumnType::kNumeric: |
| return kNumericSql; |
| case ModuleColumnType::kRowId: |
| return kIntegerSql; // rowids are ints. |
| case ModuleColumnType::kAny: |
| return base::StringPiece(); |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| std::string RecoveredColumnSpec::ToCreateTableSql() const { |
| base::StringPiece not_null_sql = (is_non_null) ? " NOT NULL" : ""; |
| return base::StrCat( |
| {this->name, " ", ColumnTypeToSql(this->type), not_null_sql}); |
| } |
| |
| bool RecoveredColumnSpec::IsAcceptableValue(ValueType value_type) const { |
| if (value_type == ValueType::kNull) |
| return !is_non_null || type == ModuleColumnType::kRowId; |
| if (type == ModuleColumnType::kAny) |
| return true; |
| |
| if (value_type == ValueType::kInteger) { |
| return type == ModuleColumnType::kInteger || |
| (!is_strict && type == ModuleColumnType::kFloat); |
| } |
| if (value_type == ValueType::kFloat) { |
| return type == ModuleColumnType::kFloat || |
| (!is_strict && type == ModuleColumnType::kFloat); |
| } |
| if (value_type == ValueType::kText) |
| return type == ModuleColumnType::kText; |
| if (value_type == ValueType::kBlob) { |
| return type == ModuleColumnType::kBlob || |
| (!is_strict && type == ModuleColumnType::kText); |
| } |
| NOTREACHED() << "Unimplemented value type"; |
| return false; |
| } |
| |
| RecoveredColumnSpec ParseColumnSpec(const char* sqlite_arg) { |
| // The result is invalid until its |name| member is set. |
| RecoveredColumnSpec result; |
| base::StringPiece sql(sqlite_arg); |
| |
| base::StringPiece column_name; |
| std::tie(column_name, sql) = SplitToken(sql); |
| if (column_name.empty()) { |
| // Empty column names are invalid. |
| DCHECK(!result.IsValid()); |
| return result; |
| } |
| |
| base::StringPiece column_type_sql; |
| std::tie(column_type_sql, sql) = SplitToken(sql); |
| base::Optional<ModuleColumnType> column_type = |
| ParseColumnType(column_type_sql); |
| if (!column_type.has_value()) { |
| // Invalid column type. |
| DCHECK(!result.IsValid()); |
| return result; |
| } |
| result.type = column_type.value(); |
| |
| // Consume keywords. |
| result.is_non_null = result.type == ModuleColumnType::kRowId; |
| while (!sql.empty()) { |
| base::StringPiece token; |
| std::tie(token, sql) = SplitToken(sql); |
| |
| if (token == kStrictSql) { |
| if (result.type == ModuleColumnType::kAny) { |
| // STRICT is incompatible with ANY. |
| DCHECK(!result.IsValid()); |
| return result; |
| } |
| |
| result.is_strict = true; |
| continue; |
| } |
| |
| if (token != kNonNullSql1) { |
| // Invalid SQL keyword. |
| DCHECK(!result.IsValid()); |
| return result; |
| } |
| std::tie(token, sql) = SplitToken(sql); |
| if (token != kNonNullSql2) { |
| // Invalid SQL keyword. |
| DCHECK(!result.IsValid()); |
| return result; |
| } |
| result.is_non_null = true; |
| } |
| |
| result.name = column_name.as_string(); |
| return result; |
| } |
| |
| TargetTableSpec ParseTableSpec(const char* sqlite_arg) { |
| base::StringPiece sql(sqlite_arg); |
| |
| size_t separator_pos = sql.find('.'); |
| if (separator_pos == base::StringPiece::npos) { |
| // The default attachment point name is "main". |
| return TargetTableSpec{"main", sqlite_arg}; |
| } |
| |
| if (separator_pos == 0) { |
| // Empty attachment point names are invalid. |
| return TargetTableSpec(); |
| } |
| |
| base::StringPiece db_name = sql.substr(0, separator_pos); |
| base::StringPiece table_name = sql.substr(separator_pos + 1); |
| return TargetTableSpec{db_name.as_string(), table_name.as_string()}; |
| } |
| |
| } // namespace recover |
| } // namespace sql |