/Users/deen/code/yugabyte-db/src/yb/tserver/pg_create_table.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include "yb/tserver/pg_create_table.h" |
15 | | |
16 | | #include "yb/client/client.h" |
17 | | #include "yb/client/table.h" |
18 | | #include "yb/client/table_creator.h" |
19 | | |
20 | | #include "yb/common/pg_system_attr.h" |
21 | | #include "yb/common/ql_type.h" |
22 | | #include "yb/common/schema.h" |
23 | | |
24 | | #include "yb/docdb/doc_key.h" |
25 | | #include "yb/docdb/value_type.h" |
26 | | |
27 | | #include "yb/gutil/casts.h" |
28 | | |
29 | | #include "yb/tserver/pg_client.pb.h" |
30 | | |
31 | | #include "yb/util/result.h" |
32 | | #include "yb/util/status_format.h" |
33 | | |
34 | | namespace yb { |
35 | | namespace tserver { |
36 | | |
37 | | namespace { |
38 | | |
39 | | //-------------------------------------------------------------------------------------------------- |
40 | | // Constants used for the sequences data table. |
41 | | //-------------------------------------------------------------------------------------------------- |
42 | | static constexpr const char* const kPgSequencesNamespaceName = "system_postgres"; |
43 | | static constexpr const char* const kPgSequencesDataTableName = "sequences_data"; |
44 | | |
45 | | // Columns names and ids. |
46 | | static constexpr const char* const kPgSequenceDbOidColName = "db_oid"; |
47 | | |
48 | | static constexpr const char* const kPgSequenceSeqOidColName = "seq_oid"; |
49 | | |
50 | | static constexpr const char* const kPgSequenceLastValueColName = "last_value"; |
51 | | |
52 | | static constexpr const char* const kPgSequenceIsCalledColName = "is_called"; |
53 | | |
54 | | } // namespace |
55 | | |
56 | 5.05k | PgCreateTable::PgCreateTable(const PgCreateTableRequestPB& req) : req_(req) { |
57 | 5.05k | } |
58 | | |
59 | 5.05k | Status PgCreateTable::Prepare() { |
60 | 5.05k | table_name_ = client::YBTableName( |
61 | 5.05k | YQL_DATABASE_PGSQL, GetPgsqlNamespaceId(req_.table_id().database_oid()), |
62 | 5.05k | req_.database_name(), req_.table_name()); |
63 | 5.05k | indexed_table_id_ = PgObjectId::FromPB(req_.base_table_id()); |
64 | | |
65 | 13.9k | for (const auto& create_column : req_.create_columns()) { |
66 | 13.9k | RETURN_NOT_OK(AddColumn(create_column)); |
67 | 13.9k | } |
68 | | |
69 | 5.05k | EnsureYBbasectidColumnCreated(); |
70 | | |
71 | 5.05k | if (!req_.split_bounds().empty()) { |
72 | 85 | if (hash_schema_.is_initialized()) { |
73 | 1 | return STATUS(InvalidArgument, |
74 | 1 | "SPLIT AT option is not yet supported for hash partitioned tables"); |
75 | 1 | } |
76 | 85 | } |
77 | | |
78 | 5.05k | return Status::OK(); |
79 | 5.05k | } |
80 | | |
81 | | Status PgCreateTable::Exec( |
82 | | client::YBClient* client, const TransactionMetadata* transaction_metadata, |
83 | 5.05k | CoarseTimePoint deadline) { |
84 | | // Construct schema. |
85 | 5.05k | client::YBSchema schema; |
86 | | |
87 | 5.05k | const char* pg_txn_enabled_env_var = getenv("YB_PG_TRANSACTIONS_ENABLED"); |
88 | 5.05k | const bool transactional = |
89 | 5.05k | !pg_txn_enabled_env_var || strcmp(pg_txn_enabled_env_var, "1") == 00 ; |
90 | 5.05k | LOG(INFO) << Format( |
91 | 5.05k | "PgCreateTable: creating a $0 $1: $2/$3", |
92 | 5.05k | transactional ? "transactional" : "non-transactional"0 , |
93 | 5.05k | indexed_table_id_.IsValid() ? "index"862 : "table"4.19k , |
94 | 5.05k | table_name_, PgObjectId::FromPB(req_.table_id())); |
95 | 5.05k | TableProperties table_properties; |
96 | 5.05k | bool set_table_properties = false; |
97 | 5.05k | if (transactional) { |
98 | 5.05k | table_properties.SetTransactional(true); |
99 | 5.05k | set_table_properties = true; |
100 | 5.05k | } |
101 | 5.05k | if (req_.num_tablets() > 0) { |
102 | 352 | table_properties.SetNumTablets(req_.num_tablets()); |
103 | 352 | set_table_properties = true; |
104 | 352 | } |
105 | 5.05k | if (set_table_properties) { |
106 | 5.05k | schema_builder_.SetTableProperties(table_properties); |
107 | 5.05k | } |
108 | 5.05k | if (!req_.schema_name().empty()) { |
109 | 5.05k | schema_builder_.SetSchemaName(req_.schema_name()); |
110 | 5.05k | } |
111 | | |
112 | 5.05k | RETURN_NOT_OK(schema_builder_.Build(&schema)); |
113 | 5.05k | const auto split_rows = VERIFY_RESULT5.05k (BuildSplitRows(schema));5.05k |
114 | | |
115 | | // Create table. |
116 | 0 | auto table_creator = client->NewTableCreator(); |
117 | 5.05k | table_creator->table_name(table_name_).table_type(client::YBTableType::PGSQL_TABLE_TYPE) |
118 | 5.05k | .table_id(PgObjectId::GetYbTableIdFromPB(req_.table_id())) |
119 | 5.05k | .schema(&schema) |
120 | 5.05k | .colocated(req_.colocated()); |
121 | 5.05k | if (req_.is_pg_catalog_table()) { |
122 | 313 | table_creator->is_pg_catalog_table(); |
123 | 313 | } |
124 | 5.05k | if (req_.is_shared_table()) { |
125 | 50 | table_creator->is_pg_shared_table(); |
126 | 50 | } |
127 | 5.05k | if (hash_schema_) { |
128 | 4.32k | table_creator->hash_schema(*hash_schema_); |
129 | 4.32k | } else if (728 !req_.is_pg_catalog_table()728 ) { |
130 | 415 | table_creator->set_range_partition_columns(range_columns_, split_rows); |
131 | 415 | } |
132 | | |
133 | 5.05k | auto tablegroup_oid = PgObjectId::FromPB(req_.tablegroup_oid()); |
134 | 5.05k | if (tablegroup_oid.IsValid()) { |
135 | 89 | table_creator->tablegroup_id(tablegroup_oid.GetYbTablegroupId()); |
136 | 89 | } |
137 | | |
138 | 5.05k | if (req_.optional_colocation_id_case() != |
139 | 5.05k | PgCreateTableRequestPB::OptionalColocationIdCase::OPTIONAL_COLOCATION_ID_NOT_SET) { |
140 | 24 | table_creator->colocation_id(req_.colocation_id()); |
141 | 24 | } |
142 | | |
143 | 5.05k | auto tablespace_oid = PgObjectId::FromPB(req_.tablespace_oid()); |
144 | 5.05k | if (tablespace_oid.IsValid()) { |
145 | 114 | table_creator->tablespace_id(tablespace_oid.GetYbTablespaceId()); |
146 | 114 | } |
147 | | |
148 | 5.05k | auto matview_pg_table_oid = PgObjectId::FromPB(req_.matview_pg_table_oid()); |
149 | 5.05k | if (matview_pg_table_oid.IsValid()) { |
150 | 24 | table_creator->matview_pg_table_id(matview_pg_table_oid.GetYbTableId()); |
151 | 24 | } |
152 | | |
153 | | // For index, set indexed (base) table id. |
154 | 5.05k | if (indexed_table_id_.IsValid()) { |
155 | 862 | table_creator->indexed_table_id(indexed_table_id_.GetYbTableId()); |
156 | 862 | if (req_.is_unique_index()) { |
157 | 365 | table_creator->is_unique_index(true); |
158 | 365 | } |
159 | 862 | if (req_.skip_index_backfill()) { |
160 | 307 | table_creator->skip_index_backfill(true); |
161 | 307 | } |
162 | 862 | } |
163 | | |
164 | 5.05k | if (transaction_metadata) { |
165 | 4.78k | table_creator->part_of_transaction(transaction_metadata); |
166 | 4.78k | } |
167 | | |
168 | 5.05k | table_creator->timeout(deadline - CoarseMonoClock::now()); |
169 | | |
170 | 5.05k | const Status s = table_creator->Create(); |
171 | 5.05k | if (PREDICT_FALSE(!s.ok())) { |
172 | 20 | if (s.IsAlreadyPresent()) { |
173 | 0 | if (req_.if_not_exist()) { |
174 | 0 | return Status::OK(); |
175 | 0 | } |
176 | 0 | return STATUS(InvalidArgument, "Duplicate table"); |
177 | 0 | } |
178 | 20 | if (s.IsNotFound()) { |
179 | 0 | return STATUS(InvalidArgument, "Database not found", table_name_.namespace_name()); |
180 | 0 | } |
181 | 20 | return STATUS_FORMAT( |
182 | 20 | InvalidArgument, "Invalid table definition: $0", |
183 | 20 | s.ToString(false /* include_file_and_line */, false /* include_code */)); |
184 | 20 | } |
185 | | |
186 | 5.03k | return Status::OK(); |
187 | 5.05k | } |
188 | | |
189 | 13.9k | Status PgCreateTable::AddColumn(const PgCreateColumnPB& req) { |
190 | 13.9k | auto yb_type = QLType::Create(static_cast<DataType>(req.attr_ybtype())); |
191 | 13.9k | if (!req.is_hash() && !req.is_range()9.57k ) { |
192 | 8.20k | EnsureYBbasectidColumnCreated(); |
193 | 8.20k | } |
194 | 13.9k | client::YBColumnSpec* col = schema_builder_.AddColumn(req.attr_name())->Type(yb_type); |
195 | 13.9k | col->Order(req.attr_num()); |
196 | 13.9k | auto sorting_type = static_cast<SortingType>(req.sorting_type()); |
197 | 13.9k | if (req.is_hash()) { |
198 | 4.37k | if (!range_columns_.empty()) { |
199 | 0 | return STATUS(InvalidArgument, "Hash column not allowed after an ASC/DESC column"); |
200 | 0 | } |
201 | 4.37k | if (sorting_type != SortingType::kNotSpecified) { |
202 | 0 | return STATUS(InvalidArgument, "Hash column can't have sorting order"); |
203 | 0 | } |
204 | 4.37k | col->HashPrimaryKey(); |
205 | 4.37k | hash_schema_ = YBHashSchema::kPgsqlHash; |
206 | 9.57k | } else if (req.is_range()) { |
207 | 1.37k | col->PrimaryKey(); |
208 | 1.37k | range_columns_.emplace_back(req.attr_name()); |
209 | 1.37k | } |
210 | 13.9k | col->SetSortingType(sorting_type); |
211 | 13.9k | col->PgTypeOid(req.attr_pgoid()); |
212 | 13.9k | return Status::OK(); |
213 | 13.9k | } |
214 | | |
215 | 13.2k | void PgCreateTable::EnsureYBbasectidColumnCreated() { |
216 | 13.2k | if (ybbasectid_added_ || !indexed_table_id_.IsValid()13.2k ) { |
217 | 12.4k | return; |
218 | 12.4k | } |
219 | | |
220 | 862 | auto yb_type = QLType::Create(DataType::BINARY); |
221 | | |
222 | | // Add YBUniqueIdxKeySuffix column to store key suffix for handling multiple NULL values in |
223 | | // column with unique index. |
224 | | // Value of this column is set to ybctid (same as ybbasectid) for index row in case index |
225 | | // is unique and at least one of its key column is NULL. |
226 | | // In all other case value of this column is NULL. |
227 | 862 | if (req_.is_unique_index()) { |
228 | 365 | auto name = "ybuniqueidxkeysuffix"; |
229 | 365 | client::YBColumnSpec* col = schema_builder_.AddColumn(name)->Type(yb_type); |
230 | 365 | col->Order(to_underlying(PgSystemAttrNum::kYBUniqueIdxKeySuffix)); |
231 | 365 | col->PrimaryKey(); |
232 | 365 | range_columns_.emplace_back(name); |
233 | 365 | } |
234 | | |
235 | | // Add ybbasectid column to store the ybctid of the rows in the indexed table. It should be |
236 | | // added at the end of the primary key of the index, i.e. either before any non-primary-key |
237 | | // column if any or before exec() below. |
238 | 862 | auto name = "ybidxbasectid"; |
239 | 862 | client::YBColumnSpec* col = schema_builder_.AddColumn(name)->Type(yb_type); |
240 | 862 | col->Order(to_underlying(PgSystemAttrNum::kYBIdxBaseTupleId)); |
241 | 862 | if (!req_.is_unique_index()) { |
242 | 497 | col->PrimaryKey(); |
243 | 497 | range_columns_.emplace_back(name); |
244 | 497 | } |
245 | | |
246 | 862 | ybbasectid_added_ = true; |
247 | 862 | } |
248 | | |
249 | 5.05k | Result<std::vector<std::string>> PgCreateTable::BuildSplitRows(const client::YBSchema& schema) { |
250 | 5.05k | std::vector<std::string> rows; |
251 | 5.05k | rows.reserve(req_.split_bounds().size()); |
252 | 5.05k | docdb::DocKey prev_doc_key; |
253 | 5.05k | for (const auto& bounds : req_.split_bounds()) { |
254 | 175 | const auto& row = bounds.values(); |
255 | 175 | SCHECK_EQ( |
256 | 175 | implicit_cast<size_t>(row.size()), |
257 | 175 | PrimaryKeyRangeColumnCount() - (ybbasectid_added_ ? 1 : 0), |
258 | 175 | IllegalState, |
259 | 175 | "Number of split row values must be equal to number of primary key columns"); |
260 | 175 | std::vector<docdb::PrimitiveValue> range_components; |
261 | 175 | range_components.reserve(row.size()); |
262 | 175 | bool compare_columns = true; |
263 | 272 | for (const auto& row_value : row) { |
264 | 272 | const auto column_index = range_components.size(); |
265 | 272 | range_components.push_back(row_value.value_case() == QLValuePB::VALUE_NOT_SET |
266 | 272 | ? docdb::PrimitiveValue(docdb::ValueType::kLowest)0 |
267 | 272 | : docdb::PrimitiveValue::FromQLValuePB( |
268 | 272 | row_value, |
269 | 272 | schema.Column(schema.FindColumn(range_columns_[column_index])).sorting_type())); |
270 | | |
271 | | // Validate that split rows honor column ordering. |
272 | 272 | if (compare_columns && !prev_doc_key.empty()227 ) { |
273 | 112 | const auto& prev_value = prev_doc_key.range_group()[column_index]; |
274 | 112 | const auto compare = prev_value.CompareTo(range_components.back()); |
275 | 112 | if (compare > 0) { |
276 | 5 | return STATUS(InvalidArgument, "Split rows ordering does not match column ordering"); |
277 | 107 | } else if (compare < 0) { |
278 | | // Don't need to compare further columns |
279 | 85 | compare_columns = false; |
280 | 85 | } |
281 | 112 | } |
282 | 272 | } |
283 | 170 | prev_doc_key = docdb::DocKey(std::move(range_components)); |
284 | 170 | const auto keybytes = prev_doc_key.Encode(); |
285 | | |
286 | | // Validate that there are no duplicate split rows. |
287 | 170 | if (rows.size() > 0 && keybytes.AsSlice() == Slice(rows.back())86 ) { |
288 | 1 | return STATUS(InvalidArgument, "Cannot have duplicate split rows"); |
289 | 1 | } |
290 | 169 | rows.push_back(keybytes.ToStringBuffer()); |
291 | 169 | } |
292 | 5.05k | return rows; |
293 | 5.05k | } |
294 | | |
295 | 175 | size_t PgCreateTable::PrimaryKeyRangeColumnCount() const { |
296 | 175 | return range_columns_.size(); |
297 | 175 | } |
298 | | |
299 | 92 | Status CreateSequencesDataTable(client::YBClient* client, CoarseTimePoint deadline) { |
300 | 92 | const client::YBTableName table_name(YQL_DATABASE_PGSQL, |
301 | 92 | kPgSequencesDataNamespaceId, |
302 | 92 | kPgSequencesNamespaceName, |
303 | 92 | kPgSequencesDataTableName); |
304 | 92 | RETURN_NOT_OK(client->CreateNamespaceIfNotExists(kPgSequencesNamespaceName, |
305 | 92 | YQLDatabase::YQL_DATABASE_PGSQL, |
306 | 92 | "" /* creator_role_name */, |
307 | 92 | kPgSequencesDataNamespaceId)); |
308 | | |
309 | | // Set up the schema. |
310 | 92 | client::YBSchemaBuilder schemaBuilder; |
311 | 92 | schemaBuilder.AddColumn(kPgSequenceDbOidColName)->HashPrimaryKey()->Type(yb::INT64)->NotNull(); |
312 | 92 | schemaBuilder.AddColumn(kPgSequenceSeqOidColName)->HashPrimaryKey()->Type(yb::INT64)->NotNull(); |
313 | 92 | schemaBuilder.AddColumn(kPgSequenceLastValueColName)->Type(yb::INT64)->NotNull(); |
314 | 92 | schemaBuilder.AddColumn(kPgSequenceIsCalledColName)->Type(yb::BOOL)->NotNull(); |
315 | 92 | client::YBSchema schema; |
316 | 92 | CHECK_OK(schemaBuilder.Build(&schema)); |
317 | | |
318 | | // Generate the table id. |
319 | 92 | PgObjectId oid(kPgSequencesDataDatabaseOid, kPgSequencesDataTableOid); |
320 | | |
321 | | // Try to create the table. |
322 | 92 | auto table_creator(client->NewTableCreator()); |
323 | | |
324 | 92 | auto status = table_creator->table_name(table_name) |
325 | 92 | .schema(&schema) |
326 | 92 | .table_type(client::YBTableType::PGSQL_TABLE_TYPE) |
327 | 92 | .table_id(oid.GetYbTableId()) |
328 | 92 | .hash_schema(YBHashSchema::kPgsqlHash) |
329 | 92 | .timeout(deadline - CoarseMonoClock::now()) |
330 | 92 | .Create(); |
331 | | // If we could create it, then all good! |
332 | 92 | if (status.ok()) { |
333 | 92 | LOG(INFO) << "Table '" << table_name.ToString() << "' created."; |
334 | | // If the table was already there, also not an error... |
335 | 92 | } else if (0 status.IsAlreadyPresent()0 ) { |
336 | 0 | LOG(INFO) << "Table '" << table_name.ToString() << "' already exists"; |
337 | 0 | } else { |
338 | | // If any other error, report that! |
339 | 0 | LOG(ERROR) << "Error creating table '" << table_name.ToString() << "': " << status; |
340 | 0 | return status; |
341 | 0 | } |
342 | 92 | return Status::OK(); |
343 | 92 | } |
344 | | |
345 | | } // namespace tserver |
346 | | } // namespace yb |