sqlspec 0.32.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. sqlspec/__init__.py +104 -0
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +14 -0
  4. sqlspec/_serialization.py +312 -0
  5. sqlspec/_typing.py +784 -0
  6. sqlspec/adapters/__init__.py +0 -0
  7. sqlspec/adapters/adbc/__init__.py +5 -0
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  10. sqlspec/adapters/adbc/adk/store.py +880 -0
  11. sqlspec/adapters/adbc/config.py +436 -0
  12. sqlspec/adapters/adbc/data_dictionary.py +537 -0
  13. sqlspec/adapters/adbc/driver.py +841 -0
  14. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  15. sqlspec/adapters/adbc/litestar/store.py +504 -0
  16. sqlspec/adapters/adbc/type_converter.py +153 -0
  17. sqlspec/adapters/aiosqlite/__init__.py +29 -0
  18. sqlspec/adapters/aiosqlite/_types.py +13 -0
  19. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/adk/store.py +536 -0
  21. sqlspec/adapters/aiosqlite/config.py +310 -0
  22. sqlspec/adapters/aiosqlite/data_dictionary.py +260 -0
  23. sqlspec/adapters/aiosqlite/driver.py +463 -0
  24. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  25. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  26. sqlspec/adapters/aiosqlite/pool.py +500 -0
  27. sqlspec/adapters/asyncmy/__init__.py +25 -0
  28. sqlspec/adapters/asyncmy/_types.py +12 -0
  29. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/adk/store.py +503 -0
  31. sqlspec/adapters/asyncmy/config.py +246 -0
  32. sqlspec/adapters/asyncmy/data_dictionary.py +241 -0
  33. sqlspec/adapters/asyncmy/driver.py +632 -0
  34. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  35. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  36. sqlspec/adapters/asyncpg/__init__.py +23 -0
  37. sqlspec/adapters/asyncpg/_type_handlers.py +76 -0
  38. sqlspec/adapters/asyncpg/_types.py +23 -0
  39. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/adk/store.py +460 -0
  41. sqlspec/adapters/asyncpg/config.py +464 -0
  42. sqlspec/adapters/asyncpg/data_dictionary.py +321 -0
  43. sqlspec/adapters/asyncpg/driver.py +720 -0
  44. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  45. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  46. sqlspec/adapters/bigquery/__init__.py +18 -0
  47. sqlspec/adapters/bigquery/_types.py +12 -0
  48. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  49. sqlspec/adapters/bigquery/adk/store.py +585 -0
  50. sqlspec/adapters/bigquery/config.py +298 -0
  51. sqlspec/adapters/bigquery/data_dictionary.py +256 -0
  52. sqlspec/adapters/bigquery/driver.py +1073 -0
  53. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  54. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  55. sqlspec/adapters/bigquery/type_converter.py +125 -0
  56. sqlspec/adapters/duckdb/__init__.py +24 -0
  57. sqlspec/adapters/duckdb/_types.py +12 -0
  58. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  59. sqlspec/adapters/duckdb/adk/store.py +563 -0
  60. sqlspec/adapters/duckdb/config.py +396 -0
  61. sqlspec/adapters/duckdb/data_dictionary.py +264 -0
  62. sqlspec/adapters/duckdb/driver.py +604 -0
  63. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  64. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  65. sqlspec/adapters/duckdb/pool.py +273 -0
  66. sqlspec/adapters/duckdb/type_converter.py +133 -0
  67. sqlspec/adapters/oracledb/__init__.py +32 -0
  68. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  69. sqlspec/adapters/oracledb/_types.py +39 -0
  70. sqlspec/adapters/oracledb/_uuid_handlers.py +130 -0
  71. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  72. sqlspec/adapters/oracledb/adk/store.py +1632 -0
  73. sqlspec/adapters/oracledb/config.py +469 -0
  74. sqlspec/adapters/oracledb/data_dictionary.py +717 -0
  75. sqlspec/adapters/oracledb/driver.py +1493 -0
  76. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  77. sqlspec/adapters/oracledb/litestar/store.py +765 -0
  78. sqlspec/adapters/oracledb/migrations.py +532 -0
  79. sqlspec/adapters/oracledb/type_converter.py +207 -0
  80. sqlspec/adapters/psqlpy/__init__.py +16 -0
  81. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  82. sqlspec/adapters/psqlpy/_types.py +12 -0
  83. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  84. sqlspec/adapters/psqlpy/adk/store.py +483 -0
  85. sqlspec/adapters/psqlpy/config.py +271 -0
  86. sqlspec/adapters/psqlpy/data_dictionary.py +179 -0
  87. sqlspec/adapters/psqlpy/driver.py +892 -0
  88. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  90. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  91. sqlspec/adapters/psycopg/__init__.py +32 -0
  92. sqlspec/adapters/psycopg/_type_handlers.py +90 -0
  93. sqlspec/adapters/psycopg/_types.py +18 -0
  94. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  95. sqlspec/adapters/psycopg/adk/store.py +962 -0
  96. sqlspec/adapters/psycopg/config.py +487 -0
  97. sqlspec/adapters/psycopg/data_dictionary.py +630 -0
  98. sqlspec/adapters/psycopg/driver.py +1336 -0
  99. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  100. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  101. sqlspec/adapters/spanner/__init__.py +38 -0
  102. sqlspec/adapters/spanner/_type_handlers.py +186 -0
  103. sqlspec/adapters/spanner/_types.py +12 -0
  104. sqlspec/adapters/spanner/adk/__init__.py +5 -0
  105. sqlspec/adapters/spanner/adk/store.py +435 -0
  106. sqlspec/adapters/spanner/config.py +241 -0
  107. sqlspec/adapters/spanner/data_dictionary.py +95 -0
  108. sqlspec/adapters/spanner/dialect/__init__.py +6 -0
  109. sqlspec/adapters/spanner/dialect/_spangres.py +52 -0
  110. sqlspec/adapters/spanner/dialect/_spanner.py +123 -0
  111. sqlspec/adapters/spanner/driver.py +366 -0
  112. sqlspec/adapters/spanner/litestar/__init__.py +5 -0
  113. sqlspec/adapters/spanner/litestar/store.py +266 -0
  114. sqlspec/adapters/spanner/type_converter.py +46 -0
  115. sqlspec/adapters/sqlite/__init__.py +18 -0
  116. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  117. sqlspec/adapters/sqlite/_types.py +11 -0
  118. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  119. sqlspec/adapters/sqlite/adk/store.py +582 -0
  120. sqlspec/adapters/sqlite/config.py +221 -0
  121. sqlspec/adapters/sqlite/data_dictionary.py +256 -0
  122. sqlspec/adapters/sqlite/driver.py +527 -0
  123. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  124. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  125. sqlspec/adapters/sqlite/pool.py +140 -0
  126. sqlspec/base.py +811 -0
  127. sqlspec/builder/__init__.py +146 -0
  128. sqlspec/builder/_base.py +900 -0
  129. sqlspec/builder/_column.py +517 -0
  130. sqlspec/builder/_ddl.py +1642 -0
  131. sqlspec/builder/_delete.py +84 -0
  132. sqlspec/builder/_dml.py +381 -0
  133. sqlspec/builder/_expression_wrappers.py +46 -0
  134. sqlspec/builder/_factory.py +1537 -0
  135. sqlspec/builder/_insert.py +315 -0
  136. sqlspec/builder/_join.py +375 -0
  137. sqlspec/builder/_merge.py +848 -0
  138. sqlspec/builder/_parsing_utils.py +297 -0
  139. sqlspec/builder/_select.py +1615 -0
  140. sqlspec/builder/_update.py +161 -0
  141. sqlspec/builder/_vector_expressions.py +259 -0
  142. sqlspec/cli.py +764 -0
  143. sqlspec/config.py +1540 -0
  144. sqlspec/core/__init__.py +305 -0
  145. sqlspec/core/cache.py +785 -0
  146. sqlspec/core/compiler.py +603 -0
  147. sqlspec/core/filters.py +872 -0
  148. sqlspec/core/hashing.py +274 -0
  149. sqlspec/core/metrics.py +83 -0
  150. sqlspec/core/parameters/__init__.py +64 -0
  151. sqlspec/core/parameters/_alignment.py +266 -0
  152. sqlspec/core/parameters/_converter.py +413 -0
  153. sqlspec/core/parameters/_processor.py +341 -0
  154. sqlspec/core/parameters/_registry.py +201 -0
  155. sqlspec/core/parameters/_transformers.py +226 -0
  156. sqlspec/core/parameters/_types.py +430 -0
  157. sqlspec/core/parameters/_validator.py +123 -0
  158. sqlspec/core/pipeline.py +187 -0
  159. sqlspec/core/result.py +1124 -0
  160. sqlspec/core/splitter.py +940 -0
  161. sqlspec/core/stack.py +163 -0
  162. sqlspec/core/statement.py +835 -0
  163. sqlspec/core/type_conversion.py +235 -0
  164. sqlspec/driver/__init__.py +36 -0
  165. sqlspec/driver/_async.py +1027 -0
  166. sqlspec/driver/_common.py +1236 -0
  167. sqlspec/driver/_sync.py +1025 -0
  168. sqlspec/driver/mixins/__init__.py +7 -0
  169. sqlspec/driver/mixins/_result_tools.py +61 -0
  170. sqlspec/driver/mixins/_sql_translator.py +122 -0
  171. sqlspec/driver/mixins/_storage.py +311 -0
  172. sqlspec/exceptions.py +321 -0
  173. sqlspec/extensions/__init__.py +0 -0
  174. sqlspec/extensions/adk/__init__.py +53 -0
  175. sqlspec/extensions/adk/_types.py +51 -0
  176. sqlspec/extensions/adk/converters.py +172 -0
  177. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  178. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  179. sqlspec/extensions/adk/service.py +181 -0
  180. sqlspec/extensions/adk/store.py +536 -0
  181. sqlspec/extensions/aiosql/__init__.py +10 -0
  182. sqlspec/extensions/aiosql/adapter.py +471 -0
  183. sqlspec/extensions/fastapi/__init__.py +19 -0
  184. sqlspec/extensions/fastapi/extension.py +341 -0
  185. sqlspec/extensions/fastapi/providers.py +543 -0
  186. sqlspec/extensions/flask/__init__.py +36 -0
  187. sqlspec/extensions/flask/_state.py +72 -0
  188. sqlspec/extensions/flask/_utils.py +40 -0
  189. sqlspec/extensions/flask/extension.py +402 -0
  190. sqlspec/extensions/litestar/__init__.py +23 -0
  191. sqlspec/extensions/litestar/_utils.py +52 -0
  192. sqlspec/extensions/litestar/cli.py +92 -0
  193. sqlspec/extensions/litestar/config.py +90 -0
  194. sqlspec/extensions/litestar/handlers.py +316 -0
  195. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  196. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  197. sqlspec/extensions/litestar/plugin.py +638 -0
  198. sqlspec/extensions/litestar/providers.py +454 -0
  199. sqlspec/extensions/litestar/store.py +265 -0
  200. sqlspec/extensions/otel/__init__.py +58 -0
  201. sqlspec/extensions/prometheus/__init__.py +107 -0
  202. sqlspec/extensions/starlette/__init__.py +10 -0
  203. sqlspec/extensions/starlette/_state.py +26 -0
  204. sqlspec/extensions/starlette/_utils.py +52 -0
  205. sqlspec/extensions/starlette/extension.py +257 -0
  206. sqlspec/extensions/starlette/middleware.py +154 -0
  207. sqlspec/loader.py +716 -0
  208. sqlspec/migrations/__init__.py +36 -0
  209. sqlspec/migrations/base.py +728 -0
  210. sqlspec/migrations/commands.py +1140 -0
  211. sqlspec/migrations/context.py +142 -0
  212. sqlspec/migrations/fix.py +203 -0
  213. sqlspec/migrations/loaders.py +450 -0
  214. sqlspec/migrations/runner.py +1024 -0
  215. sqlspec/migrations/templates.py +234 -0
  216. sqlspec/migrations/tracker.py +403 -0
  217. sqlspec/migrations/utils.py +256 -0
  218. sqlspec/migrations/validation.py +203 -0
  219. sqlspec/observability/__init__.py +22 -0
  220. sqlspec/observability/_config.py +228 -0
  221. sqlspec/observability/_diagnostics.py +67 -0
  222. sqlspec/observability/_dispatcher.py +151 -0
  223. sqlspec/observability/_observer.py +180 -0
  224. sqlspec/observability/_runtime.py +381 -0
  225. sqlspec/observability/_spans.py +158 -0
  226. sqlspec/protocols.py +530 -0
  227. sqlspec/py.typed +0 -0
  228. sqlspec/storage/__init__.py +46 -0
  229. sqlspec/storage/_utils.py +104 -0
  230. sqlspec/storage/backends/__init__.py +1 -0
  231. sqlspec/storage/backends/base.py +163 -0
  232. sqlspec/storage/backends/fsspec.py +398 -0
  233. sqlspec/storage/backends/local.py +377 -0
  234. sqlspec/storage/backends/obstore.py +580 -0
  235. sqlspec/storage/errors.py +104 -0
  236. sqlspec/storage/pipeline.py +604 -0
  237. sqlspec/storage/registry.py +289 -0
  238. sqlspec/typing.py +219 -0
  239. sqlspec/utils/__init__.py +31 -0
  240. sqlspec/utils/arrow_helpers.py +95 -0
  241. sqlspec/utils/config_resolver.py +153 -0
  242. sqlspec/utils/correlation.py +132 -0
  243. sqlspec/utils/data_transformation.py +114 -0
  244. sqlspec/utils/dependencies.py +79 -0
  245. sqlspec/utils/deprecation.py +113 -0
  246. sqlspec/utils/fixtures.py +250 -0
  247. sqlspec/utils/logging.py +172 -0
  248. sqlspec/utils/module_loader.py +273 -0
  249. sqlspec/utils/portal.py +325 -0
  250. sqlspec/utils/schema.py +288 -0
  251. sqlspec/utils/serializers.py +396 -0
  252. sqlspec/utils/singleton.py +41 -0
  253. sqlspec/utils/sync_tools.py +277 -0
  254. sqlspec/utils/text.py +108 -0
  255. sqlspec/utils/type_converters.py +99 -0
  256. sqlspec/utils/type_guards.py +1324 -0
  257. sqlspec/utils/version.py +444 -0
  258. sqlspec-0.32.0.dist-info/METADATA +202 -0
  259. sqlspec-0.32.0.dist-info/RECORD +262 -0
  260. sqlspec-0.32.0.dist-info/WHEEL +4 -0
  261. sqlspec-0.32.0.dist-info/entry_points.txt +2 -0
  262. sqlspec-0.32.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,123 @@
