Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contrib/babelfishpg_tsql/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ OBJS += src/cursor.o
OBJS += src/applock.o
OBJS += src/pltsql_coerce.o
OBJS += runtime/functions.o
OBJS += runtime/temp_table_hooks.o
OBJS += src/err_handler.o
OBJS += src/pltsql_function_probin_handler.o
OBJS += src/pltsql_utils.o
Expand Down
1 change: 0 additions & 1 deletion contrib/babelfishpg_tsql/runtime/functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -6093,4 +6093,3 @@ openxml_simple(PG_FUNCTION_ARGS)
NO_XML_SUPPORT();
#endif /* USE_LIBXML */
}

197 changes: 197 additions & 0 deletions contrib/babelfishpg_tsql/runtime/temp_table_hooks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/skey.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_attribute.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/queryenvironment.h"
#include "utils/tuplestore.h"

#include "../src/pltsql.h"
#include "../src/multidb.h"

PG_FUNCTION_INFO_V1(get_tsql_temp_table_attributes);
PG_FUNCTION_INFO_V1(is_temp_table_name);

/*
* get_tsql_temp_table_attributes - Get pg_attribute rows for a temp table by name
*
* This function returns all pg_attribute columns for a temp table.
* It works for both ENR temp tables (reads from ENR cache) and non-ENR temp tables
* (reads from actual pg_attribute catalog).
*/
Datum
get_tsql_temp_table_attributes(PG_FUNCTION_ARGS)
{
char *input;
char *db_name;
char *schema_name;
char *object_name;
Oid relid = InvalidOid;
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
MemoryContext per_query_ctx;
MemoryContext oldcontext;
EphemeralNamedRelation enr = NULL;
Relation pg_attribute_rel;
bool is_enr = false;

/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialize mode required, but it is not allowed in this context")));

if (PG_ARGISNULL(0))
PG_RETURN_NULL();

input = text_to_cstring(PG_GETARG_VARCHAR_PP(0));

/* Parse the table name */
downcase_truncate_split_object_name(input, NULL, &db_name, &schema_name, &object_name);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need db_name? we can pass NULL instead

pfree(input);

/* Must be a temp table (starts with #) */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid confusion, let's use tsql temp tables

if (object_name[0] != '#')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

object_name could be empty or null? any chance this could be called from PG dialect?

{
pfree(db_name);
pfree(schema_name);
pfree(object_name);
Comment on lines +71 to +73
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate pointers before pfree

PG_RETURN_NULL();
}

/* Try ENR first */
if (currentQueryEnv != NULL)
{
enr = get_ENR(currentQueryEnv, object_name, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have we tested this part of code for following change of commands?

TSQL Dialect -> TSQL Proc -> calls get_tsql_temp_table_attributes
TSQL Dialect -> TSQL Proc -> PG proc -> call get_tsql_temp_table_attributes
TSQL Dialect -> PG proc -> call get_tsql_temp_table_attributes
TSQL Dialect -> PG proc -> TSQL proc -> call get_tsql_temp_table_attributes

remember that TSQL temp table can be created at stage in between

if (enr != NULL && enr->md.enrtype == ENR_TSQL_TEMP)
{
is_enr = true;
relid = enr->md.reliddesc;
}
}

/* If not ENR, try pg_temp namespace for non-ENR temp tables */
if (!is_enr)
{
Oid temp_ns = LookupNamespaceNoError("pg_temp");
if (OidIsValid(temp_ns))
relid = get_relname_relid(object_name, temp_ns);
}

pfree(db_name);
pfree(schema_name);
pfree(object_name);

if (!OidIsValid(relid))
PG_RETURN_NULL();

/* Setup return - use pg_attribute's tuple descriptor */
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
oldcontext = MemoryContextSwitchTo(per_query_ctx);

pg_attribute_rel = table_open(AttributeRelationId, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(pg_attribute_rel));

tupstore = tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
false, work_mem);

rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = BlessTupleDesc(tupdesc);
Comment on lines +103 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason why we are preferring this way of SRF instead of what PG Doc says, https://www.postgresql.org/docs/current/xfunc-c.html#XFUNC-C-RETURN-SET


MemoryContextSwitchTo(oldcontext);

if (is_enr)
{
/* ENR path: return tuples directly from ENR cache */
ListCell *lc;

foreach(lc, enr->md.cattups[ENR_CATTUP_ATTRIBUTE])
{
HeapTuple tup = (HeapTuple) lfirst(lc);

tuplestore_puttuple(tupstore, tup);
Comment on lines +126 to +128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to make a copy before puttuple

}
}
else
{
/* Non-ENR path: scan pg_attribute catalog */
ScanKeyData skey[1];
SysScanDesc scan;
HeapTuple tup;

ScanKeyInit(&skey[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));

scan = systable_beginscan(pg_attribute_rel, AttributeRelidNumIndexId,
true, NULL, 1, skey);

while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
tuplestore_puttuple(tupstore, tup);
}

systable_endscan(scan);
}

