|
18 | 18 | #include "cnxninfo.h" |
19 | 19 |
|
20 | 20 |
|
| 21 | +// Insert or update an entry. If the cache is full, the least recently used item is evicted. |
| 22 | +void BindInfoCache::put(const std::string& key, const BindInfoSet& value) { |
| 23 | + auto it = map_.find(key); |
| 24 | + if (it != map_.end()) { |
| 25 | + // Key exists: update value and move to front. |
| 26 | + it->second->second = value; |
| 27 | + list_.splice(list_.begin(), list_, it->second); |
| 28 | + return; |
| 29 | + } |
| 30 | + // Key new: if full, evict the last (least recently used) item. |
| 31 | + if (map_.size() >= capacity_) { |
| 32 | + const auto& lastKey = list_.back().first; |
| 33 | + map_.erase(lastKey); |
| 34 | + list_.pop_back(); |
| 35 | + } |
| 36 | + // Insert new item at the front. |
| 37 | + list_.emplace_front(key, value); |
| 38 | + map_[key] = list_.begin(); |
| 39 | +} |
| 40 | + |
| 41 | +// Retrieve a value by key. Returns std::nullopt if not found. |
| 42 | +std::optional<BindInfoSet> BindInfoCache::get(const std::string& key) { |
| 43 | + auto it = map_.find(key); |
| 44 | + if (it == map_.end()) |
| 45 | + return std::nullopt; |
| 46 | + |
| 47 | + // Move the accessed node to the front (unless it's already there). |
| 48 | + if (it->second != list_.begin()) |
| 49 | + list_.splice(list_.begin(), list_, it->second); |
| 50 | + return it->second->second; |
| 51 | +} |
| 52 | + |
| 53 | +// Adjust the capacity of the cache, discarding any entries we can no longer accommodate. |
| 54 | +void BindInfoCache::resize(size_t new_capacity) { |
| 55 | + if (new_capacity == capacity_) return; |
| 56 | + |
| 57 | + if (new_capacity < capacity_) { |
| 58 | + // Shrink: remove least recent entries from the back |
| 59 | + while (map_.size() > new_capacity) { |
| 60 | + const auto& last_key = list_.back().first; |
| 61 | + map_.erase(last_key); |
| 62 | + list_.pop_back(); |
| 63 | + } |
| 64 | + } |
| 65 | + // For both grow and shrink we need to update the private member variable. |
| 66 | + capacity_ = new_capacity; |
| 67 | +} |
| 68 | + |
21 | 69 | static char connection_doc[] = |
22 | 70 | "Connection objects manage connections to the database.\n" |
23 | 71 | "\n" |
@@ -249,6 +297,9 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou |
249 | 297 | cnxn->searchescape = 0; |
250 | 298 | cnxn->maxwrite = 0; |
251 | 299 | cnxn->timeout = 0; |
| 300 | + cnxn->bindinfo_cache = NULL; |
| 301 | + cnxn->none_binding = SQL_UNKNOWN_TYPE; |
| 302 | + cnxn->var_binding_length = 0; |
252 | 303 | cnxn->map_sqltype_to_converter = 0; |
253 | 304 |
|
254 | 305 | cnxn->attrs_before = attrs_before_o.Detach(); |
@@ -426,6 +477,9 @@ static int Connection_clear(PyObject* self) |
426 | 477 | Py_XDECREF(cnxn->map_sqltype_to_converter); |
427 | 478 | cnxn->map_sqltype_to_converter = 0; |
428 | 479 |
|
| 480 | + delete cnxn->bindinfo_cache; |
| 481 | + cnxn->bindinfo_cache = 0; |
| 482 | + |
429 | 483 | return 0; |
430 | 484 | } |
431 | 485 |
|
@@ -991,6 +1045,90 @@ static int Connection_settimeout(PyObject* self, PyObject* value, void* closure) |
991 | 1045 | return 0; |
992 | 1046 | } |
993 | 1047 |
|
| 1048 | +static PyObject* Connection_getbindinfocachesize(PyObject* self, void* closure) |
| 1049 | +{ |
| 1050 | + UNUSED(closure); |
| 1051 | + Connection* cnxn = Connection_Validate(self); |
| 1052 | + if (!cnxn) |
| 1053 | + return 0; |
| 1054 | + if (!cnxn->bindinfo_cache) |
| 1055 | + return PyLong_FromLong(0); |
| 1056 | + return PyLong_FromSize_t(cnxn->bindinfo_cache->capacity()); |
| 1057 | +} |
| 1058 | + |
| 1059 | +static int Connection_setbindinfocachesize(PyObject* self, PyObject* value, void* closure) |
| 1060 | +{ |
| 1061 | + UNUSED(closure); |
| 1062 | + Connection* cnxn = Connection_Validate(self); |
| 1063 | + if (!cnxn) |
| 1064 | + return -1; |
| 1065 | + size_t cache_size = PyLong_AsSize_t(value); |
| 1066 | + if (cache_size == (size_t)-1 && PyErr_Occurred()) |
| 1067 | + return -1; |
| 1068 | + if (cache_size > 0) { |
| 1069 | + if (!cnxn->bindinfo_cache) { |
| 1070 | + cnxn->bindinfo_cache = new BindInfoCache(cache_size); |
| 1071 | + } else { |
| 1072 | + cnxn->bindinfo_cache->resize(cache_size); |
| 1073 | + } |
| 1074 | + } else { |
| 1075 | + delete cnxn->bindinfo_cache; |
| 1076 | + cnxn->bindinfo_cache = NULL; |
| 1077 | + } |
| 1078 | + return 0; |
| 1079 | +} |
| 1080 | + |
| 1081 | +static PyObject* Connection_getvarbindinglength(PyObject* self, void* closure) |
| 1082 | +{ |
| 1083 | + UNUSED(closure); |
| 1084 | + Connection* cnxn = Connection_Validate(self); |
| 1085 | + if (!cnxn) |
| 1086 | + return 0; |
| 1087 | + if (cnxn->var_binding_length) |
| 1088 | + return PyLong_FromSize_t(cnxn->var_binding_length); |
| 1089 | + Py_INCREF(Py_None); |
| 1090 | + return Py_None; |
| 1091 | +} |
| 1092 | + |
| 1093 | +static int Connection_setvarbindinglength(PyObject* self, PyObject* value, void* closure) |
| 1094 | +{ |
| 1095 | + UNUSED(closure); |
| 1096 | + Connection* cnxn = Connection_Validate(self); |
| 1097 | + if (!cnxn) |
| 1098 | + return -1; |
| 1099 | + if (value == Py_None) |
| 1100 | + cnxn->var_binding_length = 0; |
| 1101 | + else { |
| 1102 | + size_t length = PyLong_AsSize_t(value); |
| 1103 | + if (length == (size_t)-1 && PyErr_Occurred()) |
| 1104 | + return -1; |
| 1105 | + cnxn->var_binding_length = length; |
| 1106 | + } |
| 1107 | + return 0; |
| 1108 | +} |
| 1109 | + |
| 1110 | +static PyObject* Connection_getnonebinding(PyObject* self, void* closure) |
| 1111 | +{ |
| 1112 | + UNUSED(closure); |
| 1113 | + Connection* cnxn = Connection_Validate(self); |
| 1114 | + if (!cnxn) |
| 1115 | + return 0; |
| 1116 | + return PyLong_FromLong((long)cnxn->none_binding); |
| 1117 | +} |
| 1118 | + |
| 1119 | +static int Connection_setnonebinding(PyObject* self, PyObject* value, void* closure) |
| 1120 | +{ |
| 1121 | + UNUSED(closure); |
| 1122 | + Connection* cnxn = Connection_Validate(self); |
| 1123 | + if (!cnxn) |
| 1124 | + return -1; |
| 1125 | + long binding = PyLong_AsLong(value); |
| 1126 | + if (binding == -1 && PyErr_Occurred()) |
| 1127 | + return -1; |
| 1128 | + cnxn->none_binding = binding; |
| 1129 | + return 0; |
| 1130 | +} |
| 1131 | + |
994 | 1132 | static bool _remove_converter(PyObject* self, SQLSMALLINT sqltype) |
995 | 1133 | { |
996 | 1134 | Connection* cnxn = (Connection*)self; |
@@ -1394,6 +1532,21 @@ static PyGetSetDef Connection_getseters[] = { |
1394 | 1532 | { "timeout", Connection_gettimeout, Connection_settimeout, |
1395 | 1533 | "The timeout in seconds, zero means no timeout.", 0 }, |
1396 | 1534 | { "maxwrite", Connection_getmaxwrite, Connection_setmaxwrite, "The maximum bytes to write before using SQLPutData.", 0 }, |
| 1535 | + { "bindinfo_cache_size", Connection_getbindinfocachesize, Connection_setbindinfocachesize, |
| 1536 | + "The number of prepared queries for which the connection should\n" |
| 1537 | + "remember bind information (zero disables cache).", 0 }, |
| 1538 | + { "var_binding_length", Connection_getvarbindinglength, Connection_setvarbindinglength, |
| 1539 | + "If set to an integer greater than zero, bind information is determined\n" |
| 1540 | + "based on the values rather than by preparing the statements, and column\n" |
| 1541 | + "lengths for variable-length value types are rounded up to multiples of\n" |
| 1542 | + "the value of this property. Set to None (the default) or 0 to disable\n" |
| 1543 | + "this override.", 0 }, |
| 1544 | + { "none_binding", Connection_getnonebinding, Connection_setnonebinding, |
| 1545 | + "By default, any query for which at least one parameter value is None will\n" |
| 1546 | + "result in calls to SQLPrepare() and SQLDescribeParam() (providing the\n" |
| 1547 | + "driver supports both calls), ignoring the var_binding_length property.\n" |
| 1548 | + "Set this property to pyodbc.SQLVARCHAR to avoid those calls when the\n" |
| 1549 | + "var_binding_length has been set to an integer greater than zero.", 0 }, |
1397 | 1550 | { 0 } |
1398 | 1551 | }; |
1399 | 1552 |
|
|
0 commit comments