1
+ """Parameter extraction utilities."""
2
+
3
+ import re
4
+ from collections import OrderedDict
5
+
6
+ from mypy_extensions import mypyc_attr
7
+
8
+ from sqlspec.core.parameters._types import ParameterInfo, ParameterStyle
9
+
10
+ __all__ = ("PARAMETER_REGEX", "ParameterValidator")
11
+
12
+ PARAMETER_REGEX = re.compile(
13
+ r"""
14
+ (?P<dquote>"(?:[^"\\]|\\.)*") |
15
+ (?P<squote>'(?:[^'\\]|\\.)*') |
16
+ (?P<dollar_quoted_string>\$(?P<dollar_quote_tag_inner>\w*)?\$[\s\S]*?\$\4\$) |
17
+ (?P<line_comment>--[^\r\n]*) |
18
+ (?P<block_comment>/\*(?:[^*]|\*(?!/))*\*/) |
19
+ (?P<pg_q_operator>\?\?|\?\||\?&) |
20
+ (?P<pg_cast>::(?P<cast_type>\w+)) |
21
+ (?P<sql_server_global>@@(?P<global_var_name>\w+)) |
22
+ (?P<pyformat_named>%\((?P<pyformat_name>\w+)\)s) |
23
+ (?P<pyformat_pos>%s) |
24
+ (?P<positional_colon>(?<![A-Za-z0-9_]):(?P<colon_num>\d+)) |
25
+ (?P<named_colon>(?<![A-Za-z0-9_]):(?P<colon_name>\w+)) |
26
+ (?P<named_at>(?<![A-Za-z0-9_])@(?P<at_name>\w+)) |
27
+ (?P<numeric>(?<![A-Za-z0-9_])\$(?P<numeric_num>\d+)) |
28
+ (?P<named_dollar_param>(?<![A-Za-z0-9_])\$(?P<dollar_param_name>\w+)) |
29
+ (?P<qmark>\?)
30
+ """,
31
+ re.VERBOSE | re.IGNORECASE | re.MULTILINE | re.DOTALL,
32
+ )
33
+
34
+
35
+ @mypyc_attr(allow_interpreted_subclasses=False)
36
+ class ParameterValidator:
37
+ """Extracts placeholder metadata and dialect compatibility information."""
38
+
39
+ __slots__ = ("_cache_max_size", "_parameter_cache")
40
+
41
+ def __init__(self, cache_max_size: int = 5000) -> None:
42
+ self._parameter_cache: OrderedDict[str, list[ParameterInfo]] = OrderedDict()
43
+ self._cache_max_size = cache_max_size
44
+
45
+ def _extract_parameter_style(self, match: re.Match[str]) -> "tuple[ParameterStyle | None, str | None]":
46
+ """Map a regex match to a placeholder style and optional name."""
47
+ if match.group("qmark"):
48
+ return ParameterStyle.QMARK, None
49
+ if match.group("named_colon"):
50
+ return ParameterStyle.NAMED_COLON, match.group("colon_name")
51
+ if match.group("numeric"):
52
+ return ParameterStyle.NUMERIC, match.group("numeric_num")
53
+ if match.group("named_at"):
54
+ return ParameterStyle.NAMED_AT, match.group("at_name")
55
+ if match.group("pyformat_named"):
56
+ return ParameterStyle.NAMED_PYFORMAT, match.group("pyformat_name")
57
+ if match.group("pyformat_pos"):
58
+ return ParameterStyle.POSITIONAL_PYFORMAT, None
59
+ if match.group("positional_colon"):
60
+ return ParameterStyle.POSITIONAL_COLON, match.group("colon_num")
61
+ if match.group("named_dollar_param"):
62
+ return ParameterStyle.NAMED_DOLLAR, match.group("dollar_param_name")
63
+ return None, None
64
+
65
+ def extract_parameters(self, sql: str) -> "list[ParameterInfo]":
66
+ """Extract ordered parameter metadata from SQL text."""
67
+ cached_result = self._parameter_cache.get(sql)
68
+ if cached_result is not None:
69
+ self._parameter_cache.move_to_end(sql)
70
+ return cached_result
71
+
72
+ if not any(c in sql for c in ("?", "%", ":", "@", "$")):
73
+ if len(self._parameter_cache) >= self._cache_max_size:
74
+ self._parameter_cache.popitem(last=False)
75
+ self._parameter_cache[sql] = []
76
+ return []
77
+
78
+ parameters: list[ParameterInfo] = []
79
+ ordinal = 0
80
+
81
+ skip_groups = (
82
+ "dquote",
83
+ "squote",
84
+ "dollar_quoted_string",
85
+ "line_comment",
86
+ "block_comment",
87
+ "pg_q_operator",
88
+ "pg_cast",
89
+ "sql_server_global",
90
+ )
91
+
92
+ for match in PARAMETER_REGEX.finditer(sql):
93
+ if any(match.group(group) for group in skip_groups):
94
+ continue
95
+ style, name = self._extract_parameter_style(match)
96
+ if style is None:
97
+ continue
98
+ placeholder_text = match.group(0)
99
+ parameters.append(ParameterInfo(name, style, match.start(), ordinal, placeholder_text))
100
+ ordinal += 1
101
+
102
+ if len(self._parameter_cache) >= self._cache_max_size:
103
+ self._parameter_cache.popitem(last=False)
104
+ self._parameter_cache[sql] = parameters
105
+ return parameters
106
+
107
+ def get_sqlglot_incompatible_styles(self, dialect: str | None = None) -> "set[ParameterStyle]":
108
+ """Return placeholder styles incompatible with SQLGlot for the dialect."""
109
+ base_incompatible = {
110
+ ParameterStyle.NAMED_PYFORMAT,
111
+ ParameterStyle.POSITIONAL_PYFORMAT,
112
+ ParameterStyle.POSITIONAL_COLON,
113
+ }
114
+
115
+ if dialect and dialect.lower() in {"mysql", "mariadb"}:
116
+ return base_incompatible
117
+ if dialect and dialect.lower() in {"postgres", "postgresql"}:
118
+ return {ParameterStyle.POSITIONAL_COLON}
119
+ if dialect and dialect.lower() == "sqlite":
120
+ return {ParameterStyle.POSITIONAL_COLON}
121
+ if dialect and dialect.lower() in {"oracle", "bigquery"}:
122
+ return base_incompatible
123
+ return base_incompatible
@@ -0,0 +1,187 @@
1
+ """Shared statement pipeline registry and instrumentation."""
2
+
3
+ import os
4
+ from collections import OrderedDict
5
+ from typing import Any, Final
6
+
7
+ from mypy_extensions import mypyc_attr
8
+
9
+ from sqlspec.core.compiler import CompiledSQL, SQLProcessor
10
+
11
+ DEBUG_ENV_FLAG: Final[str] = "SQLSPEC_DEBUG_PIPELINE_CACHE"
12
+ DEFAULT_PIPELINE_CACHE_SIZE: Final[int] = 1000
13
+ DEFAULT_PIPELINE_COUNT: Final[int] = 32
14
+
15
+
16
+ def _is_truthy(value: "str | None") -> bool:
17
+ if value is None:
18
+ return False
19
+ normalized = value.strip().lower()
20
+ return normalized in {"1", "true", "yes", "on"}
21
+
22
+
23
+ @mypyc_attr(allow_interpreted_subclasses=False)
24
+ class _PipelineMetrics:
25
+ __slots__ = ("hits", "max_size", "misses", "size")
26
+
27
+ def __init__(self) -> None:
28
+ self.hits = 0
29
+ self.misses = 0
30
+ self.size = 0
31
+ self.max_size = 0
32
+
33
+ def update(self, stats: "dict[str, int]") -> None:
34
+ self.hits = stats.get("hits", 0)
35
+ self.misses = stats.get("misses", 0)
36
+ self.size = stats.get("size", 0)
37
+ self.max_size = stats.get("max_size", 0)
38
+
39
+ def snapshot(self) -> "dict[str, int]":
40
+ return {"hits": self.hits, "misses": self.misses, "size": self.size, "max_size": self.max_size}
41
+
42
+ def reset(self) -> None:
43
+ self.hits = 0
44
+ self.misses = 0
45
+ self.size = 0
46
+ self.max_size = 0
47
+
48
+
49
+ @mypyc_attr(allow_interpreted_subclasses=False)
50
+ class _StatementPipeline:
51
+ __slots__ = ("_metrics", "_processor", "dialect", "parameter_style")
52
+
53
+ def __init__(self, config: "Any", cache_size: int, record_metrics: bool) -> None:
54
+ self._processor = SQLProcessor(config, max_cache_size=cache_size)
55
+ self.dialect = str(config.dialect) if getattr(config, "dialect", None) else "default"
56
+ parameter_style = config.parameter_config.default_parameter_style
57
+ self.parameter_style = parameter_style.value if parameter_style else "unknown"
58
+ self._metrics = _PipelineMetrics() if record_metrics else None
59
+
60
+ def compile(self, sql: str, parameters: Any, is_many: bool, record_metrics: bool) -> "CompiledSQL":
61
+ result = self._processor.compile(sql, parameters, is_many=is_many)
62
+ if record_metrics and self._metrics is not None:
63
+ self._metrics.update(self._processor.cache_stats)
64
+ return result
65
+
66
+ def reset(self) -> None:
67
+ self._processor.clear_cache()
68
+ if self._metrics is not None:
69
+ self._metrics.reset()
70
+
71
+ def metrics(self) -> "dict[str, int] | None":
72
+ if self._metrics is None:
73
+ return None
74
+ return self._metrics.snapshot()
75
+
76
+
77
+ @mypyc_attr(allow_interpreted_subclasses=False)
78
+ class StatementPipelineRegistry:
79
+ __slots__ = ("_max_pipelines", "_pipeline_cache_size", "_pipelines")
80
+
81
+ def __init__(
82
+ self, max_pipelines: int = DEFAULT_PIPELINE_COUNT, cache_size: int = DEFAULT_PIPELINE_CACHE_SIZE
83
+ ) -> None:
84
+ self._pipelines: OrderedDict[str, _StatementPipeline] = OrderedDict()
85
+ self._max_pipelines = max_pipelines
86
+ self._pipeline_cache_size = cache_size
87
+
88
+ def compile(self, config: "Any", sql: str, parameters: Any, is_many: bool = False) -> "CompiledSQL":
89
+ key = self._fingerprint_config(config)
90
+ pipeline = self._pipelines.get(key)
91
+ record_metrics = _is_truthy(os.getenv(DEBUG_ENV_FLAG))
92
+
93
+ if pipeline is not None:
94
+ self._pipelines.move_to_end(key)
95
+ else:
96
+ pipeline = _StatementPipeline(config, self._pipeline_cache_size, record_metrics)
97
+ if len(self._pipelines) >= self._max_pipelines:
98
+ self._pipelines.popitem(last=False)
99
+ self._pipelines[key] = pipeline
100
+
101
+ return pipeline.compile(sql, parameters, is_many, record_metrics)
102
+
103
+ def reset(self) -> None:
104
+ for pipeline in self._pipelines.values():
105
+ pipeline.reset()
106
+ self._pipelines.clear()
107
+
108
+ def metrics(self) -> "list[dict[str, Any]]":
109
+ if not _is_truthy(os.getenv(DEBUG_ENV_FLAG)):
110
+ return []
111
+
112
+ snapshots: list[dict[str, Any]] = []
113
+ for key, pipeline in self._pipelines.items():
114
+ metrics = pipeline.metrics()
115
+ if metrics is None:
116
+ continue
117
+ entry = {"config": key, "dialect": pipeline.dialect, "parameter_style": pipeline.parameter_style}
118
+ entry.update(metrics)
119
+ snapshots.append(entry)
120
+ return snapshots
121
+
122
+ def _fingerprint_config(self, config: "Any") -> str:
123
+ param_config = config.parameter_config
124
+ supported_styles = sorted(style.value for style in param_config.supported_parameter_styles)
125
+ exec_styles = (
126
+ sorted(style.value for style in param_config.supported_execution_parameter_styles)
127
+ if param_config.supported_execution_parameter_styles
128
+ else None
129
+ )
130
+ converter_name = type(config.parameter_converter).__name__ if config.parameter_converter else "None"
131
+ validator_name = type(config.parameter_validator).__name__ if config.parameter_validator else "None"
132
+ pre_steps = tuple(type(step).__name__ for step in config.pre_process_steps) if config.pre_process_steps else ()
133
+ post_steps = (
134
+ tuple(type(step).__name__ for step in config.post_process_steps) if config.post_process_steps else ()
135
+ )
136
+ output_name = type(config.output_transformer).__name__ if config.output_transformer else "None"
137
+ finger_components = (
138
+ bool(config.enable_parsing),
139
+ bool(config.enable_validation),
140
+ bool(config.enable_transformations),
141
+ bool(config.enable_analysis),
142
+ bool(config.enable_expression_simplification),
143
+ bool(config.enable_parameter_type_wrapping),
144
+ bool(config.enable_caching),
145
+ str(config.dialect),
146
+ param_config.default_parameter_style.value,
147
+ param_config.default_execution_parameter_style.value,
148
+ param_config.hash(),
149
+ tuple(supported_styles),
150
+ tuple(exec_styles) if exec_styles else None,
151
+ converter_name,
152
+ validator_name,
153
+ pre_steps,
154
+ post_steps,
155
+ output_name,
156
+ bool(param_config.output_transformer),
157
+ bool(param_config.ast_transformer),
158
+ param_config.has_native_list_expansion,
159
+ param_config.allow_mixed_parameter_styles,
160
+ param_config.preserve_parameter_format,
161
+ param_config.preserve_original_params_for_many,
162
+ )
163
+ fingerprint = hash(finger_components)
164
+ return f"pipeline::{fingerprint}"
165
+
166
+
167
+ _PIPELINE_REGISTRY: "StatementPipelineRegistry" = StatementPipelineRegistry()
168
+
169
+
170
+ def compile_with_shared_pipeline(config: "Any", sql: str, parameters: Any, is_many: bool = False) -> "CompiledSQL":
171
+ return _PIPELINE_REGISTRY.compile(config, sql, parameters, is_many=is_many)
172
+
173
+
174
+ def reset_statement_pipeline_cache() -> None:
175
+ _PIPELINE_REGISTRY.reset()
176
+
177
+
178
+ def get_statement_pipeline_metrics() -> "list[dict[str, Any]]":
179
+ return _PIPELINE_REGISTRY.metrics()
180
+
181
+
182
+ __all__ = (
183
+ "StatementPipelineRegistry",
184
+ "compile_with_shared_pipeline",
185
+ "get_statement_pipeline_metrics",
186
+ "reset_statement_pipeline_cache",
187
+ )