1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/api.h"
#include "common/arrow/arrow.h"
#include "common/types/value/value.h"
#include "query_summary.h"
namespace lbug {
namespace processor {
class PhysicalPlan;
struct ResultSetDescriptor;
} // namespace processor
} // namespace lbug
namespace lbug {
namespace common {
class LogicalType;
}
namespace parser {
class Statement;
}
namespace binder {
class Expression;
class ParameterExpression;
} // namespace binder
namespace planner {
class LogicalPlan;
}
namespace main {
class CachedPreparedStatementManager;
// Prepared statement cached in client context and NEVER serialized to client side.
struct CachedPreparedStatement {
bool useInternalCatalogEntry = false;
std::shared_ptr<parser::Statement> parsedStatement;
std::unique_ptr<planner::LogicalPlan> logicalPlan;
std::vector<std::shared_ptr<binder::Expression>> columns;
std::vector<std::string> columnNames;
// Cached physical plan for fast re-execution. When canReuseCachedPlanWith returns true,
// this operator-tree template is cloned and its sink state is refreshed for each call,
// avoiding the PlanMapper::mapOperator recursion entirely.
std::unique_ptr<processor::PhysicalPlan> physicalPlanCache;
// Cached ResultSetDescriptors of every pipeline-head sink, in preorder position order.
// Operator::copy() does not propagate descriptors, and multi-pipeline plans (e.g.
// aggregates) clone into several tasks that each need a descriptor to build their
// ResultSet, so we snapshot them all from the freshly mapped tree and re-attach them onto
// each cloned instance.
std::vector<std::unique_ptr<processor::ResultSetDescriptor>> sinkResultSetDescriptors;
// Every typed parameter bound for this statement. Scanned for parameters whose values
// were frozen into a plan at plan-build time (ParameterExpression::wasBakedIntoPlan) —
// e.g. SKIP/LIMIT numbers baked to uint64_t by the mapper, or evaluated numbers baked
// into operators by optimizers. The scan is operator-agnostic, so future operators that
// bake parameter values (through a marking helper) are covered without touching this
// code (see https://github.com/LadybugDB/ladybug/issues/985).
std::vector<std::shared_ptr<binder::ParameterExpression>> boundParameters;
// True when a bound parameter was baked into a plan during prepare (bind/optimize), or
// during a previous physical mapping. Such statements always rebind/replan on
// re-execution and never populate or serve the physical-plan cache, since a cached
// plan would keep serving the first execution's frozen values.
bool hasBakedParameters = false;
CachedPreparedStatement();
~CachedPreparedStatement();
std::vector<std::string> getColumnNames() const;
std::vector<common::LogicalType> getColumnTypes() const;
};
/**
* @brief A prepared statement is a parameterized query which can avoid planning the same query for
* repeated execution.
*/
class PreparedStatement {
friend class Connection;
friend class ClientContext;
public:
LBUG_API ~PreparedStatement();
/**
* @return the query is prepared successfully or not.
*/
LBUG_API bool isSuccess() const;
/**
* @return the error message if the query is not prepared successfully.
*/
LBUG_API std::string getErrorMessage() const;
/**
* @return the prepared statement is read-only or not.
*/
LBUG_API bool isReadOnly() const;
/**
* @return the column names of the query result, populated at prepare time
* (parse + bind + plan) without executing the query.
*/
LBUG_API std::vector<std::string> getColumnNames() const;
/**
* @return the column data types of the query result, populated at prepare time
* without executing the query.
*/
LBUG_API std::vector<common::LogicalType> getColumnTypes() const;
/**
* @brief Returns the arrow schema of the query result, derived from the bound
* and planned statement without executing the query.
* @return datatypes of the columns as an arrow schema
*
* It is the caller's responsibility to call the release function to release the underlying
* data. If converting to another arrow type, this is usually handled automatically.
*/
LBUG_API std::unique_ptr<ArrowSchema> getArrowSchema() const;
const std::unordered_set<std::string>& getUnknownParameters() const {
return unknownParameters;
}
bool canReuseCachedPlanWith(
const std::unordered_map<std::string, std::unique_ptr<common::Value>>& inputParams) const;
std::unordered_set<std::string> getKnownParameters();
void updateParameter(const std::string& name, common::Value* value);
void addParameter(const std::string& name, common::Value* value);
LBUG_API void setParameter(const std::string& name, common::Value value);
std::string getName() const { return cachedPreparedStatementName; }
common::StatementType getStatementType() const;
static std::unique_ptr<PreparedStatement> getPreparedStatementWithError(
const std::string& errorMessage);
private:
bool success = true;
bool readOnly = true;
std::string errMsg;
PreparedSummary preparedSummary;
std::string cachedPreparedStatementName;
// Weak back-reference to the manager this statement was registered with (via
// ClientContext::prepareWithParams). The destructor uses it to unregister itself so the
// cached plan is freed promptly. Weak so that a statement destroyed after its connection
// (possible through the C API) does not access a freed manager.
std::weak_ptr<CachedPreparedStatementManager> ownerManager;
std::unordered_set<std::string> unknownParameters;
std::unordered_map<std::string, std::shared_ptr<common::Value>> parameterMap;
};
} // namespace main
} // namespace lbug