2
2
#include " pgduckdb/pgduckdb_utils.hpp"
3
3
#include " pgduckdb/pgduckdb_xact.hpp"
4
4
#include " pgduckdb/pgduckdb_guc.h"
5
+ #include " pgduckdb/pgduckdb_ddl.hpp"
5
6
6
7
extern " C" {
7
8
#include " postgres.h"
@@ -32,6 +33,7 @@ extern "C" {
32
33
#include " pgduckdb/pgduckdb_ruleutils.h"
33
34
}
34
35
36
+ #include " pgduckdb/pgduckdb_guc.h"
35
37
#include " pgduckdb/utility/cpp_wrapper.hpp"
36
38
#include " pgduckdb/pgduckdb_duckdb.hpp"
37
39
#include " pgduckdb/pgduckdb_background_worker.hpp"
@@ -40,6 +42,10 @@ extern "C" {
40
42
#include " pgduckdb/vendor/pg_list.hpp"
41
43
#include < inttypes.h>
42
44
45
+ namespace pgduckdb {
46
+ DDLType top_level_duckdb_ddl_type = DDLType::NONE;
47
+ } // namespace pgduckdb
48
+
43
49
/*
44
50
* ctas_skip_data stores the original value of the skipData field of the
45
51
* CreateTableAsStmt of the query that's currently being executed. For duckdb
@@ -181,6 +187,11 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
181
187
char *access_method = stmt->accessMethod ? stmt->accessMethod : default_table_access_method;
182
188
bool is_duckdb_table = strcmp (access_method, " duckdb" ) == 0 ;
183
189
if (is_duckdb_table) {
190
+ if (pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::NONE) {
191
+ ereport (ERROR, (errcode (ERRCODE_INVALID_TABLE_DEFINITION),
192
+ errmsg (" Only one DuckDB table can be created in a single statement" )));
193
+ }
194
+ pgduckdb::top_level_duckdb_ddl_type = pgduckdb::DDLType::CREATE_TABLE;
184
195
pgduckdb::ClaimCurrentCommandId ();
185
196
}
186
197
@@ -202,6 +213,11 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
202
213
char *access_method = stmt->into ->accessMethod ? stmt->into ->accessMethod : default_table_access_method;
203
214
bool is_duckdb_table = strcmp (access_method, " duckdb" ) == 0 ;
204
215
if (is_duckdb_table) {
216
+ if (pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::NONE) {
217
+ ereport (ERROR, (errcode (ERRCODE_INVALID_TABLE_DEFINITION),
218
+ errmsg (" Only one DuckDB table can be created in a single statement" )));
219
+ }
220
+ pgduckdb::top_level_duckdb_ddl_type = pgduckdb::DDLType::CREATE_TABLE;
205
221
pgduckdb::ClaimCurrentCommandId ();
206
222
/*
207
223
* Force skipData to false for duckdb tables, so that Postgres does
@@ -313,6 +329,21 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
313
329
break ;
314
330
}
315
331
return ;
332
+ } else if (IsA (parsetree, AlterTableStmt)) {
333
+ auto stmt = castNode (AlterTableStmt, parsetree);
334
+ Oid relation_oid = RangeVarGetRelid (stmt->relation , AccessShareLock, false );
335
+ Relation relation = RelationIdGetRelation (relation_oid);
336
+ /*
337
+ * Certain CREATE TABLE commands also trigger an ALTER TABLE command,
338
+ * specifically if you use REFERENCES it will alter the table
339
+ * afterwards. We currently only do this to get a better error message,
340
+ * because we don't support REFERENCES anyway.
341
+ */
342
+ if (pgduckdb::IsDuckdbTable (relation ) && pgduckdb::top_level_duckdb_ddl_type == pgduckdb::DDLType::NONE) {
343
+ pgduckdb::top_level_duckdb_ddl_type = pgduckdb::DDLType::ALTER_TABLE;
344
+ pgduckdb::ClaimCurrentCommandId ();
345
+ }
346
+ RelationClose (relation );
316
347
}
317
348
}
318
349
@@ -416,6 +447,15 @@ DECLARE_PG_FUNCTION(duckdb_create_table_trigger) {
416
447
PG_RETURN_NULL ();
417
448
}
418
449
450
+ if (pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::CREATE_TABLE &&
451
+ pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::NONE) {
452
+ ereport (ERROR, (errcode (ERRCODE_INVALID_TABLE_DEFINITION),
453
+ errmsg (" Cannot create a DuckDB table this way, use CREATE TABLE or CREATE TABLE ... AS" )));
454
+ PG_RETURN_NULL ();
455
+ }
456
+ /* Reset it back to NONE, for the remainder of the event trigger */
457
+ pgduckdb::top_level_duckdb_ddl_type = pgduckdb::DDLType::NONE;
458
+
419
459
EventTriggerData *trigger_data = (EventTriggerData *)fcinfo->context ;
420
460
Node *parsetree = trigger_data->parsetree ;
421
461
@@ -859,6 +899,16 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
859
899
PG_RETURN_NULL ();
860
900
}
861
901
902
+ /* Reset since we don't need it anymore */
903
+ if (pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::ALTER_TABLE &&
904
+ pgduckdb::top_level_duckdb_ddl_type != pgduckdb::DDLType::NONE) {
905
+ ereport (ERROR, (errcode (ERRCODE_INVALID_TABLE_DEFINITION),
906
+ errmsg (" Cannot ALTER a DuckDB table this way, please use ALTER TABLE" )));
907
+ PG_RETURN_NULL ();
908
+ }
909
+ /* Reset it back to NONE, for the remainder of the event trigger */
910
+ pgduckdb::top_level_duckdb_ddl_type = pgduckdb::DDLType::NONE;
911
+
862
912
SPI_connect ();
863
913
864
914
/*
@@ -889,20 +939,20 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
889
939
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
890
940
JOIN pg_catalog.pg_class
891
941
ON cmds.objid = pg_class.oid
892
- WHERE cmds.object_type = 'table'
942
+ WHERE cmds.object_type in ( 'table', 'table column')
893
943
AND pg_class.relam = (SELECT oid FROM pg_am WHERE amname = 'duckdb')
894
944
UNION ALL
895
945
SELECT objid as relid, false AS needs_to_check_temporary_set
896
946
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
897
947
JOIN duckdb.tables AS ddbtables
898
948
ON cmds.objid = ddbtables.relid
899
- WHERE cmds.object_type = 'table'
949
+ WHERE cmds.object_type in ( 'table', 'table column')
900
950
UNION ALL
901
951
SELECT objid as relid, true AS needs_to_check_temporary_set
902
952
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
903
953
JOIN pg_catalog.pg_class
904
954
ON cmds.objid = pg_class.oid
905
- WHERE cmds.object_type = 'table'
955
+ WHERE cmds.object_type in ( 'table', 'table column')
906
956
AND pg_class.relam != (SELECT oid FROM pg_am WHERE amname = 'duckdb')
907
957
AND pg_class.relpersistence = 't'
908
958
)" ,
@@ -946,7 +996,29 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
946
996
}
947
997
}
948
998
949
- elog (ERROR, " DuckDB does not support ALTER TABLE yet" );
999
+ /* Forcibly allow whatever writes Postgres did for this command */
1000
+ pgduckdb::ClaimCurrentCommandId (true );
1001
+
1002
+ /* We're going to run multiple queries in DuckDB, so we need to start a
1003
+ * transaction to ensure ACID guarantees hold. */
1004
+ auto connection = pgduckdb::DuckDBManager::GetConnection (true );
1005
+
1006
+ EventTriggerData *trigdata = (EventTriggerData *)fcinfo->context ;
1007
+ char *alter_table_stmt_string;
1008
+ if (IsA (trigdata->parsetree , AlterTableStmt)) {
1009
+ AlterTableStmt *alter_table_stmt = (AlterTableStmt *)trigdata->parsetree ;
1010
+ alter_table_stmt_string = pgduckdb_get_alter_tabledef (relid, alter_table_stmt);
1011
+ } else if (IsA (trigdata->parsetree , RenameStmt)) {
1012
+ RenameStmt *rename_stmt = (RenameStmt *)trigdata->parsetree ;
1013
+ alter_table_stmt_string = pgduckdb_get_rename_tabledef (relid, rename_stmt);
1014
+ } else {
1015
+ elog (ERROR, " Unexpected parsetree type: %d" , nodeTag (trigdata->parsetree ));
1016
+ }
1017
+
1018
+ elog (DEBUG1, " Executing: %s" , alter_table_stmt_string);
1019
+ auto res = pgduckdb::DuckDBQueryOrThrow (*connection, alter_table_stmt_string);
1020
+
1021
+ PG_RETURN_NULL ();
950
1022
}
951
1023
952
1024
/*
0 commit comments