Line data Source code
1 : // Regression tests for the UDF registry's object lifetime contract.
2 : //
3 : // Catalogs (notably the long-lived per-project registration catalog in
4 : // udf_registration_catalog.cc) hold raw `googlesql::Function*` pointers
5 : // handed out via SimpleCatalog::AddFunction. Re-registering a function
6 : // (the routine-update / upsert path) used to destroy the old object,
7 : // leaving those raw pointers dangling; the next
8 : // ReplayFunctionsIntoCatalog dereferenced them and crashed the engine
9 : // (use-after-free surfacing as an InsertOrDie duplicate-key abort or a
10 : // SIGSEGV). See the node-bigquery-tests "Delete Routine" before-all
11 : // hook failure in CI.
12 :
13 : #include "backend/catalog/udf_registry.h"
14 :
15 : #include <memory>
16 : #include <string>
17 : #include <utility>
18 : #include <vector>
19 :
20 : #include "googlesql/public/function.h"
21 : #include "googlesql/public/function_signature.h"
22 : #include "googlesql/public/simple_catalog.h"
23 : #include "googlesql/public/types/type_factory.h"
24 : #include "gtest/gtest.h"
25 :
26 : namespace bigquery_emulator {
27 : namespace backend {
28 : namespace catalog {
29 : namespace {
30 :
31 : std::unique_ptr<const ::googlesql::Function> MakeScalarFn(
32 4 : const std::string& name) {
33 4 : ::googlesql::FunctionSignature signature(
34 4 : ::googlesql::FunctionArgumentType(::googlesql::types::Int64Type()),
35 4 : /*arguments=*/{},
36 4 : /*context_id=*/static_cast<int64_t>(0));
37 4 : return std::make_unique<::googlesql::Function>(
38 4 : std::vector<std::string>{name},
39 4 : /*group=*/"External_function",
40 4 : ::googlesql::Function::SCALAR,
41 4 : std::vector<::googlesql::FunctionSignature>{signature});
42 4 : }
43 :
44 1 : TEST(UdfRegistryTest, ReRegisterKeepsOldPointerValidAndReplaysNewFunction) {
45 : // Distinct project id: the registry is process-global state.
46 1 : const std::string project = "udf_registry_test_reregister";
47 1 : const std::string fn_name = "regtest_ds.fn1";
48 :
49 1 : ::googlesql::TypeFactory type_factory;
50 1 : ::googlesql::SimpleCatalog catalog(project, &type_factory);
51 :
52 1 : ASSERT_TRUE(RegisterProjectFunction(project,
53 1 : /*dataset_id=*/"",
54 1 : /*is_temp=*/false,
55 1 : /*analyzer_output=*/nullptr,
56 1 : MakeScalarFn(fn_name))
57 1 : .ok());
58 1 : ReplayFunctionsIntoCatalog(project, catalog);
59 :
60 1 : const ::googlesql::Function* first = nullptr;
61 1 : ASSERT_TRUE(catalog.GetFunction(fn_name, &first).ok());
62 1 : ASSERT_NE(first, nullptr);
63 :
64 : // Routine update path: re-register the same name. The old object
65 : // must stay alive because `catalog` still holds a raw pointer.
66 1 : ASSERT_TRUE(RegisterProjectFunction(project,
67 1 : /*dataset_id=*/"",
68 1 : /*is_temp=*/false,
69 1 : /*analyzer_output=*/nullptr,
70 1 : MakeScalarFn(fn_name))
71 1 : .ok());
72 1 : EXPECT_EQ(first->Name(), fn_name); // dereference: dies pre-fix under ASAN
73 :
74 : // Replay must replace the catalog entry with the new registration
75 : // instead of aborting on a duplicate key.
76 1 : ReplayFunctionsIntoCatalog(project, catalog);
77 1 : const ::googlesql::Function* second = nullptr;
78 1 : ASSERT_TRUE(catalog.GetFunction(fn_name, &second).ok());
79 1 : ASSERT_NE(second, nullptr);
80 1 : EXPECT_NE(second, first);
81 1 : EXPECT_TRUE(IsProjectRegisteredFunction(project, fn_name));
82 1 : }
83 :
84 1 : TEST(UdfRegistryTest, DropRemovesFunctionFromCatalogOnNextReplay) {
85 1 : const std::string project = "udf_registry_test_drop";
86 1 : const std::string fn_name = "regtest_ds.fn2";
87 :
88 1 : ::googlesql::TypeFactory type_factory;
89 1 : ::googlesql::SimpleCatalog catalog(project, &type_factory);
90 :
91 1 : ASSERT_TRUE(RegisterProjectFunction(project,
92 1 : /*dataset_id=*/"",
93 1 : /*is_temp=*/false,
94 1 : /*analyzer_output=*/nullptr,
95 1 : MakeScalarFn(fn_name))
96 1 : .ok());
97 1 : ReplayFunctionsIntoCatalog(project, catalog);
98 :
99 1 : const ::googlesql::Function* registered = nullptr;
100 1 : ASSERT_TRUE(catalog.GetFunction(fn_name, ®istered).ok());
101 1 : ASSERT_NE(registered, nullptr);
102 :
103 1 : ASSERT_TRUE(DropProjectFunction(project, fn_name).ok());
104 1 : EXPECT_FALSE(IsProjectRegisteredFunction(project, fn_name));
105 :
106 : // The dropped function is retired, not destroyed, and the next
107 : // replay purges the stale catalog entry.
108 1 : ReplayFunctionsIntoCatalog(project, catalog);
109 1 : const ::googlesql::Function* after_drop = nullptr;
110 1 : ASSERT_TRUE(catalog.GetFunction(fn_name, &after_drop).ok());
111 1 : EXPECT_EQ(after_drop, nullptr);
112 1 : }
113 :
114 1 : TEST(UdfRegistryTest, FindProjectFunctionResolvesDatasetQualifiedRoutine) {
115 1 : const std::string project = "udf_registry_test_qualified";
116 1 : const std::string dataset = "ds_udf";
117 1 : const std::string routine = "add_one";
118 :
119 1 : ::googlesql::TypeFactory type_factory;
120 1 : ::googlesql::SimpleCatalog catalog(project, &type_factory);
121 :
122 1 : ASSERT_TRUE(RegisterProjectFunction(project,
123 1 : dataset,
124 1 : /*is_temp=*/false,
125 1 : /*analyzer_output=*/nullptr,
126 1 : MakeScalarFn(routine))
127 1 : .ok());
128 1 : ReplayFunctionsIntoCatalog(project, catalog);
129 :
130 1 : const ::googlesql::Function* by_short = nullptr;
131 1 : ASSERT_TRUE(catalog.GetFunction(routine, &by_short).ok());
132 1 : ASSERT_NE(by_short, nullptr);
133 :
134 1 : const ::googlesql::Function* qualified =
135 1 : FindProjectFunction(project, dataset, routine);
136 1 : ASSERT_NE(qualified, nullptr);
137 1 : EXPECT_EQ(qualified, by_short);
138 :
139 1 : EXPECT_EQ(FindProjectFunction(project, "other_ds", routine), nullptr);
140 1 : EXPECT_EQ(FindProjectFunction(project, dataset, "missing"), nullptr);
141 :
142 1 : const ::googlesql::Function* from_path = FindProjectFunctionFromPath(
143 1 : {project, dataset, routine}, project, dataset);
144 1 : ASSERT_NE(from_path, nullptr);
145 1 : EXPECT_EQ(from_path, qualified);
146 :
147 1 : const ::googlesql::Function* dotted = FindProjectFunctionFromPath(
148 1 : {project + "." + dataset + "." + routine}, project, dataset);
149 1 : ASSERT_NE(dotted, nullptr);
150 1 : EXPECT_EQ(dotted, qualified);
151 1 : }
152 :
153 : } // namespace
154 : } // namespace catalog
155 : } // namespace backend
156 : } // namespace bigquery_emulator
|