17
17
#include "utils/lsyscache.h"
18
18
#include "catalog/pg_class.h"
19
19
#include "catalog/pg_database.h"
20
+ #include "catalog/pg_inherits.h"
20
21
#include "commands/defrem.h"
21
22
#include "commands/sequence.h"
23
+ #include "access/heapam.h"
22
24
#include "access/table.h"
23
25
#include "access/relation.h"
24
26
#include "catalog/pg_event_trigger.h"
@@ -141,6 +143,97 @@ push_event_stack(const TdeDdlEvent *event)
141
143
MemoryContextSwitchTo (oldCtx );
142
144
}
143
145
146
+ static List *
147
+ find_typed_table_dependencies (Oid typeOid )
148
+ {
149
+ Relation classRel ;
150
+ ScanKeyData key [1 ];
151
+ TableScanDesc scan ;
152
+ HeapTuple tuple ;
153
+ List * result = NIL ;
154
+
155
+ classRel = table_open (RelationRelationId , AccessShareLock );
156
+
157
+ ScanKeyInit (& key [0 ],
158
+ Anum_pg_class_reloftype ,
159
+ BTEqualStrategyNumber , F_OIDEQ ,
160
+ ObjectIdGetDatum (typeOid ));
161
+
162
+ scan = table_beginscan_catalog (classRel , 1 , key );
163
+
164
+ while ((tuple = heap_getnext (scan , ForwardScanDirection )) != NULL )
165
+ {
166
+ Form_pg_class classform = (Form_pg_class ) GETSTRUCT (tuple );
167
+
168
+ LockRelationOid (classform -> oid , AccessShareLock );
169
+ result = lappend_oid (result , classform -> oid );
170
+ }
171
+
172
+ table_endscan (scan);
173
+ table_close (classRel , AccessShareLock );
174
+
175
+ return result ;
176
+ }
177
+
178
+ typedef enum
179
+ {
180
+ ENC_MIX_UNKNOWN ,
181
+ ENC_MIX_PLAIN ,
182
+ ENC_MIX_ENCRYPTED ,
183
+ ENC_MIX_MIXED ,
184
+ } EncryptionMix ;
185
+
186
+ /*
187
+ * Since ALTER TABLE can modify multiple tables due to inheritance or typed
188
+ * tables which can for example result in TOAST tables being created for some
189
+ * or all of the modified tables while the event trigger is only fired once we
190
+ * cannot rely on the event stack to make sure we get the correct encryption
191
+ * status.
192
+ *
193
+ * Our solution is to be cautious and only modify tables when all tables with
194
+ * storage are either encrypted or not encrypted. If there is a mix we will
195
+ * throw and error. The result of this is also used to properly inform the
196
+ * SMGR of the current encryption status.
197
+ */
198
+ static EncryptionMix
199
+ alter_table_encryption_mix (Oid relid )
200
+ {
201
+ EncryptionMix enc = ENC_MIX_UNKNOWN ;
202
+ Relation rel ;
203
+ List * children ;
204
+ ListCell * lc ;
205
+
206
+ rel = relation_open (relid , NoLock );
207
+
208
+ if (RELKIND_HAS_STORAGE (rel -> rd_rel -> relkind ))
209
+ enc = rel -> rd_rel -> relam == get_tde_table_am_oid () ? ENC_MIX_ENCRYPTED : ENC_MIX_PLAIN ;
210
+
211
+ if (rel -> rd_rel -> relkind == RELKIND_COMPOSITE_TYPE )
212
+ children = find_typed_table_dependencies (rel -> rd_rel -> reltype );
213
+ else
214
+ children = find_inheritance_children (relid , AccessShareLock );
215
+
216
+ relation_close (rel , NoLock );
217
+
218
+ foreach (lc , children )
219
+ {
220
+ Oid childid = lfirst_oid (lc );
221
+ EncryptionMix childenc ;
222
+
223
+ childenc = alter_table_encryption_mix (childid );
224
+
225
+ if (childenc != ENC_MIX_UNKNOWN )
226
+ {
227
+ if (enc == ENC_MIX_UNKNOWN )
228
+ enc = childenc ;
229
+ else if (enc != childenc )
230
+ return ENC_MIX_MIXED ;
231
+ }
232
+ }
233
+
234
+ return enc ;
235
+ }
236
+
144
237
/*
145
238
* pg_tde_ddl_command_start_capture is an event trigger function triggered
146
239
* at the start of any DDL command execution.
@@ -267,6 +360,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS)
267
360
AlterTableCmd * setAccessMethod = NULL ;
268
361
ListCell * lcmd ;
269
362
TdeDdlEvent event = {.parsetree = parsetree };
363
+ EncryptionMix encmix ;
270
364
271
365
foreach (lcmd , stmt -> cmds )
272
366
{
@@ -276,6 +370,20 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS)
276
370
setAccessMethod = cmd ;
277
371
}
278
372
373
+ encmix = alter_table_encryption_mix (relid );
374
+
375
+ /*
376
+ * This check is very braod and could be limited only to commands
377
+ * which recurse to child tables or to those which may create new
378
+ * relfilenodes, but this restrictive code is good enough for now.
379
+ */
380
+ if (encmix == ENC_MIX_MIXED )
381
+ {
382
+ ereport (ERROR ,
383
+ errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
384
+ errmsg ("Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported" ));
385
+ }
386
+
279
387
/*
280
388
* With a SET ACCESS METHOD clause, use that as the basis for
281
389
* decisions. But if it's not present, look up encryption status
@@ -292,19 +400,13 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS)
292
400
}
293
401
else
294
402
{
295
- Relation rel = relation_open (relid , AccessShareLock );
296
-
297
- if (rel -> rd_rel -> relam == get_tde_table_am_oid ())
403
+ if (encmix == ENC_MIX_ENCRYPTED )
298
404
{
299
- /*
300
- * We are altering an encrypted table and ALTER TABLE can
301
- * possibly create new files so set the global state.
302
- */
303
405
event .encryptMode = TDE_ENCRYPT_MODE_ENCRYPT ;
304
406
checkPrincipalKeyConfigured ();
305
407
}
306
-
307
- relation_close ( rel , NoLock ) ;
408
+ else if ( encmix == ENC_MIX_PLAIN )
409
+ event . encryptMode = TDE_ENCRYPT_MODE_PLAIN ;
308
410
}
309
411
310
412
push_event_stack (& event );
0 commit comments