• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.server.healthconnect.storage.request;
18 
19 import android.database.SQLException;
20 import android.util.Pair;
21 import android.util.Slog;
22 
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 
29 /**
30  * Creates a alter table request and alter statements for it.
31  *
32  * @hide
33  */
34 public final class AlterTableRequest {
35     public static final String TAG = "HealthConnectAlter";
36     private static final String NOT_NULL = "NOT NULL";
37     private static final String ALTER_TABLE_COMMAND = "ALTER TABLE ";
38     private static final String ADD_COLUMN_COMMAND = " ADD COLUMN ";
39     private final String mTableName;
40     private final List<Pair<String, String>> mColumnInfo;
41 
42     private final Map<String, Pair<String, String>> mForeignKeyConstraints = new HashMap<>();
43 
AlterTableRequest(String tableName, List<Pair<String, String>> columnInfo)44     public AlterTableRequest(String tableName, List<Pair<String, String>> columnInfo) {
45         mTableName = tableName;
46         mColumnInfo = columnInfo;
47     }
48 
49     /**
50      * Adds a foreign key constraint between one column and another. Deletion behavior is to set
51      * dangling references to null.
52      */
addForeignKeyConstraint( String column, String referencedTable, String referencedColumn)53     public AlterTableRequest addForeignKeyConstraint(
54             String column, String referencedTable, String referencedColumn) {
55         mForeignKeyConstraints.put(column, new Pair<>(referencedTable, referencedColumn));
56         return this;
57     }
58 
59     /** Returns a list of alter table SQL statements to add new columns */
getAlterTableAddColumnsCommands()60     public List<String> getAlterTableAddColumnsCommands() {
61         List<String> statements = new ArrayList<>();
62         for (int i = 0; i < mColumnInfo.size(); i++) {
63             StringBuilder statement = new StringBuilder(ALTER_TABLE_COMMAND);
64             statement.append(mTableName);
65             String columnName = mColumnInfo.get(i).first;
66             String columnType = mColumnInfo.get(i).second;
67             statement.append(ADD_COLUMN_COMMAND).append(columnName).append(" ").append(columnType);
68             if (mForeignKeyConstraints.containsKey(columnName)) {
69                 statement.append(" ");
70                 statement.append("REFERENCES ");
71                 statement.append(mForeignKeyConstraints.get(columnName).first);
72                 statement.append("(");
73                 statement.append(mForeignKeyConstraints.get(columnName).second);
74                 statement.append(")");
75                 statement.append(" ON DELETE SET NULL");
76             }
77             statement.append(";");
78             statements.add(statement.toString());
79         }
80         Slog.d(TAG, "Alter table: " + statements);
81 
82         // Check on the final commands for now as it's more broad. Should this become a problem
83         // later, we can change/move the check to narrow scope such as only check on the
84         // `columnInfo` list passed to the constructor
85         checkNoNotNullColumns(statements);
86         return statements;
87     }
88 
getAlterTableCommandToAddGeneratedColumn( String tableName, CreateTableRequest.GeneratedColumnInfo generatedColumnInfo)89     public static String getAlterTableCommandToAddGeneratedColumn(
90             String tableName, CreateTableRequest.GeneratedColumnInfo generatedColumnInfo) {
91         String request =
92                 ALTER_TABLE_COMMAND
93                         + tableName
94                         + ADD_COLUMN_COMMAND
95                         + generatedColumnInfo.getColumnName()
96                         + " "
97                         + generatedColumnInfo.getColumnType()
98                         + " GENERATED ALWAYS AS ("
99                         + generatedColumnInfo.getExpression()
100                         + ")";
101 
102         Slog.d(TAG, "Alter table generated: " + request);
103         return request;
104     }
105 
checkNoNotNullColumns(List<String> alterTableCommands)106     private static void checkNoNotNullColumns(List<String> alterTableCommands) {
107         for (String command : alterTableCommands) {
108             if (command == null) {
109                 continue;
110             }
111             String upperCased = command.toUpperCase(Locale.ROOT);
112             if (upperCased.contains(NOT_NULL)) {
113                 throw new SQLException(
114                         String.format(
115                                 "Alter table command \"%s\" must not contain \"%s\"",
116                                 upperCased, NOT_NULL));
117             }
118         }
119     }
120 }
121