table_close(pg_attribute_rel, AccessShareLock);

tuplestore_donestoring(tupstore);

PG_RETURN_NULL();
}

/*
* is_temp_table_name - Check if a name refers to a temp table
*
* Returns true if the object name part starts with #.
*
* The input name can be a four-part qualified name, with quoted identifiers
* or square bracket qualifiers. We use downcase_truncate_split_object_name()
* to parse and extract the object_name part.
*/
Datum
is_temp_table_name(PG_FUNCTION_ARGS)
{
char *input;
char *db_name;
char *schema_name;
char *object_name;
bool result = false;

if (PG_ARGISNULL(0))
PG_RETURN_BOOL(false);

input = text_to_cstring(PG_GETARG_VARCHAR_PP(0));

/* Parse the name to extract object_name */
downcase_truncate_split_object_name(input, NULL, &db_name, &schema_name, &object_name);
pfree(input);

/* Check if object_name starts with # */
if (object_name[0] == '#')
result = true;
Comment on lines +188 to +190
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it fine to make such decision just base on name? we should look into currentQueryEnv.

There could be issue with interoperability here since name with # in PG is valid and can be regular table. And sys.sp_tablecollations_100 could be called for those PG relations, right? how are we making sure that it works fine under those scenarios?


pfree(db_name);
pfree(schema_name);
pfree(object_name);

PG_RETURN_BOOL(result);
}
65 changes: 55 additions & 10 deletions contrib/babelfishpg_tsql/sql/babelfishpg_tsql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -802,24 +802,69 @@ CREATE OR REPLACE VIEW sys.spt_tablecollations_view AS
c.is_sparse = 0;
GRANT SELECT ON sys.spt_tablecollations_view TO PUBLIC;

-- Function to get pg_attribute rows for #temp tables (ENR and non-ENR)
CREATE OR REPLACE FUNCTION sys.babelfish_get_temp_table_attributes(IN table_name sys.varchar(4000))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for safer side, it should be sys.nvarchar(128)

RETURNS SETOF pg_catalog.pg_attribute
AS 'babelfishpg_tsql', 'get_tsql_temp_table_attributes'
LANGUAGE C STABLE PARALLEL UNSAFE;
GRANT EXECUTE ON FUNCTION sys.babelfish_get_temp_table_attributes(IN sys.varchar(4000)) TO PUBLIC;

-- Function to check if a name refers to a #temp table
CREATE OR REPLACE FUNCTION sys.is_temp_table_name(IN name sys.varchar(4000))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, use sys.nvarchar

RETURNS BOOLEAN
AS 'babelfishpg_tsql', 'is_temp_table_name'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
GRANT EXECUTE ON FUNCTION sys.is_temp_table_name(IN sys.varchar(4000)) TO PUBLIC;

-- Wrapper function for sp_tablecollations_100 that uses the babelfish_get_temp_table_attributes function
CREATE OR REPLACE FUNCTION sys.sp_tablecollations_100_enr(IN table_name sys.varchar(4000))
RETURNS TABLE(colid INT, name sys.varchar, collation_name sys.nvarchar(128))
AS $$
SELECT
CAST(a.attnum AS INT) AS colid,
CAST(a.attname AS sys.varchar) AS name,
CAST(c.collname AS sys.nvarchar(128)) AS collation_name
FROM sys.babelfish_get_temp_table_attributes(table_name) a
LEFT JOIN pg_catalog.pg_collation c ON a.attcollation = c.oid
WHERE a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum;
$$
LANGUAGE SQL STABLE PARALLEL UNSAFE;
GRANT EXECUTE ON FUNCTION sys.sp_tablecollations_100_enr(IN sys.varchar(4000)) TO PUBLIC;

-- We are limited by what postgres procedures can return here, but IEW may not
-- need it for initial compatibility
-- Modified to handle #temp tables using sp_tablecollations_100_enr function
CREATE OR REPLACE PROCEDURE sys.sp_tablecollations_100
(
IN "@object" nvarchar(4000)
)
AS $$
BEGIN
select
s_tcv.colid AS colid,
s_tcv.name AS name,
s_tcv.tds_collation_100 AS tds_collation,
s_tcv.collation_100 AS collation
from
sys.spt_tablecollations_view s_tcv
where
s_tcv.object_id = (SELECT sys.object_id(@object))
order by colid;
-- Check if this is a #temp table
IF sys.is_temp_table_name(@object) = 1
BEGIN
-- Use ENR function for temp tables
SELECT
t.colid AS colid,
t.name AS name,
CAST(CollationProperty(t.collation_name, 'tdscollation') AS sys.binary(5)) AS tds_collation,
t.collation_name AS collation
FROM sys.sp_tablecollations_100_enr(@object) t
ORDER BY t.colid;
END
ELSE
BEGIN
-- Existing logic for regular tables
SELECT
s_tcv.colid AS colid,
s_tcv.name AS name,
s_tcv.tds_collation_100 AS tds_collation,
s_tcv.collation_100 AS collation
FROM sys.spt_tablecollations_view s_tcv
WHERE s_tcv.object_id = (SELECT sys.object_id(@object))
ORDER BY colid;
END
END;
$$
LANGUAGE 'pltsql';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,73 @@ END;
$$;
GRANT EXECUTE ON FUNCTION sys.textsize() TO PUBLIC;

