diff --git a/include/pgduckdb/pgduckdb_ddl.hpp b/include/pgduckdb/pgduckdb_ddl.hpp new file mode 100644 index 00000000..cfae51c4 --- /dev/null +++ b/include/pgduckdb/pgduckdb_ddl.hpp @@ -0,0 +1,5 @@ +extern "C" { +#include "postgres.h" +#include "nodes/nodes.h" +} +void DuckdbHandleDDL(Node *ParseTree, const char *queryString); diff --git a/include/pgduckdb/pgduckdb_metadata_cache.hpp b/include/pgduckdb/pgduckdb_metadata_cache.hpp index cdaab47c..062d468a 100644 --- a/include/pgduckdb/pgduckdb_metadata_cache.hpp +++ b/include/pgduckdb/pgduckdb_metadata_cache.hpp @@ -5,4 +5,5 @@ extern "C" { namespace pgduckdb { bool IsExtensionRegistered(); bool IsDuckdbOnlyFunction(Oid function_oid); +Oid DuckdbTableAmOid(); } // namespace pgduckdb diff --git a/include/pgduckdb/pgduckdb_ruleutils.h b/include/pgduckdb/pgduckdb_ruleutils.h index 82c7dc6c..ca4b6e96 100644 --- a/include/pgduckdb/pgduckdb_ruleutils.h +++ b/include/pgduckdb/pgduckdb_ruleutils.h @@ -1,3 +1,5 @@ #include "postgres.h" +char *pgduckdb_relation_name(Oid relid); char *pgduckdb_function_name(Oid function_oid); +char *pgduckdb_get_tabledef(Oid relation_id); diff --git a/include/pgduckdb/pgduckdb_table_am.hpp b/include/pgduckdb/pgduckdb_table_am.hpp new file mode 100644 index 00000000..9dbea420 --- /dev/null +++ b/include/pgduckdb/pgduckdb_table_am.hpp @@ -0,0 +1,8 @@ +extern "C" { +#include "postgres.h" +#include "access/tableam.h" +} + +namespace pgduckdb { +bool IsDuckdbTableAm(const TableAmRoutine *am); +} diff --git a/include/pgduckdb/utility/rename_ruleutils.h b/include/pgduckdb/utility/rename_ruleutils.h index 286a1d61..6c1a8e76 100644 --- a/include/pgduckdb/utility/rename_ruleutils.h +++ b/include/pgduckdb/utility/rename_ruleutils.h @@ -20,3 +20,10 @@ #define get_range_partbound_string pgduckdb_get_range_partbound_string #define pg_get_statisticsobjdef_string pgduckdb_pg_get_statisticsobjdef_string #define get_list_partvalue_string pgduckdb_get_list_partvalue_string + +/* + * The following replaces all usages of generate_qualified_relation_name and + * generate_relation_name with calls to the pgduckdb_relation_name function + */ +#define generate_qualified_relation_name pgduckdb_relation_name +#define generate_relation_name(relid, namespaces) pgduckdb_relation_name(relid) diff --git a/sql/pg_duckdb--0.0.1.sql b/sql/pg_duckdb--0.0.1.sql index 202e74b7..8e748159 100644 --- a/sql/pg_duckdb--0.0.1.sql +++ b/sql/pg_duckdb--0.0.1.sql @@ -139,12 +139,38 @@ CREATE TABLE extensions ( enabled BOOL DEFAULT TRUE ); +CREATE TABLE tables ( + relid regclass PRIMARY KEY +); + CREATE OR REPLACE FUNCTION install_extension(extension_name TEXT) RETURNS bool LANGUAGE C AS 'MODULE_PATHNAME', 'install_extension'; CREATE OR REPLACE FUNCTION raw_query(query TEXT) RETURNS void LANGUAGE C AS 'MODULE_PATHNAME', 'pgduckdb_raw_query'; +CREATE FUNCTION duckdb_am_handler(internal) +RETURNS table_am_handler +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE ACCESS METHOD duckdb + TYPE TABLE + HANDLER duckdb_am_handler; + +CREATE FUNCTION duckdb_drop_table_trigger() RETURNS event_trigger + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE EVENT TRIGGER duckdb_drop_table_trigger ON sql_drop + EXECUTE FUNCTION duckdb_drop_table_trigger(); + +CREATE FUNCTION duckdb_create_table_trigger() RETURNS event_trigger + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE EVENT TRIGGER duckdb_create_table_trigger ON ddl_command_end + WHEN tag IN ('CREATE TABLE', 'CREATE TABLE AS') + EXECUTE FUNCTION duckdb_create_table_trigger(); + DO $$ BEGIN RAISE WARNING 'To actually execute queries using DuckDB you need to run "SET duckdb.execution TO true;"'; diff --git a/src/pgduckdb_ddl.cpp b/src/pgduckdb_ddl.cpp new file mode 100644 index 00000000..6559d8fe --- /dev/null +++ b/src/pgduckdb_ddl.cpp @@ -0,0 +1,179 @@ +#include "duckdb.hpp" +#include + +extern "C" { +#include "postgres.h" +#include "access/tableam.h" +#include "catalog/pg_type.h" +#include "commands/event_trigger.h" +#include "fmgr.h" +#include "catalog/pg_authid_d.h" +#include "funcapi.h" +#include "nodes/print.h" +#include "nodes/makefuncs.h" +#include "optimizer/optimizer.h" +#include "tcop/utility.h" +#include "utils/syscache.h" +#include "commands/event_trigger.h" +#include "executor/spi.h" +#include "miscadmin.h" + +#include "pgduckdb/pgduckdb_ruleutils.h" +} + +#include "pgduckdb/pgduckdb_duckdb.hpp" +#include "pgduckdb/vendor/pg_list.hpp" +#include + +void +DuckdbHandleDDL(Node *parsetree, const char *queryString) { + if (IsA(parsetree, CreateTableAsStmt)) { + auto stmt = castNode(CreateTableAsStmt, parsetree); + char *access_method = stmt->into->accessMethod ? stmt->into->accessMethod : default_table_access_method; + if (strcmp(access_method, "duckdb") != 0) { + /* not a duckdb table, so don't mess with the query */ + return; + } + + elog(ERROR, "DuckDB does not support CREATE TABLE AS yet"); + } +} + +extern "C" { + +PG_FUNCTION_INFO_V1(duckdb_create_table_trigger); + +Datum +duckdb_create_table_trigger(PG_FUNCTION_ARGS) { + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + SPI_connect(); + + /* Temporarily escalate privileges to superuser so we can insert into duckdb.tables */ + Oid saved_userid; + int sec_context; + GetUserIdAndSecContext(&saved_userid, &sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, sec_context | SECURITY_LOCAL_USERID_CHANGE); + /* + * We track the table oid in duckdb.tables so we can later check in our + * delete event trigger if the table was created using the duckdb access + * method. See the code comment in that function for more details. + */ + int ret = SPI_exec(R"( + INSERT INTO duckdb.tables(relid) + SELECT objid + FROM pg_catalog.pg_event_trigger_ddl_commands() cmds + JOIN pg_catalog.pg_class + ON cmds.objid = pg_class.oid + WHERE cmds.object_type = 'table' + AND pg_class.relam = (SELECT oid FROM pg_am WHERE amname = 'duckdb') + RETURNING relid)", + 0); + + /* Revert back to original privileges */ + SetUserIdAndSecContext(saved_userid, sec_context); + + if (ret != SPI_OK_INSERT_RETURNING) + elog(ERROR, "SPI_exec failed: error code %s", SPI_result_code_string(ret)); + + /* if we inserted a row it was a duckdb table */ + auto isDuckdbTable = SPI_processed > 0; + + if (!isDuckdbTable) { + SPI_finish(); + PG_RETURN_NULL(); + } + if (SPI_processed != 1) { + elog(ERROR, "Expected single table to be created, but found %" PRIu64, SPI_processed); + } + HeapTuple tuple = SPI_tuptable->vals[0]; + bool isnull; + Datum relid_datum = SPI_getbinval(tuple, SPI_tuptable->tupdesc, 1, &isnull); + if (isnull) { + elog(ERROR, "Expected relid to be returned, but found NULL"); + } + Oid relid = DatumGetObjectId(relid_datum); + SPI_finish(); + + const char *query_cstr = pgduckdb_get_tabledef(relid); + std::string query_string(query_cstr); + + auto db = pgduckdb::DuckDBManager::Get().GetDatabase(); + auto connection = duckdb::make_uniq(db); + auto &context = *connection->context; + auto result = context.Query(query_string, false); + if (result->HasError()) { + elog(ERROR, "(PGDuckDB/duckdb_create_table_trigger) Could not create table: %s", result->GetError().c_str()); + } + + PG_RETURN_NULL(); +} +PG_FUNCTION_INFO_V1(duckdb_drop_table_trigger); + +Datum +duckdb_drop_table_trigger(PG_FUNCTION_ARGS) { + if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "not fired by event trigger manager"); + + SPI_connect(); + + /* Temporarily escalate privileges to superuser so we can delete from duckdb.tables */ + Oid saved_userid; + int sec_context; + GetUserIdAndSecContext(&saved_userid, &sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, sec_context | SECURITY_LOCAL_USERID_CHANGE); + + // Because the table metadata is deleted from the postgres catalogs we + // cannot find out if the table was using the duckdb access method. So + // instead we keep our own metadata table that also tracks which tables are + // duckdb tables. + int ret = SPI_exec(R"( + DELETE FROM duckdb.tables + USING ( + SELECT objid, object_name, object_identity + FROM pg_catalog.pg_event_trigger_dropped_objects() + WHERE object_type = 'table' + ) objs + WHERE relid = objid + RETURNING objs.object_identity + )", + 0); + + /* Revert back to original privileges */ + SetUserIdAndSecContext(saved_userid, sec_context); + + if (ret != SPI_OK_DELETE_RETURNING) + elog(ERROR, "SPI_exec failed: error code %s", SPI_result_code_string(ret)); + + auto db = pgduckdb::DuckDBManager::Get().GetDatabase(); + auto connection = duckdb::make_uniq(db); + auto &context = *connection->context; + + auto result = context.Query("BEGIN TRANSACTION", false); + if (result->HasError()) { + elog(ERROR, "(PGDuckDB/duckdb_drop_table_trigger) Could not start transaction"); + } + + for (auto proc = 0; proc < SPI_processed; proc++) { + HeapTuple tuple = SPI_tuptable->vals[proc]; + + char *object_identity = SPI_getvalue(tuple, SPI_tuptable->tupdesc, 1); + // TODO: Handle std::string creation in a safe way for allocation failures + auto result = context.Query("DROP TABLE IF EXISTS " + std::string(object_identity), false); + if (result->HasError()) { + elog(ERROR, "(PGDuckDB/duckdb_drop_table_trigger) Could not drop table %s: %s", object_identity, + result->GetError().c_str()); + } + } + + SPI_finish(); + + result = context.Query("COMMIT", false); + if (result->HasError()) { + elog(ERROR, "(PGDuckDB/duckdb_drop_table_trigger) Could not commit transaction"); + } + + PG_RETURN_NULL(); +} +} diff --git a/src/pgduckdb_duckdb.cpp b/src/pgduckdb_duckdb.cpp index 4b7ee3bb..58cb72bb 100644 --- a/src/pgduckdb_duckdb.cpp +++ b/src/pgduckdb_duckdb.cpp @@ -13,6 +13,11 @@ #include "pgduckdb/pgduckdb_utils.hpp" #include "pgduckdb/catalog/pgduckdb_storage.hpp" +extern "C" { +#include "postgres.h" + +#include "utils/elog.h" +} #include #include @@ -173,10 +178,6 @@ DuckdbCreateConnection(List *rtables, PlannerInfo *planner_info, List *needed_co context.registered_state->Insert( "postgres_state", duckdb::make_shared_ptr(rtables, planner_info, needed_columns, query)); - auto res = context.Query("set search_path='pgduckdb.main'", false); - if (res->HasError()) { - elog(WARNING, "(DuckDB) %s", res->GetError().c_str()); - } return con; } diff --git a/src/pgduckdb_hooks.cpp b/src/pgduckdb_hooks.cpp index 486c58af..2103970a 100644 --- a/src/pgduckdb_hooks.cpp +++ b/src/pgduckdb_hooks.cpp @@ -2,19 +2,24 @@ extern "C" { #include "postgres.h" + #include "catalog/pg_namespace.h" #include "commands/extension.h" #include "nodes/nodes.h" #include "nodes/nodeFuncs.h" +#include "nodes/primnodes.h" #include "tcop/utility.h" #include "tcop/pquery.h" #include "utils/rel.h" +#include "utils/relcache.h" #include "optimizer/optimizer.h" } #include "pgduckdb/pgduckdb.h" #include "pgduckdb/pgduckdb_metadata_cache.hpp" +#include "pgduckdb/pgduckdb_ddl.hpp" #include "pgduckdb/pgduckdb_planner.hpp" +#include "pgduckdb/pgduckdb_table_am.hpp" #include "pgduckdb/utility/copy.hpp" #include "pgduckdb/vendor/pg_explain.hpp" #include "pgduckdb/vendor/pg_list.hpp" @@ -46,17 +51,41 @@ IsCatalogTable(List *tables) { } static bool -ContainsDuckdbFunctions(Node *node, void *context) { +IsDuckdbTable(Oid relid) { + if (relid == InvalidOid) { + return false; + } + auto rel = RelationIdGetRelation(relid); + bool result = pgduckdb::IsDuckdbTableAm(rel->rd_tableam); + RelationClose(rel); + return result; +} + +static bool +ContainsDuckdbTables(List *rte_list) { + foreach_node(RangeTblEntry, rte, rte_list) { + if (IsDuckdbTable(rte->relid)) { + return true; + } + } + return false; +} + +static bool +ContainsDuckdbItems(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Query)) { Query *query = (Query *)node; - #if PG_VERSION_NUM >= 160000 - return query_tree_walker(query, ContainsDuckdbFunctions, context, 0); - #else - return query_tree_walker(query, (bool (*)()) ((void *) ContainsDuckdbFunctions), context, 0); - #endif + if (ContainsDuckdbTables(query->rtable)) { + return true; + } +#if PG_VERSION_NUM >= 160000 + return query_tree_walker(query, ContainsDuckdbItems, context, 0); +#else + return query_tree_walker(query, (bool (*)())((void *)ContainsDuckdbItems), context, 0); +#endif } if (IsA(node, FuncExpr)) { @@ -67,19 +96,15 @@ ContainsDuckdbFunctions(Node *node, void *context) { } #if PG_VERSION_NUM >= 160000 - return expression_tree_walker(node, ContainsDuckdbFunctions, context); + return expression_tree_walker(node, ContainsDuckdbItems, context); #else - return expression_tree_walker(node, (bool (*)()) ((void *) ContainsDuckdbFunctions), context); + return expression_tree_walker(node, (bool (*)())((void *)ContainsDuckdbItems), context); #endif } static bool NeedsDuckdbExecution(Query *query) { -#if PG_VERSION_NUM >= 160000 - return query_tree_walker(query, ContainsDuckdbFunctions, NULL, 0); -#else - return query_tree_walker(query, (bool (*)()) ((void *) ContainsDuckdbFunctions), NULL, 0); -#endif + return ContainsDuckdbItems((Node *)query, NULL); } static bool @@ -89,9 +114,12 @@ IsAllowedStatement(Query *query) { return false; } - /* We don't support modifying statements yet */ + /* We don't support modifying statements on Postgres tables yet */ if (query->commandType != CMD_SELECT) { - return false; + RangeTblEntry *resultRte = list_nth_node(RangeTblEntry, query->rtable, query->resultRelation - 1); + if (!IsDuckdbTable(resultRte->relid)) { + return false; + } } /* @@ -127,7 +155,8 @@ DuckdbPlannerHook(Query *parse, const char *query_string, int cursor_options, Pa if (NeedsDuckdbExecution(parse)) { if (!IsAllowedStatement(parse)) { - elog(ERROR, "(PGDuckDB/DuckdbPlannerHook) Only SELECT statements involving DuckDB are supported."); + elog(ERROR, "(PGDuckDB/DuckdbPlannerHook) Writing to Postgres tables is not support for queries that " + "require DuckDB"); } PlannedStmt *duckdbPlan = DuckdbPlanNode(parse, cursor_options, bound_params); if (duckdbPlan) { @@ -157,6 +186,10 @@ DuckdbUtilityHook(PlannedStmt *pstmt, const char *query_string, bool read_only_t } } + if (pgduckdb::IsExtensionRegistered()) { + DuckdbHandleDDL(parsetree, query_string); + } + if (prev_process_utility_hook) { (*prev_process_utility_hook)(pstmt, query_string, read_only_tree, context, params, query_env, dest, qc); } else { diff --git a/src/pgduckdb_metadata_cache.cpp b/src/pgduckdb_metadata_cache.cpp index 8031cdf4..5df5b813 100644 --- a/src/pgduckdb_metadata_cache.cpp +++ b/src/pgduckdb_metadata_cache.cpp @@ -4,6 +4,7 @@ extern "C" { #include "access/htup_details.h" #include "catalog/dependency.h" #include "catalog/namespace.h" +#include "catalog/pg_am.h" #include "catalog/pg_type.h" #include "catalog/pg_proc.h" #include "commands/extension.h" @@ -34,6 +35,8 @@ struct { bool installed; /* The Postgres OID of the pg_duckdb extension. */ Oid extension_oid; + /* The OID of the duckdb Table Access Method */ + Oid table_am_oid; /* * A list of Postgres OIDs of functions that can only be executed by DuckDB. * XXX: We're iterating over this list in IsDuckdbOnlyFunction. If this list @@ -65,6 +68,8 @@ InvalidateCaches(Datum arg, int cache_id, uint32 hash_value) { if (cache.installed) { list_free(cache.duckdb_only_functions); cache.duckdb_only_functions = NIL; + cache.extension_oid = InvalidOid; + cache.table_am_oid = InvalidOid; } } @@ -140,6 +145,7 @@ IsExtensionRegistered() { if (cache.installed) { /* If the extension is installed we can build the rest of the cache */ BuildDuckdbOnlyFunctions(); + cache.table_am_oid = GetSysCacheOid1(AMNAME, Anum_pg_am_oid, CStringGetDatum("duckdb")); } cache.valid = true; @@ -162,4 +168,10 @@ IsDuckdbOnlyFunction(Oid function_oid) { return false; } +Oid +DuckdbTableAmOid() { + Assert(cache.valid); + return cache.table_am_oid; +} + } // namespace pgduckdb diff --git a/src/pgduckdb_planner.cpp b/src/pgduckdb_planner.cpp index d01d9798..84943689 100644 --- a/src/pgduckdb_planner.cpp +++ b/src/pgduckdb_planner.cpp @@ -64,17 +64,7 @@ DuckdbPrepare(const Query *query, ParamListInfo bound_params) { * subquery_planner call that PlanQuery does. */ Query *copied_query = (Query *)copyObjectImpl(query); - /* - Temporarily clear search_path so that the query will contain only fully qualified tables. - If we don't do this tables are only fully-qualified if they are not part of the current search_path. - NOTE: This still doesn't fully qualify tables in pg_catalog or temporary tables, for that we'd need to modify - pgduckdb_pg_get_querydef - */ - - auto save_nestlevel = NewGUCNestLevel(); - SetConfigOption("search_path", "", PGC_USERSET, PGC_S_SESSION); const char *query_string = pgduckdb_pg_get_querydef(copied_query, false); - AtEOXact_GUC(false, save_nestlevel); if (ActivePortal && ActivePortal->commandTag == CMDTAG_EXPLAIN) { if (duckdb_explain_analyze) { diff --git a/src/pgduckdb_ruleutils.cpp b/src/pgduckdb_ruleutils.cpp index 62bb93b3..436d88fe 100644 --- a/src/pgduckdb_ruleutils.cpp +++ b/src/pgduckdb_ruleutils.cpp @@ -1,10 +1,22 @@ extern "C" { #include "postgres.h" +#include "access/relation.h" +#include "access/htup_details.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "storage/lockdefs.h" + +#include "pgduckdb/vendor/pg_ruleutils.h" } +#include "pgduckdb/pgduckdb_table_am.hpp" #include "pgduckdb/pgduckdb_metadata_cache.hpp" extern "C" { @@ -16,4 +28,203 @@ pgduckdb_function_name(Oid function_oid) { auto func_name = get_func_name(function_oid); return psprintf("system.main.%s", quote_identifier(func_name)); } + +/* + * generate_relation_name computes the fully qualified name of the relation in + * DuckDB for the specified Postgres OID. This includes the DuckDB database name + * too. + */ +char * +pgduckdb_relation_name(Oid relation_oid) { + + HeapTuple tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relation_oid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for relation %u", relation_oid); + Form_pg_class relation = (Form_pg_class)GETSTRUCT(tp); + const char *relname = NameStr(relation->relname); + /* + * XXX: Should this be using get_namespace_name instead, the difference is + * that it will store temp tables in the pg_temp_123 style schemas instead + * of plain pg_temp. I don't think this really matters. + */ + const char *nspname = get_namespace_name_or_temp(relation->relnamespace); + const char *dbname; + + if (relation->relam == pgduckdb::DuckdbTableAmOid()) { + dbname = "memory"; + } else { + dbname = "pgduckdb"; + } + + char *result = psprintf("%s.%s.%s", quote_identifier(dbname), quote_identifier(nspname), quote_identifier(relname)); + + ReleaseSysCache(tp); + + return result; +} + +/* + * pgduckdb_get_tabledef returns the definition of a given table. This + * definition includes table's schema, default column values, not null and check + * constraints. The definition does not include constraints that trigger index + * creations; specifically, unique and primary key constraints are excluded. + * When includeSequenceDefaults is INCLUDE_SEQUENCE_DEFAULTS, the function also creates + * DEFAULT clauses for columns getting their default values from a sequence. + * When IncludeIdentities is NO_IDENTITY, the function does not include identity column + * specifications. When it's INCLUDE_IDENTITY it creates GENERATED .. AS IDENTIY clauses. + */ +char * +pgduckdb_get_tabledef(Oid relation_oid) { + AttrNumber constraint_index = 0; + StringInfoData buffer = {NULL, 0, 0, 0}; + + Relation relation = relation_open(relation_oid, AccessShareLock); + char *relation_name = pgduckdb_relation_name(relation_oid); + char *schema_name = get_namespace_name_or_temp(relation->rd_rel->relnamespace); + + initStringInfo(&buffer); + + if (get_rel_relkind(relation_oid) != RELKIND_RELATION) { + elog(ERROR, "Only regular tables are supported in DuckDB"); + } + + appendStringInfo(&buffer, "CREATE SCHEMA IF NOT EXISTS %s; ", quote_identifier(schema_name)); + + appendStringInfoString(&buffer, "CREATE "); + + if (relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP) { + elog(ERROR, "Only TEMP tables are supported in DuckDB"); + } + + appendStringInfo(&buffer, "TABLE %s (", relation_name); + + List *relation_context = pgduckdb_deparse_context_for(relation_name, relation_oid); + + /* + * Iterate over the table's columns. If a particular column is not dropped + * and is not inherited from another table, print the column's name and + * its formatted type. + */ + TupleDesc tuple_descriptor = RelationGetDescr(relation); + TupleConstr *tuple_constraints = tuple_descriptor->constr; + AttrDefault *default_value_list = tuple_constraints ? tuple_constraints->defval : NULL; + + bool first_column_printed = false; + AttrNumber default_value_index = 0; + for (int i = 0; i < tuple_descriptor->natts; i++) { + Form_pg_attribute column = TupleDescAttr(tuple_descriptor, i); + + /* + * We disregard the inherited attributes (i.e., attinhcount > 0) here. + * The reasoning behind this is that Citus implements declarative + * partitioning by creating the partitions first and then sending + * "ALTER TABLE parent_table ATTACH PARTITION .." command. This may + * not play well with regular inherited tables, which isn't a big + * concern from Citus' perspective. + */ + if (!column->attisdropped) { + const char *column_name = NameStr(column->attname); + + const char *column_type_name = format_type_with_typemod(column->atttypid, column->atttypmod); + + if (first_column_printed) { + appendStringInfoString(&buffer, ", "); + } + first_column_printed = true; + + appendStringInfo(&buffer, "%s ", quote_identifier(column_name)); + appendStringInfoString(&buffer, column_type_name); + + if (column->attcompression) { + elog(ERROR, "Column compression is not supported in DuckDB"); + } + + if (column->attidentity) { + elog(ERROR, "Identity columns are not supported in DuckDB"); + } + + /* if this column has a default value, append the default value */ + if (column->atthasdef) { + Assert(tuple_constraints != NULL); + Assert(default_value_list != NULL); + + AttrDefault *default_value = &(default_value_list[default_value_index]); + default_value_index++; + + Assert(default_value->adnum == (i + 1)); + Assert(default_value_index <= tuple_constraints->num_defval); + + /* + * convert expression to node tree, and prepare deparse + * context + */ + Node *default_node = (Node *)stringToNode(default_value->adbin); + + /* deparse default value string */ + char *default_string = pgduckdb_deparse_expression(default_node, relation_context, false, false); + + if (column->attgenerated == ATTRIBUTE_GENERATED_STORED) { + elog(ERROR, "DuckDB does not support STORED generated columns"); + } else { + appendStringInfo(&buffer, " DEFAULT %s", default_string); + } + } + + /* if this column has a not null constraint, append the constraint */ + if (column->attnotnull) { + appendStringInfoString(&buffer, " NOT NULL"); + } + + if (column->attcollation != InvalidOid && column->attcollation != DEFAULT_COLLATION_OID) { + elog(ERROR, "DuckDB does not support column collations"); + } + } + } + + /* + * Now check if the table has any constraints. If it does, set the number + * of check constraints here. Then iterate over all check constraints and + * print them. + */ + AttrNumber constraint_count = tuple_constraints ? tuple_constraints->num_check : 0; + ConstrCheck *check_constraint_list = tuple_constraints ? tuple_constraints->check : NULL; + + for (constraint_index = 0; constraint_index < constraint_count; constraint_index++) { + ConstrCheck *check_constraint = &(check_constraint_list[constraint_index]); + + /* convert expression to node tree, and prepare deparse context */ + Node *check_node = (Node *)stringToNode(check_constraint->ccbin); + + /* deparse check constraint string */ + char *check_string = pgduckdb_deparse_expression(check_node, relation_context, false, false); + + /* if an attribute or constraint has been printed, format properly */ + if (first_column_printed || constraint_index > 0) { + appendStringInfoString(&buffer, ", "); + } + + appendStringInfo(&buffer, "CONSTRAINT %s CHECK ", quote_identifier(check_constraint->ccname)); + + appendStringInfoString(&buffer, "("); + appendStringInfoString(&buffer, check_string); + appendStringInfoString(&buffer, ")"); + } + + /* close create table's outer parentheses */ + appendStringInfoString(&buffer, ")"); + + if (!pgduckdb::IsDuckdbTableAm(relation->rd_tableam)) { + /* Shouldn't happen but seems good to check anyway */ + elog(ERROR, "Only a table with the DuckDB can be stored in DuckDB, %d %d", relation->rd_rel->relam, + pgduckdb::DuckdbTableAmOid()); + } + + if (relation->rd_options) { + elog(ERROR, "Storage options are not supported in DuckDB"); + } + + relation_close(relation, AccessShareLock); + + return (buffer.data); +} } diff --git a/src/pgduckdb_table_am.cpp b/src/pgduckdb_table_am.cpp new file mode 100644 index 00000000..a6ed7b07 --- /dev/null +++ b/src/pgduckdb_table_am.cpp @@ -0,0 +1,422 @@ +/*------------------------------------------------------------------------- + * + * duckdb_table_am.c + * duckdb table access method code + * + * All the functions use snake_case naming on purpose to match the Postgres + * style for easy grepping. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +extern "C" { +#include "postgres.h" + +#include + +#include "miscadmin.h" + +#include "access/tableam.h" +#include "access/heapam.h" +#include "access/amapi.h" +#include "catalog/index.h" +#include "commands/vacuum.h" +#include "executor/tuptable.h" + +#define NOT_IMPLEMENTED() elog(ERROR, "duckdb does not implement %s", __func__) + +PG_FUNCTION_INFO_V1(duckdb_am_handler); + +/* ------------------------------------------------------------------------ + * Slot related callbacks for duckdb AM + * ------------------------------------------------------------------------ + */ + +static const TupleTableSlotOps * +duckdb_slot_callbacks(Relation relation) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * Table Scan Callbacks for duckdb AM + * ------------------------------------------------------------------------ + */ + +static TableScanDesc +duckdb_scan_begin(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, ParallelTableScanDesc parallel_scan, + uint32 flags) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_scan_end(TableScanDesc sscan) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_scan_rescan(TableScanDesc sscan, ScanKey key, bool set_params, bool allow_strat, bool allow_sync, + bool allow_pagemode) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * Index Scan Callbacks for duckdb AM + * ------------------------------------------------------------------------ + */ + +static IndexFetchTableData * +duckdb_index_fetch_begin(Relation rel) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_index_fetch_reset(IndexFetchTableData *scan) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_index_fetch_end(IndexFetchTableData *scan) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_index_fetch_tuple(struct IndexFetchTableData *scan, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, + bool *call_again, bool *all_dead) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * Callbacks for non-modifying operations on individual tuples for + * duckdb AM. + * ------------------------------------------------------------------------ + */ + +static bool +duckdb_fetch_row_version(Relation relation, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_get_latest_tid(TableScanDesc sscan, ItemPointer tid) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snapshot) { + NOT_IMPLEMENTED(); +} + +static TransactionId +duckdb_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) { + NOT_IMPLEMENTED(); +} + +/* ---------------------------------------------------------------------------- + * Functions for manipulations of physical tuples for duckdb AM. + * ---------------------------------------------------------------------------- + */ + +static void +duckdb_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, int options, BulkInsertState bistate) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_tuple_insert_speculative(Relation relation, TupleTableSlot *slot, CommandId cid, int options, + BulkInsertState bistate, uint32 specToken) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_tuple_complete_speculative(Relation relation, TupleTableSlot *slot, uint32 spekToken, bool succeeded) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, CommandId cid, int options, + BulkInsertState bistate) { + NOT_IMPLEMENTED(); +} + +static TM_Result +duckdb_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot snapshot, Snapshot crosscheck, + bool wait, TM_FailureData *tmfd, bool changingPart) { + NOT_IMPLEMENTED(); +} + +#if PG_VERSION_NUM >= 160000 + +static TM_Result +duckdb_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, + Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + TU_UpdateIndexes *update_indexes) { + NOT_IMPLEMENTED(); +} + +#else + +static TM_Result +duckdb_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, CommandId cid, Snapshot snapshot, + Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, + bool *update_indexes) { + NOT_IMPLEMENTED(); +} + +#endif + +static TM_Result +duckdb_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, CommandId cid, + LockTupleMode mode, LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_finish_bulk_insert(Relation relation, int options) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * DDL related callbacks for duckdb AM. + * ------------------------------------------------------------------------ + */ + +#if PG_VERSION_NUM >= 160000 + +static void +duckdb_relation_set_new_filelocator(Relation rel, const RelFileLocator *newrnode, char persistence, + TransactionId *freezeXid, MultiXactId *minmulti) { + /* nothing to do */ +} + +#else + +static void +duckdb_relation_set_new_filenode(Relation rel, const RelFileNode *newrnode, char persistence, TransactionId *freezeXid, + MultiXactId *minmulti) { + /* nothing to do */ +} + +#endif + +static void +duckdb_relation_nontransactional_truncate(Relation rel) { + NOT_IMPLEMENTED(); +} + +#if PG_VERSION_NUM >= 160000 + +static void +duckdb_copy_data(Relation rel, const RelFileLocator *newrnode) { + NOT_IMPLEMENTED(); +} + +#else + +static void +duckdb_copy_data(Relation rel, const RelFileNode *newrnode) { + NOT_IMPLEMENTED(); +} + +#endif + +static void +duckdb_copy_for_cluster(Relation OldTable, Relation NewTable, Relation OldIndex, bool use_sort, + TransactionId OldestXmin, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, + double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_vacuum(Relation onerel, VacuumParams *params, BufferAccessStrategy bstrategy) { + NOT_IMPLEMENTED(); +} + +#if PG_VERSION_NUM >= 170000 + +static bool +duckdb_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream) { + NOT_IMPLEMENTED(); +} + +#else + +static bool +duckdb_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, BufferAccessStrategy bstrategy) { + NOT_IMPLEMENTED(); +} +#endif + +static bool +duckdb_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, double *liverows, double *deadrows, + TupleTableSlot *slot) { + NOT_IMPLEMENTED(); +} + +static double +duckdb_index_build_range_scan(Relation tableRelation, Relation indexRelation, IndexInfo *indexInfo, bool allow_sync, + bool anyvisible, bool progress, BlockNumber start_blockno, BlockNumber numblocks, + IndexBuildCallback callback, void *callback_state, TableScanDesc scan) { + NOT_IMPLEMENTED(); +} + +static void +duckdb_index_validate_scan(Relation tableRelation, Relation indexRelation, IndexInfo *indexInfo, Snapshot snapshot, + ValidateIndexState *state) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * Miscellaneous callbacks for the duckdb AM + * ------------------------------------------------------------------------ + */ + +static uint64 +duckdb_relation_size(Relation rel, ForkNumber forkNumber) { + NOT_IMPLEMENTED(); +} + +/* + * Check to see whether the table needs a TOAST table. + */ +static bool +duckdb_relation_needs_toast_table(Relation rel) { + + /* we don't need toast, because everything is stored in duckdb */ + return false; +} + +/* ------------------------------------------------------------------------ + * Planner related callbacks for the duckdb AM + * ------------------------------------------------------------------------ + */ + +static void +duckdb_estimate_rel_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac) { + /* no data available */ + if (attr_widths) + *attr_widths = 0; + if (pages) + *pages = 0; + if (tuples) + *tuples = 0; + if (allvisfrac) + *allvisfrac = 0; +} + +/* ------------------------------------------------------------------------ + * Executor related callbacks for the duckdb AM + * ------------------------------------------------------------------------ + */ + +static bool +duckdb_scan_bitmap_next_block(TableScanDesc scan, TBMIterateResult *tbmres) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_scan_bitmap_next_tuple(TableScanDesc scan, TBMIterateResult *tbmres, TupleTableSlot *slot) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate) { + NOT_IMPLEMENTED(); +} + +static bool +duckdb_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate, TupleTableSlot *slot) { + NOT_IMPLEMENTED(); +} + +/* ------------------------------------------------------------------------ + * Definition of the duckdb table access method. + * ------------------------------------------------------------------------ + */ + +static const TableAmRoutine duckdb_methods = {.type = T_TableAmRoutine, + + .slot_callbacks = duckdb_slot_callbacks, + + .scan_begin = duckdb_scan_begin, + .scan_end = duckdb_scan_end, + .scan_rescan = duckdb_scan_rescan, + .scan_getnextslot = duckdb_scan_getnextslot, + + /* optional callbacks */ + .scan_set_tidrange = NULL, + .scan_getnextslot_tidrange = NULL, + + /* these are common helper functions */ + .parallelscan_estimate = table_block_parallelscan_estimate, + .parallelscan_initialize = table_block_parallelscan_initialize, + .parallelscan_reinitialize = table_block_parallelscan_reinitialize, + + .index_fetch_begin = duckdb_index_fetch_begin, + .index_fetch_reset = duckdb_index_fetch_reset, + .index_fetch_end = duckdb_index_fetch_end, + .index_fetch_tuple = duckdb_index_fetch_tuple, + + .tuple_fetch_row_version = duckdb_fetch_row_version, + .tuple_tid_valid = duckdb_tuple_tid_valid, + .tuple_get_latest_tid = duckdb_get_latest_tid, + .tuple_satisfies_snapshot = duckdb_tuple_satisfies_snapshot, + .index_delete_tuples = duckdb_index_delete_tuples, + + .tuple_insert = duckdb_tuple_insert, + .tuple_insert_speculative = duckdb_tuple_insert_speculative, + .tuple_complete_speculative = duckdb_tuple_complete_speculative, + .multi_insert = duckdb_multi_insert, + .tuple_delete = duckdb_tuple_delete, + .tuple_update = duckdb_tuple_update, + .tuple_lock = duckdb_tuple_lock, + .finish_bulk_insert = duckdb_finish_bulk_insert, + +#if PG_VERSION_NUM >= 160000 + .relation_set_new_filelocator = duckdb_relation_set_new_filelocator, +#else + .relation_set_new_filenode = duckdb_relation_set_new_filenode, +#endif + .relation_nontransactional_truncate = + duckdb_relation_nontransactional_truncate, + .relation_copy_data = duckdb_copy_data, + .relation_copy_for_cluster = duckdb_copy_for_cluster, + .relation_vacuum = duckdb_vacuum, + .scan_analyze_next_block = duckdb_scan_analyze_next_block, + .scan_analyze_next_tuple = duckdb_scan_analyze_next_tuple, + .index_build_range_scan = duckdb_index_build_range_scan, + .index_validate_scan = duckdb_index_validate_scan, + + .relation_size = duckdb_relation_size, + .relation_needs_toast_table = duckdb_relation_needs_toast_table, + /* can be null because relation_needs_toast_table returns false */ + .relation_fetch_toast_slice = NULL, + + .relation_estimate_size = duckdb_estimate_rel_size, + + .scan_bitmap_next_block = duckdb_scan_bitmap_next_block, + .scan_bitmap_next_tuple = duckdb_scan_bitmap_next_tuple, + .scan_sample_next_block = duckdb_scan_sample_next_block, + .scan_sample_next_tuple = duckdb_scan_sample_next_tuple}; + +Datum +duckdb_am_handler(PG_FUNCTION_ARGS) { + PG_RETURN_POINTER(&duckdb_methods); +} +} + +namespace pgduckdb { +bool +IsDuckdbTableAm(const TableAmRoutine *am) { + return am == &duckdb_methods; +} +} // namespace pgduckdb diff --git a/src/vendor/pg_ruleutils_15.c b/src/vendor/pg_ruleutils_15.c index da080362..20e81f5a 100644 --- a/src/vendor/pg_ruleutils_15.c +++ b/src/vendor/pg_ruleutils_15.c @@ -493,8 +493,6 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype, static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); -static char *generate_relation_name(Oid relid, List *namespaces); -static char *generate_qualified_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, bool has_variadic, bool *use_variadic_p, @@ -11865,103 +11863,6 @@ get_relation_name(Oid relid) return relname; } -/* - * generate_relation_name - * Compute the name to display for a relation specified by OID - * - * The result includes all necessary quoting and schema-prefixing. - * - * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. - * We will forcibly qualify the relation name if it equals any CTE name - * visible in the namespace list. - */ -static char * -generate_relation_name(Oid relid, List *namespaces) -{ - HeapTuple tp; - Form_pg_class reltup; - bool need_qual; - ListCell *nslist; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - /* Check for conflicting CTE name */ - need_qual = false; - foreach(nslist, namespaces) - { - deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); - ListCell *ctlist; - - foreach(ctlist, dpns->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); - - if (strcmp(cte->ctename, relname) == 0) - { - need_qual = true; - break; - } - } - if (need_qual) - break; - } - - /* Otherwise, qualify the name if not visible in search path */ - if (!need_qual) - need_qual = !RelationIsVisible(relid); - - if (need_qual) - nspname = get_namespace_name_or_temp(reltup->relnamespace); - else - nspname = NULL; - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - -/* - * generate_qualified_relation_name - * Compute the name to display for a relation specified by OID - * - * As above, but unconditionally schema-qualify the name. - */ -static char * -generate_qualified_relation_name(Oid relid) -{ - HeapTuple tp; - Form_pg_class reltup; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - nspname = get_namespace_name_or_temp(reltup->relnamespace); - if (!nspname) - elog(ERROR, "cache lookup failed for namespace %u", - reltup->relnamespace); - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - /* * generate_function_name * Compute the name to display for a function specified by OID, diff --git a/src/vendor/pg_ruleutils_16.c b/src/vendor/pg_ruleutils_16.c index 891a2cee..c32a73c5 100644 --- a/src/vendor/pg_ruleutils_16.c +++ b/src/vendor/pg_ruleutils_16.c @@ -507,8 +507,6 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype, static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); -static char *generate_relation_name(Oid relid, List *namespaces); -static char *generate_qualified_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, bool has_variadic, bool *use_variadic_p, @@ -12074,103 +12072,6 @@ get_relation_name(Oid relid) return relname; } -/* - * generate_relation_name - * Compute the name to display for a relation specified by OID - * - * The result includes all necessary quoting and schema-prefixing. - * - * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. - * We will forcibly qualify the relation name if it equals any CTE name - * visible in the namespace list. - */ -static char * -generate_relation_name(Oid relid, List *namespaces) -{ - HeapTuple tp; - Form_pg_class reltup; - bool need_qual; - ListCell *nslist; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - /* Check for conflicting CTE name */ - need_qual = false; - foreach(nslist, namespaces) - { - deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); - ListCell *ctlist; - - foreach(ctlist, dpns->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); - - if (strcmp(cte->ctename, relname) == 0) - { - need_qual = true; - break; - } - } - if (need_qual) - break; - } - - /* Otherwise, qualify the name if not visible in search path */ - if (!need_qual) - need_qual = !RelationIsVisible(relid); - - if (need_qual) - nspname = get_namespace_name_or_temp(reltup->relnamespace); - else - nspname = NULL; - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - -/* - * generate_qualified_relation_name - * Compute the name to display for a relation specified by OID - * - * As above, but unconditionally schema-qualify the name. - */ -static char * -generate_qualified_relation_name(Oid relid) -{ - HeapTuple tp; - Form_pg_class reltup; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - nspname = get_namespace_name_or_temp(reltup->relnamespace); - if (!nspname) - elog(ERROR, "cache lookup failed for namespace %u", - reltup->relnamespace); - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - /* * generate_function_name * Compute the name to display for a function specified by OID, diff --git a/src/vendor/pg_ruleutils_17.c b/src/vendor/pg_ruleutils_17.c index bd253e53..5fd01d8f 100644 --- a/src/vendor/pg_ruleutils_17.c +++ b/src/vendor/pg_ruleutils_17.c @@ -512,8 +512,6 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype, static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); -static char *generate_relation_name(Oid relid, List *namespaces); -static char *generate_qualified_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes, bool has_variadic, bool *use_variadic_p, @@ -12732,102 +12730,6 @@ get_relation_name(Oid relid) return relname; } -/* - * generate_relation_name - * Compute the name to display for a relation specified by OID - * - * The result includes all necessary quoting and schema-prefixing. - * - * If namespaces isn't NIL, it must be a list of deparse_namespace nodes. - * We will forcibly qualify the relation name if it equals any CTE name - * visible in the namespace list. - */ -static char * -generate_relation_name(Oid relid, List *namespaces) -{ - HeapTuple tp; - Form_pg_class reltup; - bool need_qual; - ListCell *nslist; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - /* Check for conflicting CTE name */ - need_qual = false; - foreach(nslist, namespaces) - { - deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); - ListCell *ctlist; - - foreach(ctlist, dpns->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist); - - if (strcmp(cte->ctename, relname) == 0) - { - need_qual = true; - break; - } - } - if (need_qual) - break; - } - - /* Otherwise, qualify the name if not visible in search path */ - if (!need_qual) - need_qual = !RelationIsVisible(relid); - - if (need_qual) - nspname = get_namespace_name_or_temp(reltup->relnamespace); - else - nspname = NULL; - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} - -/* - * generate_qualified_relation_name - * Compute the name to display for a relation specified by OID - * - * As above, but unconditionally schema-qualify the name. - */ -static char * -generate_qualified_relation_name(Oid relid) -{ - HeapTuple tp; - Form_pg_class reltup; - char *relname; - char *nspname; - char *result; - - tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tp)) - elog(ERROR, "cache lookup failed for relation %u", relid); - reltup = (Form_pg_class) GETSTRUCT(tp); - relname = NameStr(reltup->relname); - - nspname = get_namespace_name_or_temp(reltup->relnamespace); - if (!nspname) - elog(ERROR, "cache lookup failed for namespace %u", - reltup->relnamespace); - - result = quote_qualified_identifier(nspname, relname); - - ReleaseSysCache(tp); - - return result; -} /* * generate_function_name diff --git a/test/regression/expected/basic.out b/test/regression/expected/basic.out index fc407602..4c475e22 100644 --- a/test/regression/expected/basic.out +++ b/test/regression/expected/basic.out @@ -1,6 +1,5 @@ CREATE TABLE t(a INT); INSERT INTO t SELECT g % 10 from generate_series(1,1000) g; -SET client_min_messages to 'DEBUG1'; SELECT COUNT(*) FROM t; count ------- diff --git a/test/regression/expected/execution_error.out b/test/regression/expected/execution_error.out index 7ee50ddc..8f9cafd9 100644 --- a/test/regression/expected/execution_error.out +++ b/test/regression/expected/execution_error.out @@ -5,5 +5,5 @@ insert into int_as_varchar SELECT * from ( ) t(a); select a::INTEGER from int_as_varchar; ERROR: (PGDuckDB/ExecuteQuery) Conversion Error: Could not convert string 'abc' to INT32 -LINE 1: SELECT (a)::integer AS a FROM public.int_as_varch... +LINE 1: SELECT (a)::integer AS a FROM pgduckdb.public.int... ^ diff --git a/test/regression/expected/temporary_tables.out b/test/regression/expected/temporary_tables.out new file mode 100644 index 00000000..0192c24d --- /dev/null +++ b/test/regression/expected/temporary_tables.out @@ -0,0 +1,83 @@ +CREATE TEMP TABLE t( + bool BOOLEAN, + i2 SMALLINT, + i4 INT DEFAULT 1, + i8 BIGINT NOT NULL, + fl4 REAL DEFAULT random() + 1, + fl8 DOUBLE PRECISION CHECK(fl8 > 0), + t1 TEXT, + t2 VARCHAR, + t3 BPCHAR, + d DATE, + ts TIMESTAMP, + json_obj JSON, + CHECK (i4 > i2) +) USING duckdb; +-- FIXME: This should not be necessary to make this test work, we should always +-- send ISO dates to duckdb or even better would be to make DuckDB respect +-- postgres its datestyle +SET DateStyle = 'ISO, YMD'; +INSERT INTO t VALUES (true, 2, 4, 8, 4.0, 8.0, 't1', 't2', 't3', '2024-05-04', '2020-01-01T01:02:03', '{"a": 1}'); +SELECT * FROM t; + bool | i2 | i4 | i8 | fl4 | fl8 | t1 | t2 | t3 | d | ts | json_obj +------+----+----+----+-----+-----+----+----+----+------------+---------------------+---------- + t | 2 | 4 | 8 | 4 | 8 | t1 | t2 | t3 | 2024-05-04 | 2020-01-01 01:02:03 | {"a": 1} +(1 row) + +CREATE TEMP TABLE t_heap (a int); +INSERT INTO t_heap VALUES (2); +SELECT * FROM t JOIN t_heap ON i2 = a; + bool | i2 | i4 | i8 | fl4 | fl8 | t1 | t2 | t3 | d | ts | json_obj | a +------+----+----+----+-----+-----+----+----+----+------------+---------------------+----------+--- + t | 2 | 4 | 8 | 4 | 8 | t1 | t2 | t3 | 2024-05-04 | 2020-01-01 01:02:03 | {"a": 1} | 2 +(1 row) + +-- The default_table_access_method GUC should be honored. +set default_table_access_method = 'duckdb'; +CREATE TEMP TABLE t2(a int); +CREATE INDEX ON t2(a); +ERROR: duckdb does not implement duckdb_index_build_range_scan +SELECT duckdb.raw_query($$ SELECT database_name, schema_name, sql FROM duckdb_tables() $$); +NOTICE: result: database_name schema_name sql +VARCHAR VARCHAR VARCHAR +[ Rows: 2] +memory pg_temp CREATE TABLE pg_temp.t(bool BOOLEAN, i2 SMALLINT, i4 INTEGER DEFAULT(1), i8 BIGINT NOT NULL, fl4 FLOAT DEFAULT((random() + CAST(1 AS DOUBLE))), fl8 DOUBLE, t1 VARCHAR, t2 VARCHAR, t3 VARCHAR, d DATE, ts TIMESTAMP, json_obj JSON, CHECK((i4 > i2)), CHECK((fl8 > CAST(0 AS DOUBLE)))); +memory pg_temp CREATE TABLE pg_temp.t2(a INTEGER); + + + raw_query +----------- + +(1 row) + +DROP TABLE t, t_heap, t2; +SELECT duckdb.raw_query($$ SELECT database_name, schema_name, sql FROM duckdb_tables() $$); +NOTICE: result: database_name schema_name sql +VARCHAR VARCHAR VARCHAR +[ Rows: 0] + + + raw_query +----------- + +(1 row) + +CREATE TABLE t(a int); +ERROR: Only TEMP tables are supported in DuckDB +CREATE TEMP TABLE t(a int, b int GENERATED ALWAYS AS (a + 1) STORED); +ERROR: DuckDB does not support STORED generated columns +CREATE TEMP TABLE t(a int GENERATED ALWAYS AS IDENTITY); +ERROR: Identity columns are not supported in DuckDB +-- allowed but all other collations are not supported +CREATE TEMP TABLE t(a text COLLATE "default"); +DROP TABLE t; +CREATE TEMP TABLE t(a text COLLATE "C"); +ERROR: DuckDB does not support column collations +CREATE TEMP TABLE t(a text COLLATE "de-x-icu"); +ERROR: DuckDB does not support column collations +CREATE TEMP TABLE t(A text COMPRESSION "pglz"); +ERROR: Column compression is not supported in DuckDB +CREATE TEMP TABLE t(a int) WITH (fillfactor = 50); +ERROR: Storage options are not supported in DuckDB +CREATE TEMP TABLE webpages USING duckdb AS SELECT * FROM read_csv('../../data/web_page.csv') as (column00 int, column01 text, column02 date); +ERROR: DuckDB does not support CREATE TABLE AS yet diff --git a/test/regression/schedule b/test/regression/schedule index 360ad831..1015b5e4 100644 --- a/test/regression/schedule +++ b/test/regression/schedule @@ -13,3 +13,4 @@ test: cte test: create_table_as test: standard_conforming_strings test: query_filter +test: temporary_tables diff --git a/test/regression/sql/basic.sql b/test/regression/sql/basic.sql index d32d3f0a..7f4e3246 100644 --- a/test/regression/sql/basic.sql +++ b/test/regression/sql/basic.sql @@ -2,8 +2,6 @@ CREATE TABLE t(a INT); INSERT INTO t SELECT g % 10 from generate_series(1,1000) g; -SET client_min_messages to 'DEBUG1'; - SELECT COUNT(*) FROM t; SELECT a, COUNT(*) FROM t WHERE a > 5 GROUP BY a ORDER BY a; diff --git a/test/regression/sql/temporary_tables.sql b/test/regression/sql/temporary_tables.sql new file mode 100644 index 00000000..6ceb6a3c --- /dev/null +++ b/test/regression/sql/temporary_tables.sql @@ -0,0 +1,57 @@ +CREATE TEMP TABLE t( + bool BOOLEAN, + i2 SMALLINT, + i4 INT DEFAULT 1, + i8 BIGINT NOT NULL, + fl4 REAL DEFAULT random() + 1, + fl8 DOUBLE PRECISION CHECK(fl8 > 0), + t1 TEXT, + t2 VARCHAR, + t3 BPCHAR, + d DATE, + ts TIMESTAMP, + json_obj JSON, + CHECK (i4 > i2) +) USING duckdb; + + +-- FIXME: This should not be necessary to make this test work, we should always +-- send ISO dates to duckdb or even better would be to make DuckDB respect +-- postgres its datestyle +SET DateStyle = 'ISO, YMD'; + +INSERT INTO t VALUES (true, 2, 4, 8, 4.0, 8.0, 't1', 't2', 't3', '2024-05-04', '2020-01-01T01:02:03', '{"a": 1}'); +SELECT * FROM t; + +CREATE TEMP TABLE t_heap (a int); +INSERT INTO t_heap VALUES (2); + +SELECT * FROM t JOIN t_heap ON i2 = a; + +-- The default_table_access_method GUC should be honored. +set default_table_access_method = 'duckdb'; +CREATE TEMP TABLE t2(a int); + +CREATE INDEX ON t2(a); + +SELECT duckdb.raw_query($$ SELECT database_name, schema_name, sql FROM duckdb_tables() $$); + +DROP TABLE t, t_heap, t2; + +SELECT duckdb.raw_query($$ SELECT database_name, schema_name, sql FROM duckdb_tables() $$); + +CREATE TABLE t(a int); + +CREATE TEMP TABLE t(a int, b int GENERATED ALWAYS AS (a + 1) STORED); +CREATE TEMP TABLE t(a int GENERATED ALWAYS AS IDENTITY); +-- allowed but all other collations are not supported +CREATE TEMP TABLE t(a text COLLATE "default"); +DROP TABLE t; +CREATE TEMP TABLE t(a text COLLATE "C"); +CREATE TEMP TABLE t(a text COLLATE "de-x-icu"); + +CREATE TEMP TABLE t(A text COMPRESSION "pglz"); + +CREATE TEMP TABLE t(a int) WITH (fillfactor = 50); + +CREATE TEMP TABLE webpages USING duckdb AS SELECT * FROM read_csv('../../data/web_page.csv') as (column00 int, column01 text, column02 date);