Skip to content

Commit c4a86c2

Browse files
JelteFmkaruza
andauthored
Add ALTER TABLE support for DuckDB tables (duckdb#652)
Adds all ALTER TABLE commands that DuckDB can parse except for COLLATION changes and foreign keys. Not all of these ALTER commands are actually supported by DuckDB (e.g. adding CHECK constraints). But this starts to least forward those queries correctly to DuckDB, so once DuckDB supports it pg_duckdb will start to automatically support it as well. Fixes duckdb#536 Fixes duckdb#537 Fixes duckdb#538 Fixes duckdb#539 Fixes duckdb#540 Fixes duckdb#541 Fixes duckdb#542 --------- Co-authored-by: mkaruza <mkaruza@users.noreply.github.com>
1 parent d810d75 commit c4a86c2

15 files changed

+921
-22
lines changed

include/pgduckdb/pgduckdb_ddl.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@
22

33
#include "pgduckdb/pg/declarations.hpp"
44

5+
namespace pgduckdb {
6+
enum class DDLType { NONE, CREATE_TABLE, ALTER_TABLE };
7+
/* Tracks the type of DDL statement that is currently being executed */
8+
extern DDLType top_level_duckdb_ddl_type;
9+
} // namespace pgduckdb
10+
511
void DuckdbTruncateTable(Oid relation_oid);
612
void DuckdbInitUtilityHook();

include/pgduckdb/pgduckdb_ruleutils.h

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ char *pgduckdb_relation_name(Oid relid);
1212
char *pgduckdb_function_name(Oid function_oid, bool *use_variadic_p);
1313
char *pgduckdb_get_querydef(Query *);
1414
char *pgduckdb_get_tabledef(Oid relation_id);
15+
char *pgduckdb_get_alter_tabledef(Oid relation_oid, AlterTableStmt *alter_stmt);
16+
char *pgduckdb_get_rename_tabledef(Oid relation_oid, RenameStmt *rename_stmt);
1517
bool pgduckdb_is_not_default_expr(Node *node, void *context);
1618
List *pgduckdb_db_and_schema(const char *postgres_schema_name, bool is_duckdb_table);
1719
const char *pgduckdb_db_and_schema_string(const char *postgres_schema_name, bool is_duckdb_table);

src/pgduckdb.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extern "C" {
1010
#include "pgduckdb/pgduckdb.h"
1111
#include "pgduckdb/pgduckdb_node.hpp"
1212
#include "pgduckdb/pgduckdb_background_worker.hpp"
13+
#include "pgduckdb/pgduckdb_metadata_cache.hpp"
1314
#include "pgduckdb/pgduckdb_xact.hpp"
1415

1516
static void DuckdbInitGUC(void);

src/pgduckdb_ddl.cpp

+76-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "pgduckdb/pgduckdb_utils.hpp"
33
#include "pgduckdb/pgduckdb_xact.hpp"
44
#include "pgduckdb/pgduckdb_guc.h"
5+
#include "pgduckdb/pgduckdb_ddl.hpp"
56

67
extern "C" {
78
#include "postgres.h"
@@ -32,6 +33,7 @@ extern "C" {
3233
#include "pgduckdb/pgduckdb_ruleutils.h"
3334
}
3435

36+
#include "pgduckdb/pgduckdb_guc.h"
3537
#include "pgduckdb/utility/cpp_wrapper.hpp"
3638
#include "pgduckdb/pgduckdb_duckdb.hpp"
3739
#include "pgduckdb/pgduckdb_background_worker.hpp"
@@ -40,6 +42,10 @@ extern "C" {
4042
#include "pgduckdb/vendor/pg_list.hpp"
4143
#include <inttypes.h>
4244

45+
namespace pgduckdb {
46+
DDLType top_level_duckdb_ddl_type = DDLType::NONE;
47+
} // namespace pgduckdb
48+
4349
/*
4450
* ctas_skip_data stores the original value of the skipData field of the
4551
* CreateTableAsStmt of the query that's currently being executed. For duckdb
@@ -181,6 +187,11 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
181187
char *access_method = stmt->accessMethod ? stmt->accessMethod : default_table_access_method;
182188
bool is_duckdb_table = strcmp(access_method, "duckdb") == 0;
183189
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;
184195
pgduckdb::ClaimCurrentCommandId();
185196
}
186197

@@ -202,6 +213,11 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
202213
char *access_method = stmt->into->accessMethod ? stmt->into->accessMethod : default_table_access_method;
203214
bool is_duckdb_table = strcmp(access_method, "duckdb") == 0;
204215
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;
205221
pgduckdb::ClaimCurrentCommandId();
206222
/*
207223
* Force skipData to false for duckdb tables, so that Postgres does
@@ -313,6 +329,21 @@ DuckdbHandleDDL(PlannedStmt *pstmt, const char *query_string, ParamListInfo para
313329
break;
314330
}
315331
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);
316347
}
317348
}
318349

@@ -416,6 +447,15 @@ DECLARE_PG_FUNCTION(duckdb_create_table_trigger) {
416447
PG_RETURN_NULL();
417448
}
418449

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+
419459
EventTriggerData *trigger_data = (EventTriggerData *)fcinfo->context;
420460
Node *parsetree = trigger_data->parsetree;
421461

@@ -859,6 +899,16 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
859899
PG_RETURN_NULL();
860900
}
861901

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+
862912
SPI_connect();
863913

864914
/*
@@ -889,20 +939,20 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
889939
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
890940
JOIN pg_catalog.pg_class
891941
ON cmds.objid = pg_class.oid
892-
WHERE cmds.object_type = 'table'
942+
WHERE cmds.object_type in ('table', 'table column')
893943
AND pg_class.relam = (SELECT oid FROM pg_am WHERE amname = 'duckdb')
894944
UNION ALL
895945
SELECT objid as relid, false AS needs_to_check_temporary_set
896946
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
897947
JOIN duckdb.tables AS ddbtables
898948
ON cmds.objid = ddbtables.relid
899-
WHERE cmds.object_type = 'table'
949+
WHERE cmds.object_type in ('table', 'table column')
900950
UNION ALL
901951
SELECT objid as relid, true AS needs_to_check_temporary_set
902952
FROM pg_catalog.pg_event_trigger_ddl_commands() cmds
903953
JOIN pg_catalog.pg_class
904954
ON cmds.objid = pg_class.oid
905-
WHERE cmds.object_type = 'table'
955+
WHERE cmds.object_type in ('table', 'table column')
906956
AND pg_class.relam != (SELECT oid FROM pg_am WHERE amname = 'duckdb')
907957
AND pg_class.relpersistence = 't'
908958
)",
@@ -946,7 +996,29 @@ DECLARE_PG_FUNCTION(duckdb_alter_table_trigger) {
946996
}
947997
}
948998

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();
9501022
}
9511023

9521024
/*

0 commit comments

Comments
 (0)