picopyn 0.3.0.dev1__tar.gz → 2.0.0.dev1__tar.gz

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 (83) hide show
  1. {picopyn-0.3.0.dev1/picopyn.egg-info → picopyn-2.0.0.dev1}/PKG-INFO +48 -13
  2. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/README.md +40 -6
  3. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/bench.py +1 -1
  4. picopyn-2.0.0.dev1/docs/async.md +6 -0
  5. picopyn-2.0.0.dev1/docs/async_internal.md +18 -0
  6. picopyn-2.0.0.dev1/docs/bucket.md +7 -0
  7. picopyn-2.0.0.dev1/docs/changelog.md +3 -0
  8. picopyn-2.0.0.dev1/docs/contributing.md +7 -0
  9. picopyn-2.0.0.dev1/docs/diagrams/_pool_init.md +64 -0
  10. picopyn-2.0.0.dev1/docs/diagrams/_pool_init_discovery_disabled.md +46 -0
  11. picopyn-2.0.0.dev1/docs/diagrams/_pool_init_discovery_enabled.md +55 -0
  12. picopyn-2.0.0.dev1/docs/diagrams/_pool_lifecycle.md +44 -0
  13. picopyn-2.0.0.dev1/docs/diagrams/_query_metadata_pool_init.md +55 -0
  14. picopyn-2.0.0.dev1/docs/diagrams/_query_metadata_usage_lookup.md +44 -0
  15. picopyn-2.0.0.dev1/docs/diagrams/_reconcile_node_outside_pool_died.md +50 -0
  16. picopyn-2.0.0.dev1/docs/diagrams/_reconcile_pool_with_topology.md +46 -0
  17. picopyn-2.0.0.dev1/docs/diagrams/_reconcile_pooled_node_died_size_eq.md +50 -0
  18. picopyn-2.0.0.dev1/docs/diagrams/_reconcile_pooled_node_died_size_gt.md +54 -0
  19. picopyn-2.0.0.dev1/docs/diagrams/_reconcile_pooled_node_died_size_lt.md +48 -0
  20. picopyn-2.0.0.dev1/docs/diagrams/_request_if_missing.md +27 -0
  21. picopyn-2.0.0.dev1/docs/diagrams/_topology_apply_bucket_event.md +20 -0
  22. picopyn-2.0.0.dev1/docs/diagrams/_topology_apply_bucket_replicaset_event.md +52 -0
  23. picopyn-2.0.0.dev1/docs/diagrams/_topology_components.md +30 -0
  24. picopyn-2.0.0.dev1/docs/diagrams/_topology_refresh.md +52 -0
  25. picopyn-2.0.0.dev1/docs/diagrams/_topology_tracking.md +36 -0
  26. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/docs/explain.md +1 -1
  27. picopyn-2.0.0.dev1/docs/index.md +73 -0
  28. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/docs/installation.md +1 -1
  29. picopyn-2.0.0.dev1/docs/query_metadata.md +8 -0
  30. picopyn-2.0.0.dev1/docs/sync.md +17 -0
  31. picopyn-2.0.0.dev1/docs/topology.md +9 -0
  32. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/__init__.py +1 -4
  33. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/asynchronous/__init__.py +0 -4
  34. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/asynchronous/client.py +10 -2
  35. picopyn-2.0.0.dev1/picopyn/asynchronous/connection.py +358 -0
  36. picopyn-2.0.0.dev1/picopyn/asynchronous/metadata_service.py +177 -0
  37. picopyn-2.0.0.dev1/picopyn/asynchronous/pool.py +1262 -0
  38. picopyn-2.0.0.dev1/picopyn/asynchronous/topology_tracker.py +408 -0
  39. picopyn-2.0.0.dev1/picopyn/bucket.py +256 -0
  40. picopyn-2.0.0.dev1/picopyn/query_metadata.py +227 -0
  41. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/ssl.py +8 -3
  42. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/synchronous/__init__.py +6 -6
  43. picopyn-2.0.0.dev1/picopyn/synchronous/client.py +168 -0
  44. picopyn-2.0.0.dev1/picopyn/synchronous/connection.py +205 -0
  45. picopyn-2.0.0.dev1/picopyn/synchronous/cursor.py +272 -0
  46. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/synchronous/errors.py +26 -0
  47. picopyn-2.0.0.dev1/picopyn/synchronous/pool.py +652 -0
  48. picopyn-2.0.0.dev1/picopyn/topology.py +422 -0
  49. picopyn-2.0.0.dev1/picopyn/utils/__init__.py +42 -0
  50. picopyn-2.0.0.dev1/picopyn/utils/discovery.py +108 -0
  51. picopyn-2.0.0.dev1/picopyn/utils/dsn.py +141 -0
  52. picopyn-2.0.0.dev1/picopyn/utils/queries.py +72 -0
  53. picopyn-2.0.0.dev1/picopyn/utils/topology.py +154 -0
  54. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1/picopyn.egg-info}/PKG-INFO +48 -13
  55. picopyn-2.0.0.dev1/picopyn.egg-info/SOURCES.txt +69 -0
  56. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn.egg-info/requires.txt +4 -5
  57. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/pyproject.toml +10 -9
  58. picopyn-0.3.0.dev1/docs/async.md +0 -3
  59. picopyn-0.3.0.dev1/docs/index.md +0 -41
  60. picopyn-0.3.0.dev1/docs/sync.md +0 -3
  61. picopyn-0.3.0.dev1/picopyn/asynchronous/connection.py +0 -138
  62. picopyn-0.3.0.dev1/picopyn/asynchronous/pool.py +0 -500
  63. picopyn-0.3.0.dev1/picopyn/synchronous/connection.py +0 -62
  64. picopyn-0.3.0.dev1/picopyn/synchronous/cursor.py +0 -100
  65. picopyn-0.3.0.dev1/picopyn/utils/__init__.py +0 -16
  66. picopyn-0.3.0.dev1/picopyn/utils/discovery.py +0 -42
  67. picopyn-0.3.0.dev1/picopyn/utils/dsn.py +0 -48
  68. picopyn-0.3.0.dev1/picopyn/utils/queries.py +0 -21
  69. picopyn-0.3.0.dev1/picopyn.egg-info/SOURCES.txt +0 -38
  70. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/AUTHORS.md +0 -0
  71. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/LICENSE +0 -0
  72. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/MANIFEST.in +0 -0
  73. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/README.md +0 -0
  74. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/examples/10k_rows_1_thread_1_batch/overall_speed_time.png +0 -0
  75. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/examples/10k_rows_1_thread_1_batch/threads_speed_time.png +0 -0
  76. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/examples/1m_rows_10_threads_100_batch/errors_cumulative.png +0 -0
  77. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/examples/1m_rows_10_threads_100_batch/overall_speed_time.png +0 -0
  78. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/examples/1m_rows_10_threads_100_batch/threads_speed_time.png +0 -0
  79. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/benchmark/generate_data_to_csv.py +0 -0
  80. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn/explain.py +0 -0
  81. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn.egg-info/dependency_links.txt +0 -0
  82. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/picopyn.egg-info/top_level.txt +0 -0
  83. {picopyn-0.3.0.dev1 → picopyn-2.0.0.dev1}/setup.cfg +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: picopyn