-- Function to get pg_attribute rows for #temp tables (ENR and non-ENR)
CREATE OR REPLACE FUNCTION sys.babelfish_get_temp_table_attributes(IN table_name sys.varchar(4000))
RETURNS SETOF pg_catalog.pg_attribute
AS 'babelfishpg_tsql', 'get_tsql_temp_table_attributes'
LANGUAGE C STABLE PARALLEL UNSAFE;
GRANT EXECUTE ON FUNCTION sys.babelfish_get_temp_table_attributes(IN sys.varchar(4000)) TO PUBLIC;

-- Function to check if a name refers to a #temp table
CREATE OR REPLACE FUNCTION sys.is_temp_table_name(IN name sys.varchar(4000))
RETURNS BOOLEAN
AS 'babelfishpg_tsql', 'is_temp_table_name'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
GRANT EXECUTE ON FUNCTION sys.is_temp_table_name(IN sys.varchar(4000)) TO PUBLIC;

-- Wrapper function for sp_tablecollations_100 that uses the babelfish_get_temp_table_attributes function
CREATE OR REPLACE FUNCTION sys.sp_tablecollations_100_enr(IN table_name sys.varchar(4000))
RETURNS TABLE(colid INT, name sys.varchar, collation_name sys.nvarchar(128))
AS $$
SELECT
CAST(a.attnum AS INT) AS colid,
CAST(a.attname AS sys.varchar) AS name,
CAST(c.collname AS sys.nvarchar(128)) AS collation_name
FROM sys.babelfish_get_temp_table_attributes(table_name) a
LEFT JOIN pg_catalog.pg_collation c ON a.attcollation = c.oid
WHERE a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum;
$$
LANGUAGE SQL STABLE PARALLEL UNSAFE;
GRANT EXECUTE ON FUNCTION sys.sp_tablecollations_100_enr(IN sys.varchar(4000)) TO PUBLIC;

-- We are limited by what postgres procedures can return here, but IEW may not
-- need it for initial compatibility
-- Modified to handle #temp tables using sp_tablecollations_100_enr function
CREATE OR REPLACE PROCEDURE sys.sp_tablecollations_100
(
IN "@object" nvarchar(4000)
)
AS $$
BEGIN
-- Check if this is a #temp table
IF sys.is_temp_table_name(@object) = 1
BEGIN
-- Use ENR function for temp tables
SELECT
t.colid AS colid,
t.name AS name,
CAST(CollationProperty(t.collation_name, 'tdscollation') AS sys.binary(5)) AS tds_collation,
t.collation_name AS collation
FROM sys.sp_tablecollations_100_enr(@object) t
ORDER BY t.colid;
END
ELSE
BEGIN
-- Existing logic for regular tables
SELECT
s_tcv.colid AS colid,
s_tcv.name AS name,
s_tcv.tds_collation_100 AS tds_collation,
s_tcv.collation_100 AS collation
FROM sys.spt_tablecollations_view s_tcv
WHERE s_tcv.object_id = (SELECT sys.object_id(@object))
ORDER BY colid;
END
END;
$$
LANGUAGE 'pltsql';

-- Drops the temporary procedure used by the upgrade script.
-- Please have this be one of the last statements executed in this upgrade script.
DROP PROCEDURE sys.babelfish_drop_deprecated_object(varchar, varchar, varchar, varchar);
Expand Down
4 changes: 3 additions & 1 deletion contrib/babelfishpg_tsql/src/procedures.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/formatting.h"
#include "pltsql_instr.h"
#include "pltsql.h"
Expand All @@ -55,6 +56,7 @@
#include "catalog.h"
#include "catalog/toasting.h"
#include "extendedproperty.h"
#include "collation.h"
#include "multidb.h"
#include "session.h"
#include "rolecmds.h"
Expand Down Expand Up @@ -5115,4 +5117,4 @@ tsql_openxml_get_colpattern(PG_FUNCTION_ARGS)
}

PG_RETURN_TEXT_P(cstring_to_text(xpath_expr));
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert file back to original

Loading
Loading