3
- Version: 0.3.0.dev1
3
+ Version: 2.0.0.dev1
4
4
  Summary: Python connector for working with the distributed Picodata database.
5
- Author-email: Asya Lomakina <a.lomakina@picodata.io>, Igor Kuznetsov <i.kuznetsov@picodata.io>, Dmitriy Кoltsov <dkoltsov@picodata.io>, Daniil Gorlyakov <d.gorlyakov@picodata.io>
5
+ Author-email: Asya Lomakina <a.lomakina@picodata.io>, Igor Kuznetsov <i.kuznetsov@picodata.io>, Dmitriy Koltsov <dkoltsov@picodata.io>, Daniil Gorlyakov <d.gorlyakov@picodata.io>
6
6
  License-Expression: BSD-2-Clause
7
- Project-URL: Source, https://git.picodata.io/solution/picopyn
7
+ Project-URL: Source, https://git.picodata.io/core/drivers/picopyn
8
8
  Project-URL: Documentation, https://picopyn.readthedocs.io/en/stable/
9
9
  Project-URL: Changelog, https://git.picodata.io/core/drivers/picopyn/-/blob/main/CHANGELOG.md
10
10
  Keywords: database,picodata
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Operating System :: OS Independent
13
- Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Topic :: Database
16
16
  Classifier: Framework :: AsyncIO
@@ -18,10 +18,11 @@ Requires-Python: >=3.10
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  License-File: AUTHORS.md
21
- Requires-Dist: async-timeout==5.0.1
22
21
  Requires-Dist: asyncpg==0.30.0
23
- Requires-Dist: typing_extensions==4.12.2; python_version == "3.10"
22
+ Requires-Dist: typing_extensions<5,>=4.12.2
24
23
  Requires-Dist: psycopg[binary]==3.3.4
24
+ Requires-Dist: msgpack<2,>=1.0.0
25
+ Requires-Dist: mmh3<6,>=5.2.1
25
26
  Provides-Extra: test
26
27
  Requires-Dist: pytest==8.3.5; extra == "test"
27
28
  Requires-Dist: pytest-asyncio==0.25.3; extra == "test"
@@ -29,7 +30,7 @@ Requires-Dist: ruff==0.11.13; extra == "test"
29
30
  Requires-Dist: mypy==1.16.0; extra == "test"
30
31
  Requires-Dist: black==25.1.0; extra == "test"
31
32
  Provides-Extra: docs
32
- Requires-Dist: mkdocs<2.0,>=1.0; extra == "docs"
33
+ Requires-Dist: mkdocs<2.0,>=1.6; extra == "docs"
33
34
  Requires-Dist: mkdocs-material; extra == "docs"
34
35
  Requires-Dist: mkdocstrings[python]; extra == "docs"
35
36
  Provides-Extra: benchmark
@@ -48,11 +49,23 @@ Dynamic: license-file
48
49
 
49
50
  **Picopyn** is a Python package for working with the distributed [Picodata](https://picodata.io/en/) database.
50
51
 
52
+ **Version compatibility**:
53
+ | Picodata version | Picopyn version |
54
+ | ----------------- | ----------------- |
55
+ | >=25.2.1, <25.4.4 | 0.1.1 |
56
+ | >=25.4.4, <25.5.1 | 0.2.0 |
57
+ | >=25.5.1, <26.1.x | 1.0.0 |
58
+ | >=26.1.1, <26.2.x | 2.0.0 |
59
+ | 26.2.x | ⚠️ Unreleased yet |
60
+
51
61
  ## Features
52
62
  * Connection pooling with configurable pool size
53
63
  * Optional automatic node discovery
54
64
  * Pluggable load-balancing strategies
55
- * Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous API (based on [psycopg](https://www.psycopg.org/))
65
+ * Asynchronous API based on [asyncpg](https://github.com/MagicStack/asyncpg)
66
+ * (async only) Shard-aware routing
67
+ * (async only) Automatic pool membership reconciliation
68
+ * Synchronous [DPAPI-compatible](https://peps.python.org/pep-0249/) API based on [psycopg](https://www.psycopg.org/)
56
69
 
57
70
  ## Navigation
58
71
  * [Installation](#installation)
@@ -73,16 +86,18 @@ pip install picopyn
73
86
  Or from source:
74
87
 
75
88
  ```sh
76
- git clone https://git.picodata.io/solution/picopyn.git
89
+ git clone https://git.picodata.io/core/drivers/picopyn.git
77
90
  cd picopyn
78
91
  pip install -e .
79
92
  ```
80
93
 
81
94
  ## Quickstart
82
95
 
96
+ Async:
97
+
83
98
  ```python
84
99
  import asyncio
85
- from picopyn import Client
100
+ from picopyn.asynchronous import Client
86
101
 
87
102
  async def main():
88
103
  # create and connect client to the picodata cluster
@@ -94,9 +109,9 @@ async def main():
94
109
  CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
95
110
  ''')
96
111
 
97
- # execute DML operations
98
- await client.execute('INSERT INTO \"warehouse\" VALUES ($1::int, $2::varchar)', 1, "test")
99
- rows = await client.fetch('SELECT * FROM \"warehouse\"')
112
+ # execute DML/DQL operations
113
+ await client.execute('INSERT INTO "warehouse" VALUES ($1::int, $2::varchar)', 1, "test")
114
+ rows = await client.fetch('SELECT * FROM "warehouse"')
100
115
  print(rows)
101
116
 
102
117
  await client.close()
@@ -104,6 +119,26 @@ async def main():
104
119
  asyncio.run(main())
105
120
  ```
106
121
 
122
+ Sync:
123
+
124
+ ```python
125
+ from picopyn.synchronous import connect
126
+
127
+ # create and connect to the picodata cluster
128
+ with connect("postgresql://admin:pass@localhost:5432") as conn:
129
+ cur = conn.cursor()
130
+
131
+ # execute DDL operations
132
+ cur.execute('''
133
+ CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
134
+ ''')
135
+
136
+ # execute DML/DQL operations
137
+ cur.execute('INSERT INTO "warehouse" VALUES (%s, %s)', (1, "test"))
138
+ cur.execute('SELECT * FROM "warehouse"')
139
+ print(cur.fetchall())
140
+ ```
141
+
107
142
  ## Development
108
143
  For development we use [uv](https://docs.astral.sh/uv/getting-started/installation/) as package manager and [docker compose](https://docs.docker.com/compose/install/) for test environment.
109
144
 
@@ -6,11 +6,23 @@
6
6
 
7
7
  **Picopyn** is a Python package for working with the distributed [Picodata](https://picodata.io/en/) database.
8
8
 
9
+ **Version compatibility**:
10
+ | Picodata version | Picopyn version |
11
+ | ----------------- | ----------------- |
12
+ | >=25.2.1, <25.4.4 | 0.1.1 |
13
+ | >=25.4.4, <25.5.1 | 0.2.0 |
14
+ | >=25.5.1, <26.1.x | 1.0.0 |
15
+ | >=26.1.1, <26.2.x | 2.0.0 |
16
+ | 26.2.x | ⚠️ Unreleased yet |
17
+
9
18
  ## Features
10
19
  * Connection pooling with configurable pool size
11
20
  * Optional automatic node discovery
12
21
  * Pluggable load-balancing strategies
13
- * Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous API (based on [psycopg](https://www.psycopg.org/))
22
+ * Asynchronous API based on [asyncpg](https://github.com/MagicStack/asyncpg)
23
+ * (async only) Shard-aware routing
24
+ * (async only) Automatic pool membership reconciliation
25
+ * Synchronous [DPAPI-compatible](https://peps.python.org/pep-0249/) API based on [psycopg](https://www.psycopg.org/)
14
26
 
15
27
  ## Navigation
16
28
  * [Installation](#installation)
@@ -31,16 +43,18 @@ pip install picopyn
31
43
  Or from source:
32
44
 
33
45
  ```sh
34
- git clone https://git.picodata.io/solution/picopyn.git
46
+ git clone https://git.picodata.io/core/drivers/picopyn.git
35
47
  cd picopyn
36
48
  pip install -e .
37
49
  ```
38
50
 
39
51
  ## Quickstart
40
52
 
53
+ Async:
54
+
41
55
  ```python
42
56
  import asyncio
43
- from picopyn import Client
57
+ from picopyn.asynchronous import Client
44
58
 
45
59
  async def main():
46
60
  # create and connect client to the picodata cluster
@@ -52,9 +66,9 @@ async def main():
52
66
  CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
53
67
  ''')
54
68
 
55
- # execute DML operations
56
- await client.execute('INSERT INTO \"warehouse\" VALUES ($1::int, $2::varchar)', 1, "test")
57
- rows = await client.fetch('SELECT * FROM \"warehouse\"')
69
+ # execute DML/DQL operations
70
+ await client.execute('INSERT INTO "warehouse" VALUES ($1::int, $2::varchar)', 1, "test")
71
+ rows = await client.fetch('SELECT * FROM "warehouse"')
58
72
  print(rows)
59
73
 
60
74
  await client.close()
@@ -62,6 +76,26 @@ async def main():
62
76
  asyncio.run(main())
63
77
  ```
64
78
 
79
+ Sync:
80
+
81
+ ```python
82
+ from picopyn.synchronous import connect
83
+
84
+ # create and connect to the picodata cluster
85
+ with connect("postgresql://admin:pass@localhost:5432") as conn:
86
+ cur = conn.cursor()
87
+
88
+ # execute DDL operations
89
+ cur.execute('''
90
+ CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
91
+ ''')
92
+
93
+ # execute DML/DQL operations
94
+ cur.execute('INSERT INTO "warehouse" VALUES (%s, %s)', (1, "test"))
95
+ cur.execute('SELECT * FROM "warehouse"')
96
+ print(cur.fetchall())
97
+ ```
98
+
65
99
  ## Development
66
100
  For development we use [uv](https://docs.astral.sh/uv/getting-started/installation/) as package manager and [docker compose](https://docs.docker.com/compose/install/) for test environment.
67
101
 
@@ -8,7 +8,7 @@ from uuid import UUID
8
8
  import matplotlib
9
9
  import numpy as np
10
10
 
11
- from picopyn import Client
11
+ from picopyn.asynchronous import Client
12
12
 
13
13
  matplotlib.use("Agg")
14
14
  import os
@@ -0,0 +1,6 @@
1
+ # Async
2
+
3
+ ::: picopyn.asynchronous
4
+ options:
5
+ filters:
6
+ - "!^_"
@@ -0,0 +1,18 @@
1
+ # Async: Internal
2
+
3
+ !!! warning
4
+ Not public API. Driver wires this up and owns it; nothing here is meant
5
+ to be constructed or called directly. Documented anyway so you can see
6
+ how it actually works
7
+
8
+ ::: picopyn.asynchronous.topology_tracker
9
+ options:
10
+ show_root_heading: true
11
+ filters:
12
+ - "!^_"
13
+
14
+ ::: picopyn.asynchronous.metadata_service
15
+ options:
16
+ show_root_heading: true
17
+ filters:
18
+ - "!^_"
@@ -0,0 +1,7 @@
1
+ # Bucket ID
2
+
3
+ ::: picopyn.bucket
4
+ options:
5
+ filters:
6
+ - "!^_"
7
+ - "^__init__$"
@@ -0,0 +1,3 @@
1
+ # Changelog
2
+
3
+ --8<-- "CHANGELOG.md"
@@ -0,0 +1,7 @@
1
+ # Contributing
2
+
3
+ Contributions are welcome! The project is hosted on GitLab:
4
+
5
+ **[git.picodata.io/core/drivers/picopyn](https://git.picodata.io/core/drivers/picopyn)**
6
+
7
+ To get started with the development environment, running tests, and code style requirements, see the [Development](https://git.picodata.io/core/drivers/picopyn#development) section in the project README.
@@ -0,0 +1,64 @@
1
+ ```mermaid
2
+ ---
3
+ title: Pool initialization
4
+ ---
5
+
6
+ flowchart TD
7
+ %% === Initialization ===
8
+ classDef success fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
9
+ classDef error fill:#ffebee,stroke:#f44336,color:#b71c1c
10
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
11
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
12
+
13
+ START([Start]):::neutral
14
+ DONE([Pool ready]):::success
15
+ ERROR([Initialization failed]):::error
16
+ CFG[/"Pool settings<br>· DSN string<br>· pool size<br>· forbidden tiers<br>· cluster discovery mode"/]
17
+
18
+ USAGE_DETAILS_NOTE>"Return to pool lifecycle details"]:::note
19
+ click USAGE_DETAILS_NOTE "#picopyn.asynchronous.Pool" "Return to pool lifecycle details"
20
+
21
+ %% === Data objects ===
22
+ POOL[[Connection pool]]
23
+ B_DSN[[DSN node list]]
24
+ B_SYS[(Picodata system tables)]
25
+ D_DSN[[DSN node list]]
26
+ D_SYS[(Picodata system tables)]
27
+ CAND[[Candidate node list]]
28
+
29
+ %% === Actions ===
30
+
31
+ START --> CFG --> PARSE[Parse DSN string to DSN node list] --> DISCOVERY{Cluster discovery enabled?}
32
+
33
+ DISCOVERY -->|yes| D_FILTER[Use DSN nodes to connect to<br>Picodata cluster and retrieve<br>online nodes with allowed tiers<br>as a candidate node list]
34
+ DISCOVERY -->|no| B_FILTER[Filter DSN nodes: use only online<br>nodes with allowed tiers]
35
+
36
+ subgraph DiscoverMode["Cluster discovery mode"]
37
+ D_FILTER --> D_FILL_POOL[Try to fill pool with connections<br>to candidates up to pool size]
38
+
39
+ D_DETAILS_NOTE>"Discovery mode details"]:::note
40
+ click D_DETAILS_NOTE "#pool-init-discovery-enabled" "Discovery mode details"
41
+
42
+ D_FILTER <-. write .-> CAND
43
+ CAND <-. read .-> D_FILL_POOL
44
+ end
45
+
46
+ subgraph BootstrapMode["Bootstrap mode"]
47
+ B_FILTER --> B_FILL_POOL[Try to fill pool with connections<br>to DSN nodes up to pool size]
48
+
49
+ B_DETAILS_NOTE>"Bootstrap mode details"]:::note
50
+ click B_DETAILS_NOTE "#pool-init-discovery-disabled" "Bootstrap mode details"
51
+ end
52
+
53
+ B_DSN <-. read .-> B_FILTER
54
+ B_SYS <-. read .-> B_FILTER
55
+ B_FILL_POOL <-. write .-> POOL
56
+
57
+ D_DSN <-. read .-> D_FILTER
58
+ D_SYS <-. read .-> D_FILTER
59
+ D_FILL_POOL <-. write .-> POOL
60
+
61
+ POOL <-.-> POOL_CHECK{Pool full?}
62
+ POOL_CHECK -->|no| ERROR
63
+ POOL_CHECK -->|yes| DONE
64
+ ```
@@ -0,0 +1,46 @@
1
+ ```mermaid
2
+ ---
3
+ title: Pool initialization with cluster discovery disabled
4
+ ---
5
+
6
+ flowchart TD
7
+
8
+ %% === Initialization ===
9
+ classDef success fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
10
+ classDef error fill:#ffebee,stroke:#f44336,color:#b71c1c
11
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
12
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
13
+
14
+ START([Start]):::neutral
15
+ DONE([Pool ready]):::success
16
+ ERROR([Initialization failed]):::error
17
+ CFG[/"Pool settings<br>· DSN string<br>· pool size<br>· forbidden tiers<br>· cluster discovery disabled"/]
18
+
19
+ GENERAL_DETAILS_NOTE>"Return to general algorithm"]:::note
20
+ click GENERAL_DETAILS_NOTE "#picopyn.asynchronous.Pool.connect" "Return to general algorithm"
21
+
22
+ %% === Data objects ===
23
+ B_DSN[[DSN node list]]
24
+ BOOTSTRAP_POOL[[Connection pool]]
25
+ SYS[(Picodata system tables)]
26
+
27
+ %% === Bootstrap mode ===
28
+ START --> CFG --> PARSE[Parse DSN string to DSN node list] --> B_PoolFilling
29
+ PARSE <-. write .-> B_DSN
30
+ B_DSN <-. read .-> B_CONNECT_NODE[Connect to DSN node]
31
+
32
+ B_PoolFilling_NOTE>"iterate over DSN nodes until either:<br>- the list is empty<br>- pool_size connections<br>have been established"]:::note
33
+ B_PoolFilling_NOTE -.-> B_PoolFilling
34
+ subgraph B_PoolFilling["Pool filling"]
35
+ direction TB
36
+ SYS <-. read .-> B_CHECK_NODE_TIER[Check node tier]
37
+ B_CONNECT_NODE --> B_CHECK_NODE_TIER
38
+ B_CHECK_NODE_TIER -->|node offline or tier forbidden| B_EXCLUDE[Exclude DSN node]
39
+ B_CHECK_NODE_TIER -->|node online and tier allowed| B_SAVE[Save connection to pool]
40
+ B_SAVE <-. write .-> BOOTSTRAP_POOL
41
+ end
42
+
43
+ B_PoolFilling --> POOL_CHECK{Pool full?}
44
+ POOL_CHECK -->|no| ERROR
45
+ POOL_CHECK -->|yes| DONE
46
+ ```
@@ -0,0 +1,55 @@
1
+ ```mermaid
2
+ ---
3
+ title: Pool initialization with cluster discovery enabled
4
+ ---
5
+
6
+ flowchart TD
7
+
8
+ %% === Initialization ===
9
+ classDef success fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
10
+ classDef error fill:#ffebee,stroke:#f44336,color:#b71c1c
11
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
12
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
13
+
14
+ START([Start]):::neutral
15
+ DONE([Pool ready]):::success
16
+ D_ERROR_CAND([Initialization failed]):::error
17
+ ERROR([Initialization failed]):::error
18
+ CFG[/"Pool settings<br>· DSN string<br>· pool_size<br>· forbidden tiers<br>· cluster discovery enabled"/]
19
+
20
+ GENERAL_DETAILS_NOTE>"Return to general algorithm"]:::note
21
+ click GENERAL_DETAILS_NOTE "#picopyn.asynchronous.Pool.connect" "Return to general algorithm"
22
+
23
+ %% === Data objects ===
24
+ D_DSN[[DSN node list]]
25
+ CAND[[Candidate node list]]
26
+ DISCOVERY_POOL[[Connection pool]]
27
+ SYS[(Picodata system tables)]
28
+
29
+ %% === Discovery mode ===
30
+ START --> CFG --> PARSE[Parse DSN string to<br>DSN node list] --> DiscoverNodes
31
+ PARSE <-. write .-> D_DSN
32
+
33
+ D_DiscoverNodes_NOTE>"iterate over DSN nodes<br>to establish a temporary<br>connection and retrieve the<br>list of online nodes with<br>allowed tiers"]:::note
34
+ D_DiscoverNodes_NOTE -.-> DiscoverNodes
35
+ D_DSN <-. read .-> D_RECEIVE[Try to connect and receive cluster nodes]
36
+ subgraph DiscoverNodes["Discover cluster nodes"]
37
+ SYS <-. read .-> D_RECEIVE
38
+ D_RECEIVE -->|could not select candidates| D_ERROR_CAND
39
+ D_RECEIVE <-. write .-> CAND
40
+ end
41
+ DiscoverNodes --> D_PoolFilling
42
+
43
+ D_PoolFilling_NOTE>"iterate over candidate nodes<br>until either:<br>- the list is empty<br>- pool_size connections<br>have been established"]:::note
44
+ D_PoolFilling_NOTE -.-> D_PoolFilling
45
+ CAND <-. read .-> D_CONNECT_NODE[Connect to candidate node]
46
+ subgraph D_PoolFilling["Pool filling"]
47
+ D_CONNECT_NODE -->|connection error| D_EXCLUDE[Exclude candidate node]
48
+ D_CONNECT_NODE -->|connected| D_SAVE[Save opened connection to pool]
49
+ D_SAVE <-. write .-> DISCOVERY_POOL
50
+ end
51
+
52
+ D_PoolFilling --> POOL_CHECK{Pool full?}
53
+ POOL_CHECK -->|no| ERROR
54
+ POOL_CHECK -->|yes| DONE
55
+ ```
@@ -0,0 +1,44 @@
1
+ ```mermaid
2
+ ---
3
+ title: Pool lifecycle
4
+ ---
5
+
6
+ flowchart TD
7
+ %% === Style ===
8
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
9
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
10
+
11
+ %% === Data objects ===
12
+ POOL[[Connection pool]]
13
+
14
+ %% === Lifecycle ===
15
+ START([Start]):::neutral
16
+ DONE([Pool closed]):::neutral
17
+
18
+ CFG[/"Pool settings<br>· DSN<br>· pool size<br>· forbidden tiers<br>· cluster discovery mode"/]
19
+
20
+ START --> CFG --> CREATE["Pool.__init__"] --> CONNECT["pool.connect()"] --> UsePool
21
+ CONNECT <-. write .-> POOL
22
+
23
+ CONNECT_DETAILS_NOTE>"Connect algorithm details"]:::note
24
+ click CONNECT_DETAILS_NOTE "#picopyn.asynchronous.Pool.connect" "Connect algorithm details"
25
+ CONNECT_DETAILS_NOTE -.-> CONNECT
26
+
27
+ RECONCILE_NOTE>"(discovery mode only):<br>pool membership changes<br>in the background:<br>connections are opened/closed<br>to match topology changes"]:::note
28
+ click RECONCILE_NOTE "#picopyn.asynchronous.Pool.topology" "Reconcile algorithm details"
29
+ RECONCILE_NOTE -.-> POOL
30
+
31
+ CYCLE_NOTE>"repeated for each<br>database operation"]:::note
32
+ CYCLE_NOTE -.-> UsePool
33
+
34
+ subgraph UsePool["Pool usage"]
35
+ direction TB
36
+ ACQUIRE["pool.acquire()"] --> USE["Execute queries<br>· pool.fetchrow()<br>· pool.fetch()<br>· pool.execute()"] --> RELEASE["pool.release(conn)"]
37
+ end
38
+
39
+ ACQUIRE <-. delete .-> POOL
40
+ RELEASE <-. write .-> POOL
41
+ CLOSE <-. delete .-> POOL
42
+ UsePool --> CLOSE
43
+ CLOSE --> DONE
44
+ ```
@@ -0,0 +1,55 @@
1
+ ```mermaid
2
+ ---
3
+ title: Query metadata -- pool initialization in cache meta context
4
+ ---
5
+
6
+ flowchart TD
7
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
8
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
9
+
10
+
11
+ INIT(["Pool init"]):::neutral
12
+
13
+ INIT --> CACHE_EMPTY["Create empty query meta cache"] --> CONNECT["Fill the pool with ordinary<br>connections to picodata"] --> Pool1
14
+
15
+ NOTE_CONN>"connection options require<br>statement invalidation signal only"]:::note
16
+ NOTE_CONN -.-> CONNECT
17
+
18
+ CONNECT_DETAILS_NOTE>"Connect algorithm details"]:::note
19
+ click CONNECT_DETAILS_NOTE "#picopyn.asynchronous.Pool.connect" "Connect algorithm details"
20
+ CONNECT_DETAILS_NOTE -.-> CONNECT
21
+
22
+ subgraph Pool1["Pool"]
23
+ direction TB
24
+
25
+ CONN11["conn to inst1"] --> PICO11[(Picodata)]
26
+ CONN12["..."] --> PICO12[(Picodata)]
27
+ CONN1N["conn to instN"] --> PICO1N[(Picodata)]
28
+
29
+ CACHE1[/"Query meta cache"/]
30
+ end
31
+
32
+ Pool1 --> CONNECT_SERV["create service connection<br>for meta listener"]
33
+
34
+ NOTE_CONN_SERV>"connection options require statement invalidation signal<br>and distribution key metadata"]:::note
35
+ NOTE_CONN_SERV -.-> CONNECT_SERV
36
+
37
+ CONNECT_SERV --> LIST["add picodata notice listener"]
38
+
39
+ LIST --> Pool
40
+
41
+ subgraph Pool["Pool"]
42
+ direction TB
43
+
44
+ CONN1["conn1 for<br>user usage"] --> PICO1[(Picodata1)]
45
+ CONN2["..."] --> PICO2[(...)]
46
+ CONNN["connN for<br>user usage"] --> PICON[(PicodataN)]
47
+
48
+ PIC(["can be any of"]) -.- CONNSERV["service connection with Notice listener"]
49
+ PICO1 -.- PIC
50
+ PICO2 -.- PIC
51
+ PICON -.- PIC
52
+
53
+ CACHE[/"Query meta cache"/]
54
+ end
55
+ ```
@@ -0,0 +1,44 @@
1
+ ```mermaid
2
+ ---
3
+ title: Query metadata -- usage lookup
4
+ ---
5
+
6
+ flowchart TD
7
+
8
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
9
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
10
+
11
+
12
+ QUERY(["parametrized query"]):::neutral --> CALL(["Pool.execute"]) --> HASH
13
+
14
+ HASH["hash the query text"] --> SEARCH_CACHE{"cached meta by hash?"}
15
+
16
+ CACHE1[/"Pool query meta cache"/] -. read .- SEARCH_CACHE
17
+ SEARCH_CACHE -->|yes| USE_CACHE["metadata found in cache"]
18
+ SEARCH_CACHE -->|no| CACHE_MISS["no metadata yet --<br>schedule background prepare"] --> ACQUIRE_CONN
19
+
20
+ subgraph Routing["BUCKET ROUTING"]
21
+ direction TB
22
+
23
+ CALC["calculate `bucket_id`<br>from dk_meta"]
24
+
25
+ CALC --> MAP_CONN["find bucket master<br>for this tier"]
26
+
27
+ MAP_CONN -->|routed| CALL_THE_CONN["run the query on<br>the chosen connection"]
28
+ MAP_CONN -->|cannot route| ACQUIRE_CONN
29
+
30
+ CALL_THE_CONN --> RESULT(["give result to the user"]):::neutral
31
+ end
32
+
33
+ USE_CACHE --> CALC
34
+
35
+ subgraph Fallback["FALLBACK"]
36
+ direction TB
37
+
38
+ ACQUIRE_CONN["acquire pool connection<br>via the configured balance strategy"]
39
+
40
+ ACQUIRE_CONN --> CALL_CONN["run the query on<br>the chosen connection"]
41
+
42
+ CALL_CONN --> RESULT2(["give result to the user"]):::neutral
43
+ end
44
+ ```
@@ -0,0 +1,50 @@
1
+ ```mermaid
2
+ ---
3
+ title: A node outside the pool died
4
+ ---
5
+
6
+ flowchart TD
7
+
8
+ classDef offline fill:#ffebee,stroke:#f44336,color:#b71c1c
9
+ classDef online fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
10
+ classDef neutral fill:#f5f5f5,stroke:#9e9e9e,color:#424242
11
+
12
+ classDef note fill:#fffde7,stroke:#f9a825,stroke-dasharray:4,color:#555
13
+
14
+ subgraph before["Before"]
15
+ subgraph Picodata_before["Picodata"]
16
+ p1[("ins 1")]:::online
17
+ p2[("ins 2")]:::online
18
+ p3[("ins 3")]:::online
19
+ p4[("ins 4")]:::online
20
+ end
21
+
22
+ subgraph Pool_before["Pool: max_size=3"]
23
+ c1["conn"] --> p1
24
+ c2["conn"] --> p2
25
+ c3["conn"] --> p3
26
+ end
27
+ end
28
+ EVENT1[/"Event<br>Instance 4 dead"/]:::neutral -.-> REFILL{{"reconcile pool"}}
29
+ before -.-> REFILL
30
+
31
+ NOTE>"Node outside the pool died<br>=> pool stays unchanged"]:::note
32
+ NOTE-.->REFILL
33
+
34
+ REFILL --> after
35
+
36
+ subgraph after["After"]
37
+ subgraph Picodata_after["Picodata"]
38
+ p21[("ins 1")]:::online
39
+ p22[("ins 2")]:::online
40
+ p23[("ins 3")]:::online
41
+ p24[("ins 4")]:::offline
42
+ end
43
+
44
+ subgraph Pool_after["Pool: max_size=3"]
45
+ c21["conn"] --> p21
46
+ c22["conn"] --> p22
47
+ c23["conn"] --> p23
48
+ end
49
+ end
50
+ ```