From 12cf076118570eebbff08c6b3090e0d4798447a1 Mon Sep 17 00:00:00 2001 From: cyfraeviolae Date: Wed, 3 Apr 2024 03:17:55 -0400 Subject: no venv --- .../site-packages/sqlalchemy/dialects/__init__.py | 61 - .../dialects/__pycache__/__init__.cpython-311.pyc | Bin 2097 -> 0 bytes .../dialects/__pycache__/_typing.cpython-311.pyc | Bin 1094 -> 0 bytes .../site-packages/sqlalchemy/dialects/_typing.py | 25 - .../sqlalchemy/dialects/mssql/__init__.py | 88 - .../mssql/__pycache__/__init__.cpython-311.pyc | Bin 2226 -> 0 bytes .../mssql/__pycache__/aioodbc.cpython-311.pyc | Bin 2591 -> 0 bytes .../mssql/__pycache__/base.cpython-311.pyc | Bin 157867 -> 0 bytes .../__pycache__/information_schema.cpython-311.pyc | Bin 9859 -> 0 bytes .../mssql/__pycache__/json.cpython-311.pyc | Bin 5828 -> 0 bytes .../mssql/__pycache__/provision.cpython-311.pyc | Bin 8399 -> 0 bytes .../mssql/__pycache__/pymssql.cpython-311.pyc | Bin 6678 -> 0 bytes .../mssql/__pycache__/pyodbc.cpython-311.pyc | Bin 33146 -> 0 bytes .../sqlalchemy/dialects/mssql/aioodbc.py | 64 - .../sqlalchemy/dialects/mssql/base.py | 4007 ---------------- .../dialects/mssql/information_schema.py | 254 - .../sqlalchemy/dialects/mssql/json.py | 133 - .../sqlalchemy/dialects/mssql/provision.py | 155 - .../sqlalchemy/dialects/mssql/pymssql.py | 125 - .../sqlalchemy/dialects/mssql/pyodbc.py | 745 --- .../sqlalchemy/dialects/mysql/__init__.py | 101 - .../mysql/__pycache__/__init__.cpython-311.pyc | Bin 2654 -> 0 bytes .../mysql/__pycache__/aiomysql.cpython-311.pyc | Bin 18123 -> 0 bytes .../mysql/__pycache__/asyncmy.cpython-311.pyc | Bin 18787 -> 0 bytes .../mysql/__pycache__/base.cpython-311.pyc | Bin 145328 -> 0 bytes .../mysql/__pycache__/cymysql.cpython-311.pyc | Bin 3348 -> 0 bytes .../dialects/mysql/__pycache__/dml.cpython-311.pyc | Bin 9104 -> 0 bytes .../mysql/__pycache__/enumerated.cpython-311.pyc | Bin 11258 -> 0 bytes .../mysql/__pycache__/expression.cpython-311.pyc | Bin 5392 -> 0 bytes .../mysql/__pycache__/json.cpython-311.pyc | Bin 3982 -> 0 bytes .../mysql/__pycache__/mariadb.cpython-311.pyc | Bin 1171 -> 0 bytes .../__pycache__/mariadbconnector.cpython-311.pyc | Bin 12859 -> 0 bytes .../__pycache__/mysqlconnector.cpython-311.pyc | Bin 9700 -> 0 bytes .../mysql/__pycache__/mysqldb.cpython-311.pyc | Bin 12714 -> 0 bytes .../mysql/__pycache__/provision.cpython-311.pyc | Bin 4848 -> 0 bytes .../mysql/__pycache__/pymysql.cpython-311.pyc | Bin 5649 -> 0 bytes .../mysql/__pycache__/pyodbc.cpython-311.pyc | Bin 5859 -> 0 bytes .../mysql/__pycache__/reflection.cpython-311.pyc | Bin 27141 -> 0 bytes .../__pycache__/reserved_words.cpython-311.pyc | Bin 4446 -> 0 bytes .../mysql/__pycache__/types.cpython-311.pyc | Bin 33731 -> 0 bytes .../sqlalchemy/dialects/mysql/aiomysql.py | 332 -- .../sqlalchemy/dialects/mysql/asyncmy.py | 337 -- .../sqlalchemy/dialects/mysql/base.py | 3447 -------------- .../sqlalchemy/dialects/mysql/cymysql.py | 84 - .../site-packages/sqlalchemy/dialects/mysql/dml.py | 219 - .../sqlalchemy/dialects/mysql/enumerated.py | 244 - .../sqlalchemy/dialects/mysql/expression.py | 141 - .../sqlalchemy/dialects/mysql/json.py | 81 - .../sqlalchemy/dialects/mysql/mariadb.py | 32 - .../sqlalchemy/dialects/mysql/mariadbconnector.py | 275 -- .../sqlalchemy/dialects/mysql/mysqlconnector.py | 179 - .../sqlalchemy/dialects/mysql/mysqldb.py | 303 -- .../sqlalchemy/dialects/mysql/provision.py | 107 - .../sqlalchemy/dialects/mysql/pymysql.py | 137 - .../sqlalchemy/dialects/mysql/pyodbc.py | 138 - .../sqlalchemy/dialects/mysql/reflection.py | 677 --- .../sqlalchemy/dialects/mysql/reserved_words.py | 571 --- .../sqlalchemy/dialects/mysql/types.py | 774 --- .../sqlalchemy/dialects/oracle/__init__.py | 67 - .../oracle/__pycache__/__init__.cpython-311.pyc | Bin 1731 -> 0 bytes .../oracle/__pycache__/base.cpython-311.pyc | Bin 136700 -> 0 bytes .../oracle/__pycache__/cx_oracle.cpython-311.pyc | Bin 62959 -> 0 bytes .../oracle/__pycache__/dictionary.cpython-311.pyc | Bin 32392 -> 0 bytes .../oracle/__pycache__/oracledb.cpython-311.pyc | Bin 15311 -> 0 bytes .../oracle/__pycache__/provision.cpython-311.pyc | Bin 12688 -> 0 bytes .../oracle/__pycache__/types.cpython-311.pyc | Bin 13844 -> 0 bytes .../sqlalchemy/dialects/oracle/base.py | 3240 ------------- .../sqlalchemy/dialects/oracle/cx_oracle.py | 1492 ------ .../sqlalchemy/dialects/oracle/dictionary.py | 507 -- .../sqlalchemy/dialects/oracle/oracledb.py | 311 -- .../sqlalchemy/dialects/oracle/provision.py | 220 - .../sqlalchemy/dialects/oracle/types.py | 287 -- .../sqlalchemy/dialects/postgresql/__init__.py | 167 - .../__pycache__/__init__.cpython-311.pyc | Bin 4640 -> 0 bytes .../__pycache__/_psycopg_common.cpython-311.pyc | Bin 8755 -> 0 bytes .../postgresql/__pycache__/array.cpython-311.pyc | Bin 17843 -> 0 bytes .../postgresql/__pycache__/asyncpg.cpython-311.pyc | Bin 61182 -> 0 bytes .../postgresql/__pycache__/base.cpython-311.pyc | Bin 207977 -> 0 bytes .../postgresql/__pycache__/dml.cpython-311.pyc | Bin 12630 -> 0 bytes .../postgresql/__pycache__/ext.cpython-311.pyc | Bin 20760 -> 0 bytes .../postgresql/__pycache__/hstore.cpython-311.pyc | Bin 16586 -> 0 bytes .../postgresql/__pycache__/json.cpython-311.pyc | Bin 14343 -> 0 bytes .../__pycache__/named_types.cpython-311.pyc | Bin 25105 -> 0 bytes .../__pycache__/operators.cpython-311.pyc | Bin 2188 -> 0 bytes .../postgresql/__pycache__/pg8000.cpython-311.pyc | Bin 32853 -> 0 bytes .../__pycache__/pg_catalog.cpython-311.pyc | Bin 13658 -> 0 bytes .../__pycache__/provision.cpython-311.pyc | Bin 9178 -> 0 bytes .../postgresql/__pycache__/psycopg.cpython-311.pyc | Bin 39132 -> 0 bytes .../__pycache__/psycopg2.cpython-311.pyc | Bin 36923 -> 0 bytes .../__pycache__/psycopg2cffi.cpython-311.pyc | Bin 2307 -> 0 bytes .../postgresql/__pycache__/ranges.cpython-311.pyc | Bin 37622 -> 0 bytes .../postgresql/__pycache__/types.cpython-311.pyc | Bin 12406 -> 0 bytes .../dialects/postgresql/_psycopg_common.py | 187 - .../sqlalchemy/dialects/postgresql/array.py | 425 -- .../sqlalchemy/dialects/postgresql/asyncpg.py | 1262 ----- .../sqlalchemy/dialects/postgresql/base.py | 5007 -------------------- .../sqlalchemy/dialects/postgresql/dml.py | 310 -- .../sqlalchemy/dialects/postgresql/ext.py | 496 -- .../sqlalchemy/dialects/postgresql/hstore.py | 397 -- .../sqlalchemy/dialects/postgresql/json.py | 325 -- .../sqlalchemy/dialects/postgresql/named_types.py | 509 -- .../sqlalchemy/dialects/postgresql/operators.py | 129 - .../sqlalchemy/dialects/postgresql/pg8000.py | 662 --- .../sqlalchemy/dialects/postgresql/pg_catalog.py | 300 -- .../sqlalchemy/dialects/postgresql/provision.py | 175 - .../sqlalchemy/dialects/postgresql/psycopg.py | 749 --- .../sqlalchemy/dialects/postgresql/psycopg2.py | 876 ---- .../sqlalchemy/dialects/postgresql/psycopg2cffi.py | 61 - .../sqlalchemy/dialects/postgresql/ranges.py | 1029 ---- .../sqlalchemy/dialects/postgresql/types.py | 303 -- .../sqlalchemy/dialects/sqlite/__init__.py | 57 - .../sqlite/__pycache__/__init__.cpython-311.pyc | Bin 1381 -> 0 bytes .../sqlite/__pycache__/aiosqlite.cpython-311.pyc | Bin 19317 -> 0 bytes .../sqlite/__pycache__/base.cpython-311.pyc | Bin 104751 -> 0 bytes .../sqlite/__pycache__/dml.cpython-311.pyc | Bin 10200 -> 0 bytes .../sqlite/__pycache__/json.cpython-311.pyc | Bin 4319 -> 0 bytes .../sqlite/__pycache__/provision.cpython-311.pyc | Bin 7990 -> 0 bytes .../sqlite/__pycache__/pysqlcipher.cpython-311.pyc | Bin 6604 -> 0 bytes .../sqlite/__pycache__/pysqlite.cpython-311.pyc | Bin 33807 -> 0 bytes .../sqlalchemy/dialects/sqlite/aiosqlite.py | 396 -- .../sqlalchemy/dialects/sqlite/base.py | 2782 ----------- .../sqlalchemy/dialects/sqlite/dml.py | 240 - .../sqlalchemy/dialects/sqlite/json.py | 92 - .../sqlalchemy/dialects/sqlite/provision.py | 198 - .../sqlalchemy/dialects/sqlite/pysqlcipher.py | 155 - .../sqlalchemy/dialects/sqlite/pysqlite.py | 756 --- .../dialects/type_migration_guidelines.txt | 145 - 127 files changed, 38150 deletions(-) delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/_typing.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/aioodbc.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/information_schema.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/json.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/provision.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pymssql.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pyodbc.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/dictionary.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/oracledb.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/provision.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/types.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/array.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/dml.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ext.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/hstore.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/json.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/named_types.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/operators.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg8000.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/provision.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ranges.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/types.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__init__.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/base.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/dml.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/json.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/provision.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py delete mode 100644 venv/lib/python3.11/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/dialects') diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__init__.py deleted file mode 100644 index 7d5cc1c..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -# dialects/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - -from __future__ import annotations - -from typing import Callable -from typing import Optional -from typing import Type -from typing import TYPE_CHECKING - -from .. import util - -if TYPE_CHECKING: - from ..engine.interfaces import Dialect - -__all__ = ("mssql", "mysql", "oracle", "postgresql", "sqlite") - - -def _auto_fn(name: str) -> Optional[Callable[[], Type[Dialect]]]: - """default dialect importer. - - plugs into the :class:`.PluginLoader` - as a first-hit system. - - """ - if "." in name: - dialect, driver = name.split(".") - else: - dialect = name - driver = "base" - - try: - if dialect == "mariadb": - # it's "OK" for us to hardcode here since _auto_fn is already - # hardcoded. if mysql / mariadb etc were third party dialects - # they would just publish all the entrypoints, which would actually - # look much nicer. - module = __import__( - "sqlalchemy.dialects.mysql.mariadb" - ).dialects.mysql.mariadb - return module.loader(driver) # type: ignore - else: - module = __import__("sqlalchemy.dialects.%s" % (dialect,)).dialects - module = getattr(module, dialect) - except ImportError: - return None - - if hasattr(module, driver): - module = getattr(module, driver) - return lambda: module.dialect - else: - return None - - -registry = util.PluginLoader("sqlalchemy.dialects", auto_fn=_auto_fn) - -plugins = util.PluginLoader("sqlalchemy.plugins") diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 5287370..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-311.pyc deleted file mode 100644 index c91d658..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/_typing.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/_typing.py deleted file mode 100644 index 9ee6e4b..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/_typing.py +++ /dev/null @@ -1,25 +0,0 @@ -# dialects/_typing.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -from typing import Any -from typing import Iterable -from typing import Mapping -from typing import Optional -from typing import Union - -from ..sql._typing import _DDLColumnArgument -from ..sql.elements import DQLDMLClauseElement -from ..sql.schema import ColumnCollectionConstraint -from ..sql.schema import Index - - -_OnConflictConstraintT = Union[str, ColumnCollectionConstraint, Index, None] -_OnConflictIndexElementsT = Optional[Iterable[_DDLColumnArgument]] -_OnConflictIndexWhereT = Optional[DQLDMLClauseElement] -_OnConflictSetT = Optional[Mapping[Any, Any]] -_OnConflictWhereT = Union[DQLDMLClauseElement, str, None] diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__init__.py deleted file mode 100644 index 19ab7c4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -# dialects/mssql/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from . import aioodbc # noqa -from . import base # noqa -from . import pymssql # noqa -from . import pyodbc # noqa -from .base import BIGINT -from .base import BINARY -from .base import BIT -from .base import CHAR -from .base import DATE -from .base import DATETIME -from .base import DATETIME2 -from .base import DATETIMEOFFSET -from .base import DECIMAL -from .base import DOUBLE_PRECISION -from .base import FLOAT -from .base import IMAGE -from .base import INTEGER -from .base import JSON -from .base import MONEY -from .base import NCHAR -from .base import NTEXT -from .base import NUMERIC -from .base import NVARCHAR -from .base import REAL -from .base import ROWVERSION -from .base import SMALLDATETIME -from .base import SMALLINT -from .base import SMALLMONEY -from .base import SQL_VARIANT -from .base import TEXT -from .base import TIME -from .base import TIMESTAMP -from .base import TINYINT -from .base import UNIQUEIDENTIFIER -from .base import VARBINARY -from .base import VARCHAR -from .base import XML -from ...sql import try_cast - - -base.dialect = dialect = pyodbc.dialect - - -__all__ = ( - "JSON", - "INTEGER", - "BIGINT", - "SMALLINT", - "TINYINT", - "VARCHAR", - "NVARCHAR", - "CHAR", - "NCHAR", - "TEXT", - "NTEXT", - "DECIMAL", - "NUMERIC", - "FLOAT", - "DATETIME", - "DATETIME2", - "DATETIMEOFFSET", - "DATE", - "DOUBLE_PRECISION", - "TIME", - "SMALLDATETIME", - "BINARY", - "VARBINARY", - "BIT", - "REAL", - "IMAGE", - "TIMESTAMP", - "ROWVERSION", - "MONEY", - "SMALLMONEY", - "UNIQUEIDENTIFIER", - "SQL_VARIANT", - "XML", - "dialect", - "try_cast", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 225eebc..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-311.pyc deleted file mode 100644 index 3a0585c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc deleted file mode 100644 index 527fcdf..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc deleted file mode 100644 index 88bc790..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc deleted file mode 100644 index 6312e58..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index fd72045..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc deleted file mode 100644 index 84555dc..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc deleted file mode 100644 index 19ecd43..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/aioodbc.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/aioodbc.py deleted file mode 100644 index 65945d9..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/aioodbc.py +++ /dev/null @@ -1,64 +0,0 @@ -# dialects/mssql/aioodbc.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -r""" -.. dialect:: mssql+aioodbc - :name: aioodbc - :dbapi: aioodbc - :connectstring: mssql+aioodbc://:@ - :url: https://pypi.org/project/aioodbc/ - - -Support for the SQL Server database in asyncio style, using the aioodbc -driver which itself is a thread-wrapper around pyodbc. - -.. versionadded:: 2.0.23 Added the mssql+aioodbc dialect which builds - on top of the pyodbc and general aio* dialect architecture. - -Using a special asyncio mediation layer, the aioodbc dialect is usable -as the backend for the :ref:`SQLAlchemy asyncio ` -extension package. - -Most behaviors and caveats for this driver are the same as that of the -pyodbc dialect used on SQL Server; see :ref:`mssql_pyodbc` for general -background. - -This dialect should normally be used only with the -:func:`_asyncio.create_async_engine` engine creation function; connection -styles are otherwise equivalent to those documented in the pyodbc section:: - - from sqlalchemy.ext.asyncio import create_async_engine - engine = create_async_engine( - "mssql+aioodbc://scott:tiger@mssql2017:1433/test?" - "driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes" - ) - - - -""" - -from __future__ import annotations - -from .pyodbc import MSDialect_pyodbc -from .pyodbc import MSExecutionContext_pyodbc -from ...connectors.aioodbc import aiodbcConnector - - -class MSExecutionContext_aioodbc(MSExecutionContext_pyodbc): - def create_server_side_cursor(self): - return self._dbapi_connection.cursor(server_side=True) - - -class MSDialectAsync_aioodbc(aiodbcConnector, MSDialect_pyodbc): - driver = "aioodbc" - - supports_statement_cache = True - - execution_ctx_cls = MSExecutionContext_aioodbc - - -dialect = MSDialectAsync_aioodbc diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/base.py deleted file mode 100644 index 872f858..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/base.py +++ /dev/null @@ -1,4007 +0,0 @@ -# dialects/mssql/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -""" -.. dialect:: mssql - :name: Microsoft SQL Server - :full_support: 2017 - :normal_support: 2012+ - :best_effort: 2005+ - -.. _mssql_external_dialects: - -External Dialects ------------------ - -In addition to the above DBAPI layers with native SQLAlchemy support, there -are third-party dialects for other DBAPI layers that are compatible -with SQL Server. See the "External Dialects" list on the -:ref:`dialect_toplevel` page. - -.. _mssql_identity: - -Auto Increment Behavior / IDENTITY Columns ------------------------------------------- - -SQL Server provides so-called "auto incrementing" behavior using the -``IDENTITY`` construct, which can be placed on any single integer column in a -table. SQLAlchemy considers ``IDENTITY`` within its default "autoincrement" -behavior for an integer primary key column, described at -:paramref:`_schema.Column.autoincrement`. This means that by default, -the first integer primary key column in a :class:`_schema.Table` will be -considered to be the identity column - unless it is associated with a -:class:`.Sequence` - and will generate DDL as such:: - - from sqlalchemy import Table, MetaData, Column, Integer - - m = MetaData() - t = Table('t', m, - Column('id', Integer, primary_key=True), - Column('x', Integer)) - m.create_all(engine) - -The above example will generate DDL as: - -.. sourcecode:: sql - - CREATE TABLE t ( - id INTEGER NOT NULL IDENTITY, - x INTEGER NULL, - PRIMARY KEY (id) - ) - -For the case where this default generation of ``IDENTITY`` is not desired, -specify ``False`` for the :paramref:`_schema.Column.autoincrement` flag, -on the first integer primary key column:: - - m = MetaData() - t = Table('t', m, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('x', Integer)) - m.create_all(engine) - -To add the ``IDENTITY`` keyword to a non-primary key column, specify -``True`` for the :paramref:`_schema.Column.autoincrement` flag on the desired -:class:`_schema.Column` object, and ensure that -:paramref:`_schema.Column.autoincrement` -is set to ``False`` on any integer primary key column:: - - m = MetaData() - t = Table('t', m, - Column('id', Integer, primary_key=True, autoincrement=False), - Column('x', Integer, autoincrement=True)) - m.create_all(engine) - -.. versionchanged:: 1.4 Added :class:`_schema.Identity` construct - in a :class:`_schema.Column` to specify the start and increment - parameters of an IDENTITY. These replace - the use of the :class:`.Sequence` object in order to specify these values. - -.. deprecated:: 1.4 - - The ``mssql_identity_start`` and ``mssql_identity_increment`` parameters - to :class:`_schema.Column` are deprecated and should we replaced by - an :class:`_schema.Identity` object. Specifying both ways of configuring - an IDENTITY will result in a compile error. - These options are also no longer returned as part of the - ``dialect_options`` key in :meth:`_reflection.Inspector.get_columns`. - Use the information in the ``identity`` key instead. - -.. deprecated:: 1.3 - - The use of :class:`.Sequence` to specify IDENTITY characteristics is - deprecated and will be removed in a future release. Please use - the :class:`_schema.Identity` object parameters - :paramref:`_schema.Identity.start` and - :paramref:`_schema.Identity.increment`. - -.. versionchanged:: 1.4 Removed the ability to use a :class:`.Sequence` - object to modify IDENTITY characteristics. :class:`.Sequence` objects - now only manipulate true T-SQL SEQUENCE types. - -.. note:: - - There can only be one IDENTITY column on the table. When using - ``autoincrement=True`` to enable the IDENTITY keyword, SQLAlchemy does not - guard against multiple columns specifying the option simultaneously. The - SQL Server database will instead reject the ``CREATE TABLE`` statement. - -.. note:: - - An INSERT statement which attempts to provide a value for a column that is - marked with IDENTITY will be rejected by SQL Server. In order for the - value to be accepted, a session-level option "SET IDENTITY_INSERT" must be - enabled. The SQLAlchemy SQL Server dialect will perform this operation - automatically when using a core :class:`_expression.Insert` - construct; if the - execution specifies a value for the IDENTITY column, the "IDENTITY_INSERT" - option will be enabled for the span of that statement's invocation.However, - this scenario is not high performing and should not be relied upon for - normal use. If a table doesn't actually require IDENTITY behavior in its - integer primary key column, the keyword should be disabled when creating - the table by ensuring that ``autoincrement=False`` is set. - -Controlling "Start" and "Increment" -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Specific control over the "start" and "increment" values for -the ``IDENTITY`` generator are provided using the -:paramref:`_schema.Identity.start` and :paramref:`_schema.Identity.increment` -parameters passed to the :class:`_schema.Identity` object:: - - from sqlalchemy import Table, Integer, Column, Identity - - test = Table( - 'test', metadata, - Column( - 'id', - Integer, - primary_key=True, - Identity(start=100, increment=10) - ), - Column('name', String(20)) - ) - -The CREATE TABLE for the above :class:`_schema.Table` object would be: - -.. sourcecode:: sql - - CREATE TABLE test ( - id INTEGER NOT NULL IDENTITY(100,10) PRIMARY KEY, - name VARCHAR(20) NULL, - ) - -.. note:: - - The :class:`_schema.Identity` object supports many other parameter in - addition to ``start`` and ``increment``. These are not supported by - SQL Server and will be ignored when generating the CREATE TABLE ddl. - -.. versionchanged:: 1.3.19 The :class:`_schema.Identity` object is - now used to affect the - ``IDENTITY`` generator for a :class:`_schema.Column` under SQL Server. - Previously, the :class:`.Sequence` object was used. As SQL Server now - supports real sequences as a separate construct, :class:`.Sequence` will be - functional in the normal way starting from SQLAlchemy version 1.4. - - -Using IDENTITY with Non-Integer numeric types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -SQL Server also allows ``IDENTITY`` to be used with ``NUMERIC`` columns. To -implement this pattern smoothly in SQLAlchemy, the primary datatype of the -column should remain as ``Integer``, however the underlying implementation -type deployed to the SQL Server database can be specified as ``Numeric`` using -:meth:`.TypeEngine.with_variant`:: - - from sqlalchemy import Column - from sqlalchemy import Integer - from sqlalchemy import Numeric - from sqlalchemy import String - from sqlalchemy.ext.declarative import declarative_base - - Base = declarative_base() - - class TestTable(Base): - __tablename__ = "test" - id = Column( - Integer().with_variant(Numeric(10, 0), "mssql"), - primary_key=True, - autoincrement=True, - ) - name = Column(String) - -In the above example, ``Integer().with_variant()`` provides clear usage -information that accurately describes the intent of the code. The general -restriction that ``autoincrement`` only applies to ``Integer`` is established -at the metadata level and not at the per-dialect level. - -When using the above pattern, the primary key identifier that comes back from -the insertion of a row, which is also the value that would be assigned to an -ORM object such as ``TestTable`` above, will be an instance of ``Decimal()`` -and not ``int`` when using SQL Server. The numeric return type of the -:class:`_types.Numeric` type can be changed to return floats by passing False -to :paramref:`_types.Numeric.asdecimal`. To normalize the return type of the -above ``Numeric(10, 0)`` to return Python ints (which also support "long" -integer values in Python 3), use :class:`_types.TypeDecorator` as follows:: - - from sqlalchemy import TypeDecorator - - class NumericAsInteger(TypeDecorator): - '''normalize floating point return values into ints''' - - impl = Numeric(10, 0, asdecimal=False) - cache_ok = True - - def process_result_value(self, value, dialect): - if value is not None: - value = int(value) - return value - - class TestTable(Base): - __tablename__ = "test" - id = Column( - Integer().with_variant(NumericAsInteger, "mssql"), - primary_key=True, - autoincrement=True, - ) - name = Column(String) - -.. _mssql_insert_behavior: - -INSERT behavior -^^^^^^^^^^^^^^^^ - -Handling of the ``IDENTITY`` column at INSERT time involves two key -techniques. The most common is being able to fetch the "last inserted value" -for a given ``IDENTITY`` column, a process which SQLAlchemy performs -implicitly in many cases, most importantly within the ORM. - -The process for fetching this value has several variants: - -* In the vast majority of cases, RETURNING is used in conjunction with INSERT - statements on SQL Server in order to get newly generated primary key values: - - .. sourcecode:: sql - - INSERT INTO t (x) OUTPUT inserted.id VALUES (?) - - As of SQLAlchemy 2.0, the :ref:`engine_insertmanyvalues` feature is also - used by default to optimize many-row INSERT statements; for SQL Server - the feature takes place for both RETURNING and-non RETURNING - INSERT statements. - - .. versionchanged:: 2.0.10 The :ref:`engine_insertmanyvalues` feature for - SQL Server was temporarily disabled for SQLAlchemy version 2.0.9 due to - issues with row ordering. As of 2.0.10 the feature is re-enabled, with - special case handling for the unit of work's requirement for RETURNING to - be ordered. - -* When RETURNING is not available or has been disabled via - ``implicit_returning=False``, either the ``scope_identity()`` function or - the ``@@identity`` variable is used; behavior varies by backend: - - * when using PyODBC, the phrase ``; select scope_identity()`` will be - appended to the end of the INSERT statement; a second result set will be - fetched in order to receive the value. Given a table as:: - - t = Table( - 't', - metadata, - Column('id', Integer, primary_key=True), - Column('x', Integer), - implicit_returning=False - ) - - an INSERT will look like: - - .. sourcecode:: sql - - INSERT INTO t (x) VALUES (?); select scope_identity() - - * Other dialects such as pymssql will call upon - ``SELECT scope_identity() AS lastrowid`` subsequent to an INSERT - statement. If the flag ``use_scope_identity=False`` is passed to - :func:`_sa.create_engine`, - the statement ``SELECT @@identity AS lastrowid`` - is used instead. - -A table that contains an ``IDENTITY`` column will prohibit an INSERT statement -that refers to the identity column explicitly. The SQLAlchemy dialect will -detect when an INSERT construct, created using a core -:func:`_expression.insert` -construct (not a plain string SQL), refers to the identity column, and -in this case will emit ``SET IDENTITY_INSERT ON`` prior to the insert -statement proceeding, and ``SET IDENTITY_INSERT OFF`` subsequent to the -execution. Given this example:: - - m = MetaData() - t = Table('t', m, Column('id', Integer, primary_key=True), - Column('x', Integer)) - m.create_all(engine) - - with engine.begin() as conn: - conn.execute(t.insert(), {'id': 1, 'x':1}, {'id':2, 'x':2}) - -The above column will be created with IDENTITY, however the INSERT statement -we emit is specifying explicit values. In the echo output we can see -how SQLAlchemy handles this: - -.. sourcecode:: sql - - CREATE TABLE t ( - id INTEGER NOT NULL IDENTITY(1,1), - x INTEGER NULL, - PRIMARY KEY (id) - ) - - COMMIT - SET IDENTITY_INSERT t ON - INSERT INTO t (id, x) VALUES (?, ?) - ((1, 1), (2, 2)) - SET IDENTITY_INSERT t OFF - COMMIT - - - -This is an auxiliary use case suitable for testing and bulk insert scenarios. - -SEQUENCE support ----------------- - -The :class:`.Sequence` object creates "real" sequences, i.e., -``CREATE SEQUENCE``: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy import Sequence - >>> from sqlalchemy.schema import CreateSequence - >>> from sqlalchemy.dialects import mssql - >>> print(CreateSequence(Sequence("my_seq", start=1)).compile(dialect=mssql.dialect())) - {printsql}CREATE SEQUENCE my_seq START WITH 1 - -For integer primary key generation, SQL Server's ``IDENTITY`` construct should -generally be preferred vs. sequence. - -.. tip:: - - The default start value for T-SQL is ``-2**63`` instead of 1 as - in most other SQL databases. Users should explicitly set the - :paramref:`.Sequence.start` to 1 if that's the expected default:: - - seq = Sequence("my_sequence", start=1) - -.. versionadded:: 1.4 added SQL Server support for :class:`.Sequence` - -.. versionchanged:: 2.0 The SQL Server dialect will no longer implicitly - render "START WITH 1" for ``CREATE SEQUENCE``, which was the behavior - first implemented in version 1.4. - -MAX on VARCHAR / NVARCHAR -------------------------- - -SQL Server supports the special string "MAX" within the -:class:`_types.VARCHAR` and :class:`_types.NVARCHAR` datatypes, -to indicate "maximum length possible". The dialect currently handles this as -a length of "None" in the base type, rather than supplying a -dialect-specific version of these types, so that a base type -specified such as ``VARCHAR(None)`` can assume "unlengthed" behavior on -more than one backend without using dialect-specific types. - -To build a SQL Server VARCHAR or NVARCHAR with MAX length, use None:: - - my_table = Table( - 'my_table', metadata, - Column('my_data', VARCHAR(None)), - Column('my_n_data', NVARCHAR(None)) - ) - - -Collation Support ------------------ - -Character collations are supported by the base string types, -specified by the string argument "collation":: - - from sqlalchemy import VARCHAR - Column('login', VARCHAR(32, collation='Latin1_General_CI_AS')) - -When such a column is associated with a :class:`_schema.Table`, the -CREATE TABLE statement for this column will yield:: - - login VARCHAR(32) COLLATE Latin1_General_CI_AS NULL - -LIMIT/OFFSET Support --------------------- - -MSSQL has added support for LIMIT / OFFSET as of SQL Server 2012, via the -"OFFSET n ROWS" and "FETCH NEXT n ROWS" clauses. SQLAlchemy supports these -syntaxes automatically if SQL Server 2012 or greater is detected. - -.. versionchanged:: 1.4 support added for SQL Server "OFFSET n ROWS" and - "FETCH NEXT n ROWS" syntax. - -For statements that specify only LIMIT and no OFFSET, all versions of SQL -Server support the TOP keyword. This syntax is used for all SQL Server -versions when no OFFSET clause is present. A statement such as:: - - select(some_table).limit(5) - -will render similarly to:: - - SELECT TOP 5 col1, col2.. FROM table - -For versions of SQL Server prior to SQL Server 2012, a statement that uses -LIMIT and OFFSET, or just OFFSET alone, will be rendered using the -``ROW_NUMBER()`` window function. A statement such as:: - - select(some_table).order_by(some_table.c.col3).limit(5).offset(10) - -will render similarly to:: - - SELECT anon_1.col1, anon_1.col2 FROM (SELECT col1, col2, - ROW_NUMBER() OVER (ORDER BY col3) AS - mssql_rn FROM table WHERE t.x = :x_1) AS - anon_1 WHERE mssql_rn > :param_1 AND mssql_rn <= :param_2 + :param_1 - -Note that when using LIMIT and/or OFFSET, whether using the older -or newer SQL Server syntaxes, the statement must have an ORDER BY as well, -else a :class:`.CompileError` is raised. - -.. _mssql_comment_support: - -DDL Comment Support --------------------- - -Comment support, which includes DDL rendering for attributes such as -:paramref:`_schema.Table.comment` and :paramref:`_schema.Column.comment`, as -well as the ability to reflect these comments, is supported assuming a -supported version of SQL Server is in use. If a non-supported version such as -Azure Synapse is detected at first-connect time (based on the presence -of the ``fn_listextendedproperty`` SQL function), comment support including -rendering and table-comment reflection is disabled, as both features rely upon -SQL Server stored procedures and functions that are not available on all -backend types. - -To force comment support to be on or off, bypassing autodetection, set the -parameter ``supports_comments`` within :func:`_sa.create_engine`:: - - e = create_engine("mssql+pyodbc://u:p@dsn", supports_comments=False) - -.. versionadded:: 2.0 Added support for table and column comments for - the SQL Server dialect, including DDL generation and reflection. - -.. _mssql_isolation_level: - -Transaction Isolation Level ---------------------------- - -All SQL Server dialects support setting of transaction isolation level -both via a dialect-specific parameter -:paramref:`_sa.create_engine.isolation_level` -accepted by :func:`_sa.create_engine`, -as well as the :paramref:`.Connection.execution_options.isolation_level` -argument as passed to -:meth:`_engine.Connection.execution_options`. -This feature works by issuing the -command ``SET TRANSACTION ISOLATION LEVEL `` for -each new connection. - -To set isolation level using :func:`_sa.create_engine`:: - - engine = create_engine( - "mssql+pyodbc://scott:tiger@ms_2008", - isolation_level="REPEATABLE READ" - ) - -To set using per-connection execution options:: - - connection = engine.connect() - connection = connection.execution_options( - isolation_level="READ COMMITTED" - ) - -Valid values for ``isolation_level`` include: - -* ``AUTOCOMMIT`` - pyodbc / pymssql-specific -* ``READ COMMITTED`` -* ``READ UNCOMMITTED`` -* ``REPEATABLE READ`` -* ``SERIALIZABLE`` -* ``SNAPSHOT`` - specific to SQL Server - -There are also more options for isolation level configurations, such as -"sub-engine" objects linked to a main :class:`_engine.Engine` which each apply -different isolation level settings. See the discussion at -:ref:`dbapi_autocommit` for background. - -.. seealso:: - - :ref:`dbapi_autocommit` - -.. _mssql_reset_on_return: - -Temporary Table / Resource Reset for Connection Pooling -------------------------------------------------------- - -The :class:`.QueuePool` connection pool implementation used -by the SQLAlchemy :class:`.Engine` object includes -:ref:`reset on return ` behavior that will invoke -the DBAPI ``.rollback()`` method when connections are returned to the pool. -While this rollback will clear out the immediate state used by the previous -transaction, it does not cover a wider range of session-level state, including -temporary tables as well as other server state such as prepared statement -handles and statement caches. An undocumented SQL Server procedure known -as ``sp_reset_connection`` is known to be a workaround for this issue which -will reset most of the session state that builds up on a connection, including -temporary tables. - -To install ``sp_reset_connection`` as the means of performing reset-on-return, -the :meth:`.PoolEvents.reset` event hook may be used, as demonstrated in the -example below. The :paramref:`_sa.create_engine.pool_reset_on_return` parameter -is set to ``None`` so that the custom scheme can replace the default behavior -completely. The custom hook implementation calls ``.rollback()`` in any case, -as it's usually important that the DBAPI's own tracking of commit/rollback -will remain consistent with the state of the transaction:: - - from sqlalchemy import create_engine - from sqlalchemy import event - - mssql_engine = create_engine( - "mssql+pyodbc://scott:tiger^5HHH@mssql2017:1433/test?driver=ODBC+Driver+17+for+SQL+Server", - - # disable default reset-on-return scheme - pool_reset_on_return=None, - ) - - - @event.listens_for(mssql_engine, "reset") - def _reset_mssql(dbapi_connection, connection_record, reset_state): - if not reset_state.terminate_only: - dbapi_connection.execute("{call sys.sp_reset_connection}") - - # so that the DBAPI itself knows that the connection has been - # reset - dbapi_connection.rollback() - -.. versionchanged:: 2.0.0b3 Added additional state arguments to - the :meth:`.PoolEvents.reset` event and additionally ensured the event - is invoked for all "reset" occurrences, so that it's appropriate - as a place for custom "reset" handlers. Previous schemes which - use the :meth:`.PoolEvents.checkin` handler remain usable as well. - -.. seealso:: - - :ref:`pool_reset_on_return` - in the :ref:`pooling_toplevel` documentation - -Nullability ------------ -MSSQL has support for three levels of column nullability. The default -nullability allows nulls and is explicit in the CREATE TABLE -construct:: - - name VARCHAR(20) NULL - -If ``nullable=None`` is specified then no specification is made. In -other words the database's configured default is used. This will -render:: - - name VARCHAR(20) - -If ``nullable`` is ``True`` or ``False`` then the column will be -``NULL`` or ``NOT NULL`` respectively. - -Date / Time Handling --------------------- -DATE and TIME are supported. Bind parameters are converted -to datetime.datetime() objects as required by most MSSQL drivers, -and results are processed from strings if needed. -The DATE and TIME types are not available for MSSQL 2005 and -previous - if a server version below 2008 is detected, DDL -for these types will be issued as DATETIME. - -.. _mssql_large_type_deprecation: - -Large Text/Binary Type Deprecation ----------------------------------- - -Per -`SQL Server 2012/2014 Documentation `_, -the ``NTEXT``, ``TEXT`` and ``IMAGE`` datatypes are to be removed from SQL -Server in a future release. SQLAlchemy normally relates these types to the -:class:`.UnicodeText`, :class:`_expression.TextClause` and -:class:`.LargeBinary` datatypes. - -In order to accommodate this change, a new flag ``deprecate_large_types`` -is added to the dialect, which will be automatically set based on detection -of the server version in use, if not otherwise set by the user. The -behavior of this flag is as follows: - -* When this flag is ``True``, the :class:`.UnicodeText`, - :class:`_expression.TextClause` and - :class:`.LargeBinary` datatypes, when used to render DDL, will render the - types ``NVARCHAR(max)``, ``VARCHAR(max)``, and ``VARBINARY(max)``, - respectively. This is a new behavior as of the addition of this flag. - -* When this flag is ``False``, the :class:`.UnicodeText`, - :class:`_expression.TextClause` and - :class:`.LargeBinary` datatypes, when used to render DDL, will render the - types ``NTEXT``, ``TEXT``, and ``IMAGE``, - respectively. This is the long-standing behavior of these types. - -* The flag begins with the value ``None``, before a database connection is - established. If the dialect is used to render DDL without the flag being - set, it is interpreted the same as ``False``. - -* On first connection, the dialect detects if SQL Server version 2012 or - greater is in use; if the flag is still at ``None``, it sets it to ``True`` - or ``False`` based on whether 2012 or greater is detected. - -* The flag can be set to either ``True`` or ``False`` when the dialect - is created, typically via :func:`_sa.create_engine`:: - - eng = create_engine("mssql+pymssql://user:pass@host/db", - deprecate_large_types=True) - -* Complete control over whether the "old" or "new" types are rendered is - available in all SQLAlchemy versions by using the UPPERCASE type objects - instead: :class:`_types.NVARCHAR`, :class:`_types.VARCHAR`, - :class:`_types.VARBINARY`, :class:`_types.TEXT`, :class:`_mssql.NTEXT`, - :class:`_mssql.IMAGE` - will always remain fixed and always output exactly that - type. - -.. _multipart_schema_names: - -Multipart Schema Names ----------------------- - -SQL Server schemas sometimes require multiple parts to their "schema" -qualifier, that is, including the database name and owner name as separate -tokens, such as ``mydatabase.dbo.some_table``. These multipart names can be set -at once using the :paramref:`_schema.Table.schema` argument of -:class:`_schema.Table`:: - - Table( - "some_table", metadata, - Column("q", String(50)), - schema="mydatabase.dbo" - ) - -When performing operations such as table or component reflection, a schema -argument that contains a dot will be split into separate -"database" and "owner" components in order to correctly query the SQL -Server information schema tables, as these two values are stored separately. -Additionally, when rendering the schema name for DDL or SQL, the two -components will be quoted separately for case sensitive names and other -special characters. Given an argument as below:: - - Table( - "some_table", metadata, - Column("q", String(50)), - schema="MyDataBase.dbo" - ) - -The above schema would be rendered as ``[MyDataBase].dbo``, and also in -reflection, would be reflected using "dbo" as the owner and "MyDataBase" -as the database name. - -To control how the schema name is broken into database / owner, -specify brackets (which in SQL Server are quoting characters) in the name. -Below, the "owner" will be considered as ``MyDataBase.dbo`` and the -"database" will be None:: - - Table( - "some_table", metadata, - Column("q", String(50)), - schema="[MyDataBase.dbo]" - ) - -To individually specify both database and owner name with special characters -or embedded dots, use two sets of brackets:: - - Table( - "some_table", metadata, - Column("q", String(50)), - schema="[MyDataBase.Period].[MyOwner.Dot]" - ) - - -.. versionchanged:: 1.2 the SQL Server dialect now treats brackets as - identifier delimiters splitting the schema into separate database - and owner tokens, to allow dots within either name itself. - -.. _legacy_schema_rendering: - -Legacy Schema Mode ------------------- - -Very old versions of the MSSQL dialect introduced the behavior such that a -schema-qualified table would be auto-aliased when used in a -SELECT statement; given a table:: - - account_table = Table( - 'account', metadata, - Column('id', Integer, primary_key=True), - Column('info', String(100)), - schema="customer_schema" - ) - -this legacy mode of rendering would assume that "customer_schema.account" -would not be accepted by all parts of the SQL statement, as illustrated -below: - -.. sourcecode:: pycon+sql - - >>> eng = create_engine("mssql+pymssql://mydsn", legacy_schema_aliasing=True) - >>> print(account_table.select().compile(eng)) - {printsql}SELECT account_1.id, account_1.info - FROM customer_schema.account AS account_1 - -This mode of behavior is now off by default, as it appears to have served -no purpose; however in the case that legacy applications rely upon it, -it is available using the ``legacy_schema_aliasing`` argument to -:func:`_sa.create_engine` as illustrated above. - -.. deprecated:: 1.4 - - The ``legacy_schema_aliasing`` flag is now - deprecated and will be removed in a future release. - -.. _mssql_indexes: - -Clustered Index Support ------------------------ - -The MSSQL dialect supports clustered indexes (and primary keys) via the -``mssql_clustered`` option. This option is available to :class:`.Index`, -:class:`.UniqueConstraint`. and :class:`.PrimaryKeyConstraint`. -For indexes this option can be combined with the ``mssql_columnstore`` one -to create a clustered columnstore index. - -To generate a clustered index:: - - Index("my_index", table.c.x, mssql_clustered=True) - -which renders the index as ``CREATE CLUSTERED INDEX my_index ON table (x)``. - -To generate a clustered primary key use:: - - Table('my_table', metadata, - Column('x', ...), - Column('y', ...), - PrimaryKeyConstraint("x", "y", mssql_clustered=True)) - -which will render the table, for example, as:: - - CREATE TABLE my_table (x INTEGER NOT NULL, y INTEGER NOT NULL, - PRIMARY KEY CLUSTERED (x, y)) - -Similarly, we can generate a clustered unique constraint using:: - - Table('my_table', metadata, - Column('x', ...), - Column('y', ...), - PrimaryKeyConstraint("x"), - UniqueConstraint("y", mssql_clustered=True), - ) - -To explicitly request a non-clustered primary key (for example, when -a separate clustered index is desired), use:: - - Table('my_table', metadata, - Column('x', ...), - Column('y', ...), - PrimaryKeyConstraint("x", "y", mssql_clustered=False)) - -which will render the table, for example, as:: - - CREATE TABLE my_table (x INTEGER NOT NULL, y INTEGER NOT NULL, - PRIMARY KEY NONCLUSTERED (x, y)) - -Columnstore Index Support -------------------------- - -The MSSQL dialect supports columnstore indexes via the ``mssql_columnstore`` -option. This option is available to :class:`.Index`. It be combined with -the ``mssql_clustered`` option to create a clustered columnstore index. - -To generate a columnstore index:: - - Index("my_index", table.c.x, mssql_columnstore=True) - -which renders the index as ``CREATE COLUMNSTORE INDEX my_index ON table (x)``. - -To generate a clustered columnstore index provide no columns:: - - idx = Index("my_index", mssql_clustered=True, mssql_columnstore=True) - # required to associate the index with the table - table.append_constraint(idx) - -the above renders the index as -``CREATE CLUSTERED COLUMNSTORE INDEX my_index ON table``. - -.. versionadded:: 2.0.18 - -MSSQL-Specific Index Options ------------------------------ - -In addition to clustering, the MSSQL dialect supports other special options -for :class:`.Index`. - -INCLUDE -^^^^^^^ - -The ``mssql_include`` option renders INCLUDE(colname) for the given string -names:: - - Index("my_index", table.c.x, mssql_include=['y']) - -would render the index as ``CREATE INDEX my_index ON table (x) INCLUDE (y)`` - -.. _mssql_index_where: - -Filtered Indexes -^^^^^^^^^^^^^^^^ - -The ``mssql_where`` option renders WHERE(condition) for the given string -names:: - - Index("my_index", table.c.x, mssql_where=table.c.x > 10) - -would render the index as ``CREATE INDEX my_index ON table (x) WHERE x > 10``. - -.. versionadded:: 1.3.4 - -Index ordering -^^^^^^^^^^^^^^ - -Index ordering is available via functional expressions, such as:: - - Index("my_index", table.c.x.desc()) - -would render the index as ``CREATE INDEX my_index ON table (x DESC)`` - -.. seealso:: - - :ref:`schema_indexes_functional` - -Compatibility Levels --------------------- -MSSQL supports the notion of setting compatibility levels at the -database level. This allows, for instance, to run a database that -is compatible with SQL2000 while running on a SQL2005 database -server. ``server_version_info`` will always return the database -server version information (in this case SQL2005) and not the -compatibility level information. Because of this, if running under -a backwards compatibility mode SQLAlchemy may attempt to use T-SQL -statements that are unable to be parsed by the database server. - -.. _mssql_triggers: - -Triggers --------- - -SQLAlchemy by default uses OUTPUT INSERTED to get at newly -generated primary key values via IDENTITY columns or other -server side defaults. MS-SQL does not -allow the usage of OUTPUT INSERTED on tables that have triggers. -To disable the usage of OUTPUT INSERTED on a per-table basis, -specify ``implicit_returning=False`` for each :class:`_schema.Table` -which has triggers:: - - Table('mytable', metadata, - Column('id', Integer, primary_key=True), - # ..., - implicit_returning=False - ) - -Declarative form:: - - class MyClass(Base): - # ... - __table_args__ = {'implicit_returning':False} - - -.. _mssql_rowcount_versioning: - -Rowcount Support / ORM Versioning ---------------------------------- - -The SQL Server drivers may have limited ability to return the number -of rows updated from an UPDATE or DELETE statement. - -As of this writing, the PyODBC driver is not able to return a rowcount when -OUTPUT INSERTED is used. Previous versions of SQLAlchemy therefore had -limitations for features such as the "ORM Versioning" feature that relies upon -accurate rowcounts in order to match version numbers with matched rows. - -SQLAlchemy 2.0 now retrieves the "rowcount" manually for these particular use -cases based on counting the rows that arrived back within RETURNING; so while -the driver still has this limitation, the ORM Versioning feature is no longer -impacted by it. As of SQLAlchemy 2.0.5, ORM versioning has been fully -re-enabled for the pyodbc driver. - -.. versionchanged:: 2.0.5 ORM versioning support is restored for the pyodbc - driver. Previously, a warning would be emitted during ORM flush that - versioning was not supported. - - -Enabling Snapshot Isolation ---------------------------- - -SQL Server has a default transaction -isolation mode that locks entire tables, and causes even mildly concurrent -applications to have long held locks and frequent deadlocks. -Enabling snapshot isolation for the database as a whole is recommended -for modern levels of concurrency support. This is accomplished via the -following ALTER DATABASE commands executed at the SQL prompt:: - - ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON - - ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON - -Background on SQL Server snapshot isolation is available at -https://msdn.microsoft.com/en-us/library/ms175095.aspx. - -""" # noqa - -from __future__ import annotations - -import codecs -import datetime -import operator -import re -from typing import overload -from typing import TYPE_CHECKING -from uuid import UUID as _python_UUID - -from . import information_schema as ischema -from .json import JSON -from .json import JSONIndexType -from .json import JSONPathType -from ... import exc -from ... import Identity -from ... import schema as sa_schema -from ... import Sequence -from ... import sql -from ... import text -from ... import util -from ...engine import cursor as _cursor -from ...engine import default -from ...engine import reflection -from ...engine.reflection import ReflectionDefaults -from ...sql import coercions -from ...sql import compiler -from ...sql import elements -from ...sql import expression -from ...sql import func -from ...sql import quoted_name -from ...sql import roles -from ...sql import sqltypes -from ...sql import try_cast as try_cast # noqa: F401 -from ...sql import util as sql_util -from ...sql._typing import is_sql_compiler -from ...sql.compiler import InsertmanyvaluesSentinelOpts -from ...sql.elements import TryCast as TryCast # noqa: F401 -from ...types import BIGINT -from ...types import BINARY -from ...types import CHAR -from ...types import DATE -from ...types import DATETIME -from ...types import DECIMAL -from ...types import FLOAT -from ...types import INTEGER -from ...types import NCHAR -from ...types import NUMERIC -from ...types import NVARCHAR -from ...types import SMALLINT -from ...types import TEXT -from ...types import VARCHAR -from ...util import update_wrapper -from ...util.typing import Literal - -if TYPE_CHECKING: - from ...sql.dml import DMLState - from ...sql.selectable import TableClause - -# https://sqlserverbuilds.blogspot.com/ -MS_2017_VERSION = (14,) -MS_2016_VERSION = (13,) -MS_2014_VERSION = (12,) -MS_2012_VERSION = (11,) -MS_2008_VERSION = (10,) -MS_2005_VERSION = (9,) -MS_2000_VERSION = (8,) - -RESERVED_WORDS = { - "add", - "all", - "alter", - "and", - "any", - "as", - "asc", - "authorization", - "backup", - "begin", - "between", - "break", - "browse", - "bulk", - "by", - "cascade", - "case", - "check", - "checkpoint", - "close", - "clustered", - "coalesce", - "collate", - "column", - "commit", - "compute", - "constraint", - "contains", - "containstable", - "continue", - "convert", - "create", - "cross", - "current", - "current_date", - "current_time", - "current_timestamp", - "current_user", - "cursor", - "database", - "dbcc", - "deallocate", - "declare", - "default", - "delete", - "deny", - "desc", - "disk", - "distinct", - "distributed", - "double", - "drop", - "dump", - "else", - "end", - "errlvl", - "escape", - "except", - "exec", - "execute", - "exists", - "exit", - "external", - "fetch", - "file", - "fillfactor", - "for", - "foreign", - "freetext", - "freetexttable", - "from", - "full", - "function", - "goto", - "grant", - "group", - "having", - "holdlock", - "identity", - "identity_insert", - "identitycol", - "if", - "in", - "index", - "inner", - "insert", - "intersect", - "into", - "is", - "join", - "key", - "kill", - "left", - "like", - "lineno", - "load", - "merge", - "national", - "nocheck", - "nonclustered", - "not", - "null", - "nullif", - "of", - "off", - "offsets", - "on", - "open", - "opendatasource", - "openquery", - "openrowset", - "openxml", - "option", - "or", - "order", - "outer", - "over", - "percent", - "pivot", - "plan", - "precision", - "primary", - "print", - "proc", - "procedure", - "public", - "raiserror", - "read", - "readtext", - "reconfigure", - "references", - "replication", - "restore", - "restrict", - "return", - "revert", - "revoke", - "right", - "rollback", - "rowcount", - "rowguidcol", - "rule", - "save", - "schema", - "securityaudit", - "select", - "session_user", - "set", - "setuser", - "shutdown", - "some", - "statistics", - "system_user", - "table", - "tablesample", - "textsize", - "then", - "to", - "top", - "tran", - "transaction", - "trigger", - "truncate", - "tsequal", - "union", - "unique", - "unpivot", - "update", - "updatetext", - "use", - "user", - "values", - "varying", - "view", - "waitfor", - "when", - "where", - "while", - "with", - "writetext", -} - - -class REAL(sqltypes.REAL): - """the SQL Server REAL datatype.""" - - def __init__(self, **kw): - # REAL is a synonym for FLOAT(24) on SQL server. - # it is only accepted as the word "REAL" in DDL, the numeric - # precision value is not allowed to be present - kw.setdefault("precision", 24) - super().__init__(**kw) - - -class DOUBLE_PRECISION(sqltypes.DOUBLE_PRECISION): - """the SQL Server DOUBLE PRECISION datatype. - - .. versionadded:: 2.0.11 - - """ - - def __init__(self, **kw): - # DOUBLE PRECISION is a synonym for FLOAT(53) on SQL server. - # it is only accepted as the word "DOUBLE PRECISION" in DDL, - # the numeric precision value is not allowed to be present - kw.setdefault("precision", 53) - super().__init__(**kw) - - -class TINYINT(sqltypes.Integer): - __visit_name__ = "TINYINT" - - -# MSSQL DATE/TIME types have varied behavior, sometimes returning -# strings. MSDate/TIME check for everything, and always -# filter bind parameters into datetime objects (required by pyodbc, -# not sure about other dialects). - - -class _MSDate(sqltypes.Date): - def bind_processor(self, dialect): - def process(value): - if type(value) == datetime.date: - return datetime.datetime(value.year, value.month, value.day) - else: - return value - - return process - - _reg = re.compile(r"(\d+)-(\d+)-(\d+)") - - def result_processor(self, dialect, coltype): - def process(value): - if isinstance(value, datetime.datetime): - return value.date() - elif isinstance(value, str): - m = self._reg.match(value) - if not m: - raise ValueError( - "could not parse %r as a date value" % (value,) - ) - return datetime.date(*[int(x or 0) for x in m.groups()]) - else: - return value - - return process - - -class TIME(sqltypes.TIME): - def __init__(self, precision=None, **kwargs): - self.precision = precision - super().__init__() - - __zero_date = datetime.date(1900, 1, 1) - - def bind_processor(self, dialect): - def process(value): - if isinstance(value, datetime.datetime): - value = datetime.datetime.combine( - self.__zero_date, value.time() - ) - elif isinstance(value, datetime.time): - """issue #5339 - per: https://github.com/mkleehammer/pyodbc/wiki/Tips-and-Tricks-by-Database-Platform#time-columns - pass TIME value as string - """ # noqa - value = str(value) - return value - - return process - - _reg = re.compile(r"(\d+):(\d+):(\d+)(?:\.(\d{0,6}))?") - - def result_processor(self, dialect, coltype): - def process(value): - if isinstance(value, datetime.datetime): - return value.time() - elif isinstance(value, str): - m = self._reg.match(value) - if not m: - raise ValueError( - "could not parse %r as a time value" % (value,) - ) - return datetime.time(*[int(x or 0) for x in m.groups()]) - else: - return value - - return process - - -_MSTime = TIME - - -class _BASETIMEIMPL(TIME): - __visit_name__ = "_BASETIMEIMPL" - - -class _DateTimeBase: - def bind_processor(self, dialect): - def process(value): - if type(value) == datetime.date: - return datetime.datetime(value.year, value.month, value.day) - else: - return value - - return process - - -class _MSDateTime(_DateTimeBase, sqltypes.DateTime): - pass - - -class SMALLDATETIME(_DateTimeBase, sqltypes.DateTime): - __visit_name__ = "SMALLDATETIME" - - -class DATETIME2(_DateTimeBase, sqltypes.DateTime): - __visit_name__ = "DATETIME2" - - def __init__(self, precision=None, **kw): - super().__init__(**kw) - self.precision = precision - - -class DATETIMEOFFSET(_DateTimeBase, sqltypes.DateTime): - __visit_name__ = "DATETIMEOFFSET" - - def __init__(self, precision=None, **kw): - super().__init__(**kw) - self.precision = precision - - -class _UnicodeLiteral: - def literal_processor(self, dialect): - def process(value): - value = value.replace("'", "''") - - if dialect.identifier_preparer._double_percents: - value = value.replace("%", "%%") - - return "N'%s'" % value - - return process - - -class _MSUnicode(_UnicodeLiteral, sqltypes.Unicode): - pass - - -class _MSUnicodeText(_UnicodeLiteral, sqltypes.UnicodeText): - pass - - -class TIMESTAMP(sqltypes._Binary): - """Implement the SQL Server TIMESTAMP type. - - Note this is **completely different** than the SQL Standard - TIMESTAMP type, which is not supported by SQL Server. It - is a read-only datatype that does not support INSERT of values. - - .. versionadded:: 1.2 - - .. seealso:: - - :class:`_mssql.ROWVERSION` - - """ - - __visit_name__ = "TIMESTAMP" - - # expected by _Binary to be present - length = None - - def __init__(self, convert_int=False): - """Construct a TIMESTAMP or ROWVERSION type. - - :param convert_int: if True, binary integer values will - be converted to integers on read. - - .. versionadded:: 1.2 - - """ - self.convert_int = convert_int - - def result_processor(self, dialect, coltype): - super_ = super().result_processor(dialect, coltype) - if self.convert_int: - - def process(value): - if super_: - value = super_(value) - if value is not None: - # https://stackoverflow.com/a/30403242/34549 - value = int(codecs.encode(value, "hex"), 16) - return value - - return process - else: - return super_ - - -class ROWVERSION(TIMESTAMP): - """Implement the SQL Server ROWVERSION type. - - The ROWVERSION datatype is a SQL Server synonym for the TIMESTAMP - datatype, however current SQL Server documentation suggests using - ROWVERSION for new datatypes going forward. - - The ROWVERSION datatype does **not** reflect (e.g. introspect) from the - database as itself; the returned datatype will be - :class:`_mssql.TIMESTAMP`. - - This is a read-only datatype that does not support INSERT of values. - - .. versionadded:: 1.2 - - .. seealso:: - - :class:`_mssql.TIMESTAMP` - - """ - - __visit_name__ = "ROWVERSION" - - -class NTEXT(sqltypes.UnicodeText): - """MSSQL NTEXT type, for variable-length unicode text up to 2^30 - characters.""" - - __visit_name__ = "NTEXT" - - -class VARBINARY(sqltypes.VARBINARY, sqltypes.LargeBinary): - """The MSSQL VARBINARY type. - - This type adds additional features to the core :class:`_types.VARBINARY` - type, including "deprecate_large_types" mode where - either ``VARBINARY(max)`` or IMAGE is rendered, as well as the SQL - Server ``FILESTREAM`` option. - - .. seealso:: - - :ref:`mssql_large_type_deprecation` - - """ - - __visit_name__ = "VARBINARY" - - def __init__(self, length=None, filestream=False): - """ - Construct a VARBINARY type. - - :param length: optional, a length for the column for use in - DDL statements, for those binary types that accept a length, - such as the MySQL BLOB type. - - :param filestream=False: if True, renders the ``FILESTREAM`` keyword - in the table definition. In this case ``length`` must be ``None`` - or ``'max'``. - - .. versionadded:: 1.4.31 - - """ - - self.filestream = filestream - if self.filestream and length not in (None, "max"): - raise ValueError( - "length must be None or 'max' when setting filestream" - ) - super().__init__(length=length) - - -class IMAGE(sqltypes.LargeBinary): - __visit_name__ = "IMAGE" - - -class XML(sqltypes.Text): - """MSSQL XML type. - - This is a placeholder type for reflection purposes that does not include - any Python-side datatype support. It also does not currently support - additional arguments, such as "CONTENT", "DOCUMENT", - "xml_schema_collection". - - """ - - __visit_name__ = "XML" - - -class BIT(sqltypes.Boolean): - """MSSQL BIT type. - - Both pyodbc and pymssql return values from BIT columns as - Python so just subclass Boolean. - - """ - - __visit_name__ = "BIT" - - -class MONEY(sqltypes.TypeEngine): - __visit_name__ = "MONEY" - - -class SMALLMONEY(sqltypes.TypeEngine): - __visit_name__ = "SMALLMONEY" - - -class MSUUid(sqltypes.Uuid): - def bind_processor(self, dialect): - if self.native_uuid: - # this is currently assuming pyodbc; might not work for - # some other mssql driver - return None - else: - if self.as_uuid: - - def process(value): - if value is not None: - value = value.hex - return value - - return process - else: - - def process(value): - if value is not None: - value = value.replace("-", "").replace("''", "'") - return value - - return process - - def literal_processor(self, dialect): - if self.native_uuid: - - def process(value): - return f"""'{str(value).replace("''", "'")}'""" - - return process - else: - if self.as_uuid: - - def process(value): - return f"""'{value.hex}'""" - - return process - else: - - def process(value): - return f"""'{ - value.replace("-", "").replace("'", "''") - }'""" - - return process - - -class UNIQUEIDENTIFIER(sqltypes.Uuid[sqltypes._UUID_RETURN]): - __visit_name__ = "UNIQUEIDENTIFIER" - - @overload - def __init__( - self: UNIQUEIDENTIFIER[_python_UUID], as_uuid: Literal[True] = ... - ): ... - - @overload - def __init__( - self: UNIQUEIDENTIFIER[str], as_uuid: Literal[False] = ... - ): ... - - def __init__(self, as_uuid: bool = True): - """Construct a :class:`_mssql.UNIQUEIDENTIFIER` type. - - - :param as_uuid=True: if True, values will be interpreted - as Python uuid objects, converting to/from string via the - DBAPI. - - .. versionchanged: 2.0 Added direct "uuid" support to the - :class:`_mssql.UNIQUEIDENTIFIER` datatype; uuid interpretation - defaults to ``True``. - - """ - self.as_uuid = as_uuid - self.native_uuid = True - - -class SQL_VARIANT(sqltypes.TypeEngine): - __visit_name__ = "SQL_VARIANT" - - -# old names. -MSDateTime = _MSDateTime -MSDate = _MSDate -MSReal = REAL -MSTinyInteger = TINYINT -MSTime = TIME -MSSmallDateTime = SMALLDATETIME -MSDateTime2 = DATETIME2 -MSDateTimeOffset = DATETIMEOFFSET -MSText = TEXT -MSNText = NTEXT -MSString = VARCHAR -MSNVarchar = NVARCHAR -MSChar = CHAR -MSNChar = NCHAR -MSBinary = BINARY -MSVarBinary = VARBINARY -MSImage = IMAGE -MSBit = BIT -MSMoney = MONEY -MSSmallMoney = SMALLMONEY -MSUniqueIdentifier = UNIQUEIDENTIFIER -MSVariant = SQL_VARIANT - -ischema_names = { - "int": INTEGER, - "bigint": BIGINT, - "smallint": SMALLINT, - "tinyint": TINYINT, - "varchar": VARCHAR, - "nvarchar": NVARCHAR, - "char": CHAR, - "nchar": NCHAR, - "text": TEXT, - "ntext": NTEXT, - "decimal": DECIMAL, - "numeric": NUMERIC, - "float": FLOAT, - "datetime": DATETIME, - "datetime2": DATETIME2, - "datetimeoffset": DATETIMEOFFSET, - "date": DATE, - "time": TIME, - "smalldatetime": SMALLDATETIME, - "binary": BINARY, - "varbinary": VARBINARY, - "bit": BIT, - "real": REAL, - "double precision": DOUBLE_PRECISION, - "image": IMAGE, - "xml": XML, - "timestamp": TIMESTAMP, - "money": MONEY, - "smallmoney": SMALLMONEY, - "uniqueidentifier": UNIQUEIDENTIFIER, - "sql_variant": SQL_VARIANT, -} - - -class MSTypeCompiler(compiler.GenericTypeCompiler): - def _extend(self, spec, type_, length=None): - """Extend a string-type declaration with standard SQL - COLLATE annotations. - - """ - - if getattr(type_, "collation", None): - collation = "COLLATE %s" % type_.collation - else: - collation = None - - if not length: - length = type_.length - - if length: - spec = spec + "(%s)" % length - - return " ".join([c for c in (spec, collation) if c is not None]) - - def visit_double(self, type_, **kw): - return self.visit_DOUBLE_PRECISION(type_, **kw) - - def visit_FLOAT(self, type_, **kw): - precision = getattr(type_, "precision", None) - if precision is None: - return "FLOAT" - else: - return "FLOAT(%(precision)s)" % {"precision": precision} - - def visit_TINYINT(self, type_, **kw): - return "TINYINT" - - def visit_TIME(self, type_, **kw): - precision = getattr(type_, "precision", None) - if precision is not None: - return "TIME(%s)" % precision - else: - return "TIME" - - def visit_TIMESTAMP(self, type_, **kw): - return "TIMESTAMP" - - def visit_ROWVERSION(self, type_, **kw): - return "ROWVERSION" - - def visit_datetime(self, type_, **kw): - if type_.timezone: - return self.visit_DATETIMEOFFSET(type_, **kw) - else: - return self.visit_DATETIME(type_, **kw) - - def visit_DATETIMEOFFSET(self, type_, **kw): - precision = getattr(type_, "precision", None) - if precision is not None: - return "DATETIMEOFFSET(%s)" % type_.precision - else: - return "DATETIMEOFFSET" - - def visit_DATETIME2(self, type_, **kw): - precision = getattr(type_, "precision", None) - if precision is not None: - return "DATETIME2(%s)" % precision - else: - return "DATETIME2" - - def visit_SMALLDATETIME(self, type_, **kw): - return "SMALLDATETIME" - - def visit_unicode(self, type_, **kw): - return self.visit_NVARCHAR(type_, **kw) - - def visit_text(self, type_, **kw): - if self.dialect.deprecate_large_types: - return self.visit_VARCHAR(type_, **kw) - else: - return self.visit_TEXT(type_, **kw) - - def visit_unicode_text(self, type_, **kw): - if self.dialect.deprecate_large_types: - return self.visit_NVARCHAR(type_, **kw) - else: - return self.visit_NTEXT(type_, **kw) - - def visit_NTEXT(self, type_, **kw): - return self._extend("NTEXT", type_) - - def visit_TEXT(self, type_, **kw): - return self._extend("TEXT", type_) - - def visit_VARCHAR(self, type_, **kw): - return self._extend("VARCHAR", type_, length=type_.length or "max") - - def visit_CHAR(self, type_, **kw): - return self._extend("CHAR", type_) - - def visit_NCHAR(self, type_, **kw): - return self._extend("NCHAR", type_) - - def visit_NVARCHAR(self, type_, **kw): - return self._extend("NVARCHAR", type_, length=type_.length or "max") - - def visit_date(self, type_, **kw): - if self.dialect.server_version_info < MS_2008_VERSION: - return self.visit_DATETIME(type_, **kw) - else: - return self.visit_DATE(type_, **kw) - - def visit__BASETIMEIMPL(self, type_, **kw): - return self.visit_time(type_, **kw) - - def visit_time(self, type_, **kw): - if self.dialect.server_version_info < MS_2008_VERSION: - return self.visit_DATETIME(type_, **kw) - else: - return self.visit_TIME(type_, **kw) - - def visit_large_binary(self, type_, **kw): - if self.dialect.deprecate_large_types: - return self.visit_VARBINARY(type_, **kw) - else: - return self.visit_IMAGE(type_, **kw) - - def visit_IMAGE(self, type_, **kw): - return "IMAGE" - - def visit_XML(self, type_, **kw): - return "XML" - - def visit_VARBINARY(self, type_, **kw): - text = self._extend("VARBINARY", type_, length=type_.length or "max") - if getattr(type_, "filestream", False): - text += " FILESTREAM" - return text - - def visit_boolean(self, type_, **kw): - return self.visit_BIT(type_) - - def visit_BIT(self, type_, **kw): - return "BIT" - - def visit_JSON(self, type_, **kw): - # this is a bit of a break with SQLAlchemy's convention of - # "UPPERCASE name goes to UPPERCASE type name with no modification" - return self._extend("NVARCHAR", type_, length="max") - - def visit_MONEY(self, type_, **kw): - return "MONEY" - - def visit_SMALLMONEY(self, type_, **kw): - return "SMALLMONEY" - - def visit_uuid(self, type_, **kw): - if type_.native_uuid: - return self.visit_UNIQUEIDENTIFIER(type_, **kw) - else: - return super().visit_uuid(type_, **kw) - - def visit_UNIQUEIDENTIFIER(self, type_, **kw): - return "UNIQUEIDENTIFIER" - - def visit_SQL_VARIANT(self, type_, **kw): - return "SQL_VARIANT" - - -class MSExecutionContext(default.DefaultExecutionContext): - _enable_identity_insert = False - _select_lastrowid = False - _lastrowid = None - - dialect: MSDialect - - def _opt_encode(self, statement): - if self.compiled and self.compiled.schema_translate_map: - rst = self.compiled.preparer._render_schema_translates - statement = rst(statement, self.compiled.schema_translate_map) - - return statement - - def pre_exec(self): - """Activate IDENTITY_INSERT if needed.""" - - if self.isinsert: - if TYPE_CHECKING: - assert is_sql_compiler(self.compiled) - assert isinstance(self.compiled.compile_state, DMLState) - assert isinstance( - self.compiled.compile_state.dml_table, TableClause - ) - - tbl = self.compiled.compile_state.dml_table - id_column = tbl._autoincrement_column - - if id_column is not None and ( - not isinstance(id_column.default, Sequence) - ): - insert_has_identity = True - compile_state = self.compiled.dml_compile_state - self._enable_identity_insert = ( - id_column.key in self.compiled_parameters[0] - ) or ( - compile_state._dict_parameters - and (id_column.key in compile_state._insert_col_keys) - ) - - else: - insert_has_identity = False - self._enable_identity_insert = False - - self._select_lastrowid = ( - not self.compiled.inline - and insert_has_identity - and not self.compiled.effective_returning - and not self._enable_identity_insert - and not self.executemany - ) - - if self._enable_identity_insert: - self.root_connection._cursor_execute( - self.cursor, - self._opt_encode( - "SET IDENTITY_INSERT %s ON" - % self.identifier_preparer.format_table(tbl) - ), - (), - self, - ) - - def post_exec(self): - """Disable IDENTITY_INSERT if enabled.""" - - conn = self.root_connection - - if self.isinsert or self.isupdate or self.isdelete: - self._rowcount = self.cursor.rowcount - - if self._select_lastrowid: - if self.dialect.use_scope_identity: - conn._cursor_execute( - self.cursor, - "SELECT scope_identity() AS lastrowid", - (), - self, - ) - else: - conn._cursor_execute( - self.cursor, "SELECT @@identity AS lastrowid", (), self - ) - # fetchall() ensures the cursor is consumed without closing it - row = self.cursor.fetchall()[0] - self._lastrowid = int(row[0]) - - self.cursor_fetch_strategy = _cursor._NO_CURSOR_DML - elif ( - self.compiled is not None - and is_sql_compiler(self.compiled) - and self.compiled.effective_returning - ): - self.cursor_fetch_strategy = ( - _cursor.FullyBufferedCursorFetchStrategy( - self.cursor, - self.cursor.description, - self.cursor.fetchall(), - ) - ) - - if self._enable_identity_insert: - if TYPE_CHECKING: - assert is_sql_compiler(self.compiled) - assert isinstance(self.compiled.compile_state, DMLState) - assert isinstance( - self.compiled.compile_state.dml_table, TableClause - ) - conn._cursor_execute( - self.cursor, - self._opt_encode( - "SET IDENTITY_INSERT %s OFF" - % self.identifier_preparer.format_table( - self.compiled.compile_state.dml_table - ) - ), - (), - self, - ) - - def get_lastrowid(self): - return self._lastrowid - - def handle_dbapi_exception(self, e): - if self._enable_identity_insert: - try: - self.cursor.execute( - self._opt_encode( - "SET IDENTITY_INSERT %s OFF" - % self.identifier_preparer.format_table( - self.compiled.compile_state.dml_table - ) - ) - ) - except Exception: - pass - - def fire_sequence(self, seq, type_): - return self._execute_scalar( - ( - "SELECT NEXT VALUE FOR %s" - % self.identifier_preparer.format_sequence(seq) - ), - type_, - ) - - def get_insert_default(self, column): - if ( - isinstance(column, sa_schema.Column) - and column is column.table._autoincrement_column - and isinstance(column.default, sa_schema.Sequence) - and column.default.optional - ): - return None - return super().get_insert_default(column) - - -class MSSQLCompiler(compiler.SQLCompiler): - returning_precedes_values = True - - extract_map = util.update_copy( - compiler.SQLCompiler.extract_map, - { - "doy": "dayofyear", - "dow": "weekday", - "milliseconds": "millisecond", - "microseconds": "microsecond", - }, - ) - - def __init__(self, *args, **kwargs): - self.tablealiases = {} - super().__init__(*args, **kwargs) - - def _with_legacy_schema_aliasing(fn): - def decorate(self, *arg, **kw): - if self.dialect.legacy_schema_aliasing: - return fn(self, *arg, **kw) - else: - super_ = getattr(super(MSSQLCompiler, self), fn.__name__) - return super_(*arg, **kw) - - return decorate - - def visit_now_func(self, fn, **kw): - return "CURRENT_TIMESTAMP" - - def visit_current_date_func(self, fn, **kw): - return "GETDATE()" - - def visit_length_func(self, fn, **kw): - return "LEN%s" % self.function_argspec(fn, **kw) - - def visit_char_length_func(self, fn, **kw): - return "LEN%s" % self.function_argspec(fn, **kw) - - def visit_aggregate_strings_func(self, fn, **kw): - expr = fn.clauses.clauses[0]._compiler_dispatch(self, **kw) - kw["literal_execute"] = True - delimeter = fn.clauses.clauses[1]._compiler_dispatch(self, **kw) - return f"string_agg({expr}, {delimeter})" - - def visit_concat_op_expression_clauselist( - self, clauselist, operator, **kw - ): - return " + ".join(self.process(elem, **kw) for elem in clauselist) - - def visit_concat_op_binary(self, binary, operator, **kw): - return "%s + %s" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_true(self, expr, **kw): - return "1" - - def visit_false(self, expr, **kw): - return "0" - - def visit_match_op_binary(self, binary, operator, **kw): - return "CONTAINS (%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def get_select_precolumns(self, select, **kw): - """MS-SQL puts TOP, it's version of LIMIT here""" - - s = super().get_select_precolumns(select, **kw) - - if select._has_row_limiting_clause and self._use_top(select): - # ODBC drivers and possibly others - # don't support bind params in the SELECT clause on SQL Server. - # so have to use literal here. - kw["literal_execute"] = True - s += "TOP %s " % self.process( - self._get_limit_or_fetch(select), **kw - ) - if select._fetch_clause is not None: - if select._fetch_clause_options["percent"]: - s += "PERCENT " - if select._fetch_clause_options["with_ties"]: - s += "WITH TIES " - - return s - - def get_from_hint_text(self, table, text): - return text - - def get_crud_hint_text(self, table, text): - return text - - def _get_limit_or_fetch(self, select): - if select._fetch_clause is None: - return select._limit_clause - else: - return select._fetch_clause - - def _use_top(self, select): - return (select._offset_clause is None) and ( - select._simple_int_clause(select._limit_clause) - or ( - # limit can use TOP with is by itself. fetch only uses TOP - # when it needs to because of PERCENT and/or WITH TIES - # TODO: Why? shouldn't we use TOP always ? - select._simple_int_clause(select._fetch_clause) - and ( - select._fetch_clause_options["percent"] - or select._fetch_clause_options["with_ties"] - ) - ) - ) - - def limit_clause(self, cs, **kwargs): - return "" - - def _check_can_use_fetch_limit(self, select): - # to use ROW_NUMBER(), an ORDER BY is required. - # OFFSET are FETCH are options of the ORDER BY clause - if not select._order_by_clause.clauses: - raise exc.CompileError( - "MSSQL requires an order_by when " - "using an OFFSET or a non-simple " - "LIMIT clause" - ) - - if select._fetch_clause_options is not None and ( - select._fetch_clause_options["percent"] - or select._fetch_clause_options["with_ties"] - ): - raise exc.CompileError( - "MSSQL needs TOP to use PERCENT and/or WITH TIES. " - "Only simple fetch without offset can be used." - ) - - def _row_limit_clause(self, select, **kw): - """MSSQL 2012 supports OFFSET/FETCH operators - Use it instead subquery with row_number - - """ - - if self.dialect._supports_offset_fetch and not self._use_top(select): - self._check_can_use_fetch_limit(select) - - return self.fetch_clause( - select, - fetch_clause=self._get_limit_or_fetch(select), - require_offset=True, - **kw, - ) - - else: - return "" - - def visit_try_cast(self, element, **kw): - return "TRY_CAST (%s AS %s)" % ( - self.process(element.clause, **kw), - self.process(element.typeclause, **kw), - ) - - def translate_select_structure(self, select_stmt, **kwargs): - """Look for ``LIMIT`` and OFFSET in a select statement, and if - so tries to wrap it in a subquery with ``row_number()`` criterion. - MSSQL 2012 and above are excluded - - """ - select = select_stmt - - if ( - select._has_row_limiting_clause - and not self.dialect._supports_offset_fetch - and not self._use_top(select) - and not getattr(select, "_mssql_visit", None) - ): - self._check_can_use_fetch_limit(select) - - _order_by_clauses = [ - sql_util.unwrap_label_reference(elem) - for elem in select._order_by_clause.clauses - ] - - limit_clause = self._get_limit_or_fetch(select) - offset_clause = select._offset_clause - - select = select._generate() - select._mssql_visit = True - select = ( - select.add_columns( - sql.func.ROW_NUMBER() - .over(order_by=_order_by_clauses) - .label("mssql_rn") - ) - .order_by(None) - .alias() - ) - - mssql_rn = sql.column("mssql_rn") - limitselect = sql.select( - *[c for c in select.c if c.key != "mssql_rn"] - ) - if offset_clause is not None: - limitselect = limitselect.where(mssql_rn > offset_clause) - if limit_clause is not None: - limitselect = limitselect.where( - mssql_rn <= (limit_clause + offset_clause) - ) - else: - limitselect = limitselect.where(mssql_rn <= (limit_clause)) - return limitselect - else: - return select - - @_with_legacy_schema_aliasing - def visit_table(self, table, mssql_aliased=False, iscrud=False, **kwargs): - if mssql_aliased is table or iscrud: - return super().visit_table(table, **kwargs) - - # alias schema-qualified tables - alias = self._schema_aliased_table(table) - if alias is not None: - return self.process(alias, mssql_aliased=table, **kwargs) - else: - return super().visit_table(table, **kwargs) - - @_with_legacy_schema_aliasing - def visit_alias(self, alias, **kw): - # translate for schema-qualified table aliases - kw["mssql_aliased"] = alias.element - return super().visit_alias(alias, **kw) - - @_with_legacy_schema_aliasing - def visit_column(self, column, add_to_result_map=None, **kw): - if ( - column.table is not None - and (not self.isupdate and not self.isdelete) - or self.is_subquery() - ): - # translate for schema-qualified table aliases - t = self._schema_aliased_table(column.table) - if t is not None: - converted = elements._corresponding_column_or_error(t, column) - if add_to_result_map is not None: - add_to_result_map( - column.name, - column.name, - (column, column.name, column.key), - column.type, - ) - - return super().visit_column(converted, **kw) - - return super().visit_column( - column, add_to_result_map=add_to_result_map, **kw - ) - - def _schema_aliased_table(self, table): - if getattr(table, "schema", None) is not None: - if table not in self.tablealiases: - self.tablealiases[table] = table.alias() - return self.tablealiases[table] - else: - return None - - def visit_extract(self, extract, **kw): - field = self.extract_map.get(extract.field, extract.field) - return "DATEPART(%s, %s)" % (field, self.process(extract.expr, **kw)) - - def visit_savepoint(self, savepoint_stmt, **kw): - return "SAVE TRANSACTION %s" % self.preparer.format_savepoint( - savepoint_stmt - ) - - def visit_rollback_to_savepoint(self, savepoint_stmt, **kw): - return "ROLLBACK TRANSACTION %s" % self.preparer.format_savepoint( - savepoint_stmt - ) - - def visit_binary(self, binary, **kwargs): - """Move bind parameters to the right-hand side of an operator, where - possible. - - """ - if ( - isinstance(binary.left, expression.BindParameter) - and binary.operator == operator.eq - and not isinstance(binary.right, expression.BindParameter) - ): - return self.process( - expression.BinaryExpression( - binary.right, binary.left, binary.operator - ), - **kwargs, - ) - return super().visit_binary(binary, **kwargs) - - def returning_clause( - self, stmt, returning_cols, *, populate_result_map, **kw - ): - # SQL server returning clause requires that the columns refer to - # the virtual table names "inserted" or "deleted". Here, we make - # a simple alias of our table with that name, and then adapt the - # columns we have from the list of RETURNING columns to that new name - # so that they render as "inserted." / "deleted.". - - if stmt.is_insert or stmt.is_update: - target = stmt.table.alias("inserted") - elif stmt.is_delete: - target = stmt.table.alias("deleted") - else: - assert False, "expected Insert, Update or Delete statement" - - adapter = sql_util.ClauseAdapter(target) - - # adapter.traverse() takes a column from our target table and returns - # the one that is linked to the "inserted" / "deleted" tables. So in - # order to retrieve these values back from the result (e.g. like - # row[column]), tell the compiler to also add the original unadapted - # column to the result map. Before #4877, these were (unknowingly) - # falling back using string name matching in the result set which - # necessarily used an expensive KeyError in order to match. - - columns = [ - self._label_returning_column( - stmt, - adapter.traverse(column), - populate_result_map, - {"result_map_targets": (column,)}, - fallback_label_name=fallback_label_name, - column_is_repeated=repeated, - name=name, - proxy_name=proxy_name, - **kw, - ) - for ( - name, - proxy_name, - fallback_label_name, - column, - repeated, - ) in stmt._generate_columns_plus_names( - True, cols=expression._select_iterables(returning_cols) - ) - ] - - return "OUTPUT " + ", ".join(columns) - - def get_cte_preamble(self, recursive): - # SQL Server finds it too inconvenient to accept - # an entirely optional, SQL standard specified, - # "RECURSIVE" word with their "WITH", - # so here we go - return "WITH" - - def label_select_column(self, select, column, asfrom): - if isinstance(column, expression.Function): - return column.label(None) - else: - return super().label_select_column(select, column, asfrom) - - def for_update_clause(self, select, **kw): - # "FOR UPDATE" is only allowed on "DECLARE CURSOR" which - # SQLAlchemy doesn't use - return "" - - def order_by_clause(self, select, **kw): - # MSSQL only allows ORDER BY in subqueries if there is a LIMIT: - # "The ORDER BY clause is invalid in views, inline functions, - # derived tables, subqueries, and common table expressions, - # unless TOP, OFFSET or FOR XML is also specified." - if ( - self.is_subquery() - and not self._use_top(select) - and ( - select._offset is None - or not self.dialect._supports_offset_fetch - ) - ): - # avoid processing the order by clause if we won't end up - # using it, because we don't want all the bind params tacked - # onto the positional list if that is what the dbapi requires - return "" - - order_by = self.process(select._order_by_clause, **kw) - - if order_by: - return " ORDER BY " + order_by - else: - return "" - - def update_from_clause( - self, update_stmt, from_table, extra_froms, from_hints, **kw - ): - """Render the UPDATE..FROM clause specific to MSSQL. - - In MSSQL, if the UPDATE statement involves an alias of the table to - be updated, then the table itself must be added to the FROM list as - well. Otherwise, it is optional. Here, we add it regardless. - - """ - return "FROM " + ", ".join( - t._compiler_dispatch(self, asfrom=True, fromhints=from_hints, **kw) - for t in [from_table] + extra_froms - ) - - def delete_table_clause(self, delete_stmt, from_table, extra_froms, **kw): - """If we have extra froms make sure we render any alias as hint.""" - ashint = False - if extra_froms: - ashint = True - return from_table._compiler_dispatch( - self, asfrom=True, iscrud=True, ashint=ashint, **kw - ) - - def delete_extra_from_clause( - self, delete_stmt, from_table, extra_froms, from_hints, **kw - ): - """Render the DELETE .. FROM clause specific to MSSQL. - - Yes, it has the FROM keyword twice. - - """ - return "FROM " + ", ".join( - t._compiler_dispatch(self, asfrom=True, fromhints=from_hints, **kw) - for t in [from_table] + extra_froms - ) - - def visit_empty_set_expr(self, type_, **kw): - return "SELECT 1 WHERE 1!=1" - - def visit_is_distinct_from_binary(self, binary, operator, **kw): - return "NOT EXISTS (SELECT %s INTERSECT SELECT %s)" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_is_not_distinct_from_binary(self, binary, operator, **kw): - return "EXISTS (SELECT %s INTERSECT SELECT %s)" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def _render_json_extract_from_binary(self, binary, operator, **kw): - # note we are intentionally calling upon the process() calls in the - # order in which they appear in the SQL String as this is used - # by positional parameter rendering - - if binary.type._type_affinity is sqltypes.JSON: - return "JSON_QUERY(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - # as with other dialects, start with an explicit test for NULL - case_expression = "CASE JSON_VALUE(%s, %s) WHEN NULL THEN NULL" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - if binary.type._type_affinity is sqltypes.Integer: - type_expression = "ELSE CAST(JSON_VALUE(%s, %s) AS INTEGER)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - elif binary.type._type_affinity is sqltypes.Numeric: - type_expression = "ELSE CAST(JSON_VALUE(%s, %s) AS %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ( - "FLOAT" - if isinstance(binary.type, sqltypes.Float) - else "NUMERIC(%s, %s)" - % (binary.type.precision, binary.type.scale) - ), - ) - elif binary.type._type_affinity is sqltypes.Boolean: - # the NULL handling is particularly weird with boolean, so - # explicitly return numeric (BIT) constants - type_expression = ( - "WHEN 'true' THEN 1 WHEN 'false' THEN 0 ELSE NULL" - ) - elif binary.type._type_affinity is sqltypes.String: - # TODO: does this comment (from mysql) apply to here, too? - # this fails with a JSON value that's a four byte unicode - # string. SQLite has the same problem at the moment - type_expression = "ELSE JSON_VALUE(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - else: - # other affinity....this is not expected right now - type_expression = "ELSE JSON_QUERY(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - return case_expression + " " + type_expression + " END" - - def visit_json_getitem_op_binary(self, binary, operator, **kw): - return self._render_json_extract_from_binary(binary, operator, **kw) - - def visit_json_path_getitem_op_binary(self, binary, operator, **kw): - return self._render_json_extract_from_binary(binary, operator, **kw) - - def visit_sequence(self, seq, **kw): - return "NEXT VALUE FOR %s" % self.preparer.format_sequence(seq) - - -class MSSQLStrictCompiler(MSSQLCompiler): - """A subclass of MSSQLCompiler which disables the usage of bind - parameters where not allowed natively by MS-SQL. - - A dialect may use this compiler on a platform where native - binds are used. - - """ - - ansi_bind_rules = True - - def visit_in_op_binary(self, binary, operator, **kw): - kw["literal_execute"] = True - return "%s IN %s" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_not_in_op_binary(self, binary, operator, **kw): - kw["literal_execute"] = True - return "%s NOT IN %s" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def render_literal_value(self, value, type_): - """ - For date and datetime values, convert to a string - format acceptable to MSSQL. That seems to be the - so-called ODBC canonical date format which looks - like this: - - yyyy-mm-dd hh:mi:ss.mmm(24h) - - For other data types, call the base class implementation. - """ - # datetime and date are both subclasses of datetime.date - if issubclass(type(value), datetime.date): - # SQL Server wants single quotes around the date string. - return "'" + str(value) + "'" - else: - return super().render_literal_value(value, type_) - - -class MSDDLCompiler(compiler.DDLCompiler): - def get_column_specification(self, column, **kwargs): - colspec = self.preparer.format_column(column) - - # type is not accepted in a computed column - if column.computed is not None: - colspec += " " + self.process(column.computed) - else: - colspec += " " + self.dialect.type_compiler_instance.process( - column.type, type_expression=column - ) - - if column.nullable is not None: - if ( - not column.nullable - or column.primary_key - or isinstance(column.default, sa_schema.Sequence) - or column.autoincrement is True - or column.identity - ): - colspec += " NOT NULL" - elif column.computed is None: - # don't specify "NULL" for computed columns - colspec += " NULL" - - if column.table is None: - raise exc.CompileError( - "mssql requires Table-bound columns " - "in order to generate DDL" - ) - - d_opt = column.dialect_options["mssql"] - start = d_opt["identity_start"] - increment = d_opt["identity_increment"] - if start is not None or increment is not None: - if column.identity: - raise exc.CompileError( - "Cannot specify options 'mssql_identity_start' and/or " - "'mssql_identity_increment' while also using the " - "'Identity' construct." - ) - util.warn_deprecated( - "The dialect options 'mssql_identity_start' and " - "'mssql_identity_increment' are deprecated. " - "Use the 'Identity' object instead.", - "1.4", - ) - - if column.identity: - colspec += self.process(column.identity, **kwargs) - elif ( - column is column.table._autoincrement_column - or column.autoincrement is True - ) and ( - not isinstance(column.default, Sequence) or column.default.optional - ): - colspec += self.process(Identity(start=start, increment=increment)) - else: - default = self.get_column_default_string(column) - if default is not None: - colspec += " DEFAULT " + default - - return colspec - - def visit_create_index(self, create, include_schema=False, **kw): - index = create.element - self._verify_index_table(index) - preparer = self.preparer - text = "CREATE " - if index.unique: - text += "UNIQUE " - - # handle clustering option - clustered = index.dialect_options["mssql"]["clustered"] - if clustered is not None: - if clustered: - text += "CLUSTERED " - else: - text += "NONCLUSTERED " - - # handle columnstore option (has no negative value) - columnstore = index.dialect_options["mssql"]["columnstore"] - if columnstore: - text += "COLUMNSTORE " - - text += "INDEX %s ON %s" % ( - self._prepared_index_name(index, include_schema=include_schema), - preparer.format_table(index.table), - ) - - # in some case mssql allows indexes with no columns defined - if len(index.expressions) > 0: - text += " (%s)" % ", ".join( - self.sql_compiler.process( - expr, include_table=False, literal_binds=True - ) - for expr in index.expressions - ) - - # handle other included columns - if index.dialect_options["mssql"]["include"]: - inclusions = [ - index.table.c[col] if isinstance(col, str) else col - for col in index.dialect_options["mssql"]["include"] - ] - - text += " INCLUDE (%s)" % ", ".join( - [preparer.quote(c.name) for c in inclusions] - ) - - whereclause = index.dialect_options["mssql"]["where"] - - if whereclause is not None: - whereclause = coercions.expect( - roles.DDLExpressionRole, whereclause - ) - - where_compiled = self.sql_compiler.process( - whereclause, include_table=False, literal_binds=True - ) - text += " WHERE " + where_compiled - - return text - - def visit_drop_index(self, drop, **kw): - return "\nDROP INDEX %s ON %s" % ( - self._prepared_index_name(drop.element, include_schema=False), - self.preparer.format_table(drop.element.table), - ) - - def visit_primary_key_constraint(self, constraint, **kw): - if len(constraint) == 0: - return "" - text = "" - if constraint.name is not None: - text += "CONSTRAINT %s " % self.preparer.format_constraint( - constraint - ) - text += "PRIMARY KEY " - - clustered = constraint.dialect_options["mssql"]["clustered"] - if clustered is not None: - if clustered: - text += "CLUSTERED " - else: - text += "NONCLUSTERED " - - text += "(%s)" % ", ".join( - self.preparer.quote(c.name) for c in constraint - ) - text += self.define_constraint_deferrability(constraint) - return text - - def visit_unique_constraint(self, constraint, **kw): - if len(constraint) == 0: - return "" - text = "" - if constraint.name is not None: - formatted_name = self.preparer.format_constraint(constraint) - if formatted_name is not None: - text += "CONSTRAINT %s " % formatted_name - text += "UNIQUE %s" % self.define_unique_constraint_distinct( - constraint, **kw - ) - clustered = constraint.dialect_options["mssql"]["clustered"] - if clustered is not None: - if clustered: - text += "CLUSTERED " - else: - text += "NONCLUSTERED " - - text += "(%s)" % ", ".join( - self.preparer.quote(c.name) for c in constraint - ) - text += self.define_constraint_deferrability(constraint) - return text - - def visit_computed_column(self, generated, **kw): - text = "AS (%s)" % self.sql_compiler.process( - generated.sqltext, include_table=False, literal_binds=True - ) - # explicitly check for True|False since None means server default - if generated.persisted is True: - text += " PERSISTED" - return text - - def visit_set_table_comment(self, create, **kw): - schema = self.preparer.schema_for_object(create.element) - schema_name = schema if schema else self.dialect.default_schema_name - return ( - "execute sp_addextendedproperty 'MS_Description', " - "{}, 'schema', {}, 'table', {}".format( - self.sql_compiler.render_literal_value( - create.element.comment, sqltypes.NVARCHAR() - ), - self.preparer.quote_schema(schema_name), - self.preparer.format_table(create.element, use_schema=False), - ) - ) - - def visit_drop_table_comment(self, drop, **kw): - schema = self.preparer.schema_for_object(drop.element) - schema_name = schema if schema else self.dialect.default_schema_name - return ( - "execute sp_dropextendedproperty 'MS_Description', 'schema', " - "{}, 'table', {}".format( - self.preparer.quote_schema(schema_name), - self.preparer.format_table(drop.element, use_schema=False), - ) - ) - - def visit_set_column_comment(self, create, **kw): - schema = self.preparer.schema_for_object(create.element.table) - schema_name = schema if schema else self.dialect.default_schema_name - return ( - "execute sp_addextendedproperty 'MS_Description', " - "{}, 'schema', {}, 'table', {}, 'column', {}".format( - self.sql_compiler.render_literal_value( - create.element.comment, sqltypes.NVARCHAR() - ), - self.preparer.quote_schema(schema_name), - self.preparer.format_table( - create.element.table, use_schema=False - ), - self.preparer.format_column(create.element), - ) - ) - - def visit_drop_column_comment(self, drop, **kw): - schema = self.preparer.schema_for_object(drop.element.table) - schema_name = schema if schema else self.dialect.default_schema_name - return ( - "execute sp_dropextendedproperty 'MS_Description', 'schema', " - "{}, 'table', {}, 'column', {}".format( - self.preparer.quote_schema(schema_name), - self.preparer.format_table( - drop.element.table, use_schema=False - ), - self.preparer.format_column(drop.element), - ) - ) - - def visit_create_sequence(self, create, **kw): - prefix = None - if create.element.data_type is not None: - data_type = create.element.data_type - prefix = " AS %s" % self.type_compiler.process(data_type) - return super().visit_create_sequence(create, prefix=prefix, **kw) - - def visit_identity_column(self, identity, **kw): - text = " IDENTITY" - if identity.start is not None or identity.increment is not None: - start = 1 if identity.start is None else identity.start - increment = 1 if identity.increment is None else identity.increment - text += "(%s,%s)" % (start, increment) - return text - - -class MSIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = RESERVED_WORDS - - def __init__(self, dialect): - super().__init__( - dialect, - initial_quote="[", - final_quote="]", - quote_case_sensitive_collations=False, - ) - - def _escape_identifier(self, value): - return value.replace("]", "]]") - - def _unescape_identifier(self, value): - return value.replace("]]", "]") - - def quote_schema(self, schema, force=None): - """Prepare a quoted table and schema name.""" - - # need to re-implement the deprecation warning entirely - if force is not None: - # not using the util.deprecated_params() decorator in this - # case because of the additional function call overhead on this - # very performance-critical spot. - util.warn_deprecated( - "The IdentifierPreparer.quote_schema.force parameter is " - "deprecated and will be removed in a future release. This " - "flag has no effect on the behavior of the " - "IdentifierPreparer.quote method; please refer to " - "quoted_name().", - version="1.3", - ) - - dbname, owner = _schema_elements(schema) - if dbname: - result = "%s.%s" % (self.quote(dbname), self.quote(owner)) - elif owner: - result = self.quote(owner) - else: - result = "" - return result - - -def _db_plus_owner_listing(fn): - def wrap(dialect, connection, schema=None, **kw): - dbname, owner = _owner_plus_db(dialect, schema) - return _switch_db( - dbname, - connection, - fn, - dialect, - connection, - dbname, - owner, - schema, - **kw, - ) - - return update_wrapper(wrap, fn) - - -def _db_plus_owner(fn): - def wrap(dialect, connection, tablename, schema=None, **kw): - dbname, owner = _owner_plus_db(dialect, schema) - return _switch_db( - dbname, - connection, - fn, - dialect, - connection, - tablename, - dbname, - owner, - schema, - **kw, - ) - - return update_wrapper(wrap, fn) - - -def _switch_db(dbname, connection, fn, *arg, **kw): - if dbname: - current_db = connection.exec_driver_sql("select db_name()").scalar() - if current_db != dbname: - connection.exec_driver_sql( - "use %s" % connection.dialect.identifier_preparer.quote(dbname) - ) - try: - return fn(*arg, **kw) - finally: - if dbname and current_db != dbname: - connection.exec_driver_sql( - "use %s" - % connection.dialect.identifier_preparer.quote(current_db) - ) - - -def _owner_plus_db(dialect, schema): - if not schema: - return None, dialect.default_schema_name - else: - return _schema_elements(schema) - - -_memoized_schema = util.LRUCache() - - -def _schema_elements(schema): - if isinstance(schema, quoted_name) and schema.quote: - return None, schema - - if schema in _memoized_schema: - return _memoized_schema[schema] - - # tests for this function are in: - # test/dialect/mssql/test_reflection.py -> - # OwnerPlusDBTest.test_owner_database_pairs - # test/dialect/mssql/test_compiler.py -> test_force_schema_* - # test/dialect/mssql/test_compiler.py -> test_schema_many_tokens_* - # - - if schema.startswith("__[SCHEMA_"): - return None, schema - - push = [] - symbol = "" - bracket = False - has_brackets = False - for token in re.split(r"(\[|\]|\.)", schema): - if not token: - continue - if token == "[": - bracket = True - has_brackets = True - elif token == "]": - bracket = False - elif not bracket and token == ".": - if has_brackets: - push.append("[%s]" % symbol) - else: - push.append(symbol) - symbol = "" - has_brackets = False - else: - symbol += token - if symbol: - push.append(symbol) - if len(push) > 1: - dbname, owner = ".".join(push[0:-1]), push[-1] - - # test for internal brackets - if re.match(r".*\].*\[.*", dbname[1:-1]): - dbname = quoted_name(dbname, quote=False) - else: - dbname = dbname.lstrip("[").rstrip("]") - - elif len(push): - dbname, owner = None, push[0] - else: - dbname, owner = None, None - - _memoized_schema[schema] = dbname, owner - return dbname, owner - - -class MSDialect(default.DefaultDialect): - # will assume it's at least mssql2005 - name = "mssql" - supports_statement_cache = True - supports_default_values = True - supports_empty_insert = False - favor_returning_over_lastrowid = True - - returns_native_bytes = True - - supports_comments = True - supports_default_metavalue = False - """dialect supports INSERT... VALUES (DEFAULT) syntax - - SQL Server **does** support this, but **not** for the IDENTITY column, - so we can't turn this on. - - """ - - # supports_native_uuid is partial here, so we implement our - # own impl type - - execution_ctx_cls = MSExecutionContext - use_scope_identity = True - max_identifier_length = 128 - schema_name = "dbo" - - insert_returning = True - update_returning = True - delete_returning = True - update_returning_multifrom = True - delete_returning_multifrom = True - - colspecs = { - sqltypes.DateTime: _MSDateTime, - sqltypes.Date: _MSDate, - sqltypes.JSON: JSON, - sqltypes.JSON.JSONIndexType: JSONIndexType, - sqltypes.JSON.JSONPathType: JSONPathType, - sqltypes.Time: _BASETIMEIMPL, - sqltypes.Unicode: _MSUnicode, - sqltypes.UnicodeText: _MSUnicodeText, - DATETIMEOFFSET: DATETIMEOFFSET, - DATETIME2: DATETIME2, - SMALLDATETIME: SMALLDATETIME, - DATETIME: DATETIME, - sqltypes.Uuid: MSUUid, - } - - engine_config_types = default.DefaultDialect.engine_config_types.union( - {"legacy_schema_aliasing": util.asbool} - ) - - ischema_names = ischema_names - - supports_sequences = True - sequences_optional = True - # This is actually used for autoincrement, where itentity is used that - # starts with 1. - # for sequences T-SQL's actual default is -9223372036854775808 - default_sequence_base = 1 - - supports_native_boolean = False - non_native_boolean_check_constraint = False - supports_unicode_binds = True - postfetch_lastrowid = True - - # may be changed at server inspection time for older SQL server versions - supports_multivalues_insert = True - - use_insertmanyvalues = True - - # note pyodbc will set this to False if fast_executemany is set, - # as of SQLAlchemy 2.0.9 - use_insertmanyvalues_wo_returning = True - - insertmanyvalues_implicit_sentinel = ( - InsertmanyvaluesSentinelOpts.AUTOINCREMENT - | InsertmanyvaluesSentinelOpts.IDENTITY - | InsertmanyvaluesSentinelOpts.USE_INSERT_FROM_SELECT - ) - - # "The incoming request has too many parameters. The server supports a " - # "maximum of 2100 parameters." - # in fact you can have 2099 parameters. - insertmanyvalues_max_parameters = 2099 - - _supports_offset_fetch = False - _supports_nvarchar_max = False - - legacy_schema_aliasing = False - - server_version_info = () - - statement_compiler = MSSQLCompiler - ddl_compiler = MSDDLCompiler - type_compiler_cls = MSTypeCompiler - preparer = MSIdentifierPreparer - - construct_arguments = [ - (sa_schema.PrimaryKeyConstraint, {"clustered": None}), - (sa_schema.UniqueConstraint, {"clustered": None}), - ( - sa_schema.Index, - { - "clustered": None, - "include": None, - "where": None, - "columnstore": None, - }, - ), - ( - sa_schema.Column, - {"identity_start": None, "identity_increment": None}, - ), - ] - - def __init__( - self, - query_timeout=None, - use_scope_identity=True, - schema_name="dbo", - deprecate_large_types=None, - supports_comments=None, - json_serializer=None, - json_deserializer=None, - legacy_schema_aliasing=None, - ignore_no_transaction_on_rollback=False, - **opts, - ): - self.query_timeout = int(query_timeout or 0) - self.schema_name = schema_name - - self.use_scope_identity = use_scope_identity - self.deprecate_large_types = deprecate_large_types - self.ignore_no_transaction_on_rollback = ( - ignore_no_transaction_on_rollback - ) - self._user_defined_supports_comments = uds = supports_comments - if uds is not None: - self.supports_comments = uds - - if legacy_schema_aliasing is not None: - util.warn_deprecated( - "The legacy_schema_aliasing parameter is " - "deprecated and will be removed in a future release.", - "1.4", - ) - self.legacy_schema_aliasing = legacy_schema_aliasing - - super().__init__(**opts) - - self._json_serializer = json_serializer - self._json_deserializer = json_deserializer - - def do_savepoint(self, connection, name): - # give the DBAPI a push - connection.exec_driver_sql("IF @@TRANCOUNT = 0 BEGIN TRANSACTION") - super().do_savepoint(connection, name) - - def do_release_savepoint(self, connection, name): - # SQL Server does not support RELEASE SAVEPOINT - pass - - def do_rollback(self, dbapi_connection): - try: - super().do_rollback(dbapi_connection) - except self.dbapi.ProgrammingError as e: - if self.ignore_no_transaction_on_rollback and re.match( - r".*\b111214\b", str(e) - ): - util.warn( - "ProgrammingError 111214 " - "'No corresponding transaction found.' " - "has been suppressed via " - "ignore_no_transaction_on_rollback=True" - ) - else: - raise - - _isolation_lookup = { - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "SNAPSHOT", - } - - def get_isolation_level_values(self, dbapi_connection): - return list(self._isolation_lookup) - - def set_isolation_level(self, dbapi_connection, level): - cursor = dbapi_connection.cursor() - cursor.execute(f"SET TRANSACTION ISOLATION LEVEL {level}") - cursor.close() - if level == "SNAPSHOT": - dbapi_connection.commit() - - def get_isolation_level(self, dbapi_connection): - cursor = dbapi_connection.cursor() - view_name = "sys.system_views" - try: - cursor.execute( - ( - "SELECT name FROM {} WHERE name IN " - "('dm_exec_sessions', 'dm_pdw_nodes_exec_sessions')" - ).format(view_name) - ) - row = cursor.fetchone() - if not row: - raise NotImplementedError( - "Can't fetch isolation level on this particular " - "SQL Server version." - ) - - view_name = f"sys.{row[0]}" - - cursor.execute( - """ - SELECT CASE transaction_isolation_level - WHEN 0 THEN NULL - WHEN 1 THEN 'READ UNCOMMITTED' - WHEN 2 THEN 'READ COMMITTED' - WHEN 3 THEN 'REPEATABLE READ' - WHEN 4 THEN 'SERIALIZABLE' - WHEN 5 THEN 'SNAPSHOT' END - AS TRANSACTION_ISOLATION_LEVEL - FROM {} - where session_id = @@SPID - """.format( - view_name - ) - ) - except self.dbapi.Error as err: - raise NotImplementedError( - "Can't fetch isolation level; encountered error {} when " - 'attempting to query the "{}" view.'.format(err, view_name) - ) from err - else: - row = cursor.fetchone() - return row[0].upper() - finally: - cursor.close() - - def initialize(self, connection): - super().initialize(connection) - self._setup_version_attributes() - self._setup_supports_nvarchar_max(connection) - self._setup_supports_comments(connection) - - def _setup_version_attributes(self): - if self.server_version_info[0] not in list(range(8, 17)): - util.warn( - "Unrecognized server version info '%s'. Some SQL Server " - "features may not function properly." - % ".".join(str(x) for x in self.server_version_info) - ) - - if self.server_version_info >= MS_2008_VERSION: - self.supports_multivalues_insert = True - else: - self.supports_multivalues_insert = False - - if self.deprecate_large_types is None: - self.deprecate_large_types = ( - self.server_version_info >= MS_2012_VERSION - ) - - self._supports_offset_fetch = ( - self.server_version_info and self.server_version_info[0] >= 11 - ) - - def _setup_supports_nvarchar_max(self, connection): - try: - connection.scalar( - sql.text("SELECT CAST('test max support' AS NVARCHAR(max))") - ) - except exc.DBAPIError: - self._supports_nvarchar_max = False - else: - self._supports_nvarchar_max = True - - def _setup_supports_comments(self, connection): - if self._user_defined_supports_comments is not None: - return - - try: - connection.scalar( - sql.text( - "SELECT 1 FROM fn_listextendedproperty" - "(default, default, default, default, " - "default, default, default)" - ) - ) - except exc.DBAPIError: - self.supports_comments = False - else: - self.supports_comments = True - - def _get_default_schema_name(self, connection): - query = sql.text("SELECT schema_name()") - default_schema_name = connection.scalar(query) - if default_schema_name is not None: - # guard against the case where the default_schema_name is being - # fed back into a table reflection function. - return quoted_name(default_schema_name, quote=True) - else: - return self.schema_name - - @_db_plus_owner - def has_table(self, connection, tablename, dbname, owner, schema, **kw): - self._ensure_has_table_connection(connection) - - return self._internal_has_table(connection, tablename, owner, **kw) - - @reflection.cache - @_db_plus_owner - def has_sequence( - self, connection, sequencename, dbname, owner, schema, **kw - ): - sequences = ischema.sequences - - s = sql.select(sequences.c.sequence_name).where( - sequences.c.sequence_name == sequencename - ) - - if owner: - s = s.where(sequences.c.sequence_schema == owner) - - c = connection.execute(s) - - return c.first() is not None - - @reflection.cache - @_db_plus_owner_listing - def get_sequence_names(self, connection, dbname, owner, schema, **kw): - sequences = ischema.sequences - - s = sql.select(sequences.c.sequence_name) - if owner: - s = s.where(sequences.c.sequence_schema == owner) - - c = connection.execute(s) - - return [row[0] for row in c] - - @reflection.cache - def get_schema_names(self, connection, **kw): - s = sql.select(ischema.schemata.c.schema_name).order_by( - ischema.schemata.c.schema_name - ) - schema_names = [r[0] for r in connection.execute(s)] - return schema_names - - @reflection.cache - @_db_plus_owner_listing - def get_table_names(self, connection, dbname, owner, schema, **kw): - tables = ischema.tables - s = ( - sql.select(tables.c.table_name) - .where( - sql.and_( - tables.c.table_schema == owner, - tables.c.table_type == "BASE TABLE", - ) - ) - .order_by(tables.c.table_name) - ) - table_names = [r[0] for r in connection.execute(s)] - return table_names - - @reflection.cache - @_db_plus_owner_listing - def get_view_names(self, connection, dbname, owner, schema, **kw): - tables = ischema.tables - s = ( - sql.select(tables.c.table_name) - .where( - sql.and_( - tables.c.table_schema == owner, - tables.c.table_type == "VIEW", - ) - ) - .order_by(tables.c.table_name) - ) - view_names = [r[0] for r in connection.execute(s)] - return view_names - - @reflection.cache - def _internal_has_table(self, connection, tablename, owner, **kw): - if tablename.startswith("#"): # temporary table - # mssql does not support temporary views - # SQL Error [4103] [S0001]: "#v": Temporary views are not allowed - return bool( - connection.scalar( - # U filters on user tables only. - text("SELECT object_id(:table_name, 'U')"), - {"table_name": f"tempdb.dbo.[{tablename}]"}, - ) - ) - else: - tables = ischema.tables - - s = sql.select(tables.c.table_name).where( - sql.and_( - sql.or_( - tables.c.table_type == "BASE TABLE", - tables.c.table_type == "VIEW", - ), - tables.c.table_name == tablename, - ) - ) - - if owner: - s = s.where(tables.c.table_schema == owner) - - c = connection.execute(s) - - return c.first() is not None - - def _default_or_error(self, connection, tablename, owner, method, **kw): - # TODO: try to avoid having to run a separate query here - if self._internal_has_table(connection, tablename, owner, **kw): - return method() - else: - raise exc.NoSuchTableError(f"{owner}.{tablename}") - - @reflection.cache - @_db_plus_owner - def get_indexes(self, connection, tablename, dbname, owner, schema, **kw): - filter_definition = ( - "ind.filter_definition" - if self.server_version_info >= MS_2008_VERSION - else "NULL as filter_definition" - ) - rp = connection.execution_options(future_result=True).execute( - sql.text( - f""" -select - ind.index_id, - ind.is_unique, - ind.name, - ind.type, - {filter_definition} -from - sys.indexes as ind -join sys.tables as tab on - ind.object_id = tab.object_id -join sys.schemas as sch on - sch.schema_id = tab.schema_id -where - tab.name = :tabname - and sch.name = :schname - and ind.is_primary_key = 0 - and ind.type != 0 -order by - ind.name - """ - ) - .bindparams( - sql.bindparam("tabname", tablename, ischema.CoerceUnicode()), - sql.bindparam("schname", owner, ischema.CoerceUnicode()), - ) - .columns(name=sqltypes.Unicode()) - ) - indexes = {} - for row in rp.mappings(): - indexes[row["index_id"]] = current = { - "name": row["name"], - "unique": row["is_unique"] == 1, - "column_names": [], - "include_columns": [], - "dialect_options": {}, - } - - do = current["dialect_options"] - index_type = row["type"] - if index_type in {1, 2}: - do["mssql_clustered"] = index_type == 1 - if index_type in {5, 6}: - do["mssql_clustered"] = index_type == 5 - do["mssql_columnstore"] = True - if row["filter_definition"] is not None: - do["mssql_where"] = row["filter_definition"] - - rp = connection.execution_options(future_result=True).execute( - sql.text( - """ -select - ind_col.index_id, - col.name, - ind_col.is_included_column -from - sys.columns as col -join sys.tables as tab on - tab.object_id = col.object_id -join sys.index_columns as ind_col on - ind_col.column_id = col.column_id - and ind_col.object_id = tab.object_id -join sys.schemas as sch on - sch.schema_id = tab.schema_id -where - tab.name = :tabname - and sch.name = :schname - """ - ) - .bindparams( - sql.bindparam("tabname", tablename, ischema.CoerceUnicode()), - sql.bindparam("schname", owner, ischema.CoerceUnicode()), - ) - .columns(name=sqltypes.Unicode()) - ) - for row in rp.mappings(): - if row["index_id"] not in indexes: - continue - index_def = indexes[row["index_id"]] - is_colstore = index_def["dialect_options"].get("mssql_columnstore") - is_clustered = index_def["dialect_options"].get("mssql_clustered") - if not (is_colstore and is_clustered): - # a clustered columnstore index includes all columns but does - # not want them in the index definition - if row["is_included_column"] and not is_colstore: - # a noncludsted columnstore index reports that includes - # columns but requires that are listed as normal columns - index_def["include_columns"].append(row["name"]) - else: - index_def["column_names"].append(row["name"]) - for index_info in indexes.values(): - # NOTE: "root level" include_columns is legacy, now part of - # dialect_options (issue #7382) - index_info["dialect_options"]["mssql_include"] = index_info[ - "include_columns" - ] - - if indexes: - return list(indexes.values()) - else: - return self._default_or_error( - connection, tablename, owner, ReflectionDefaults.indexes, **kw - ) - - @reflection.cache - @_db_plus_owner - def get_view_definition( - self, connection, viewname, dbname, owner, schema, **kw - ): - view_def = connection.execute( - sql.text( - "select mod.definition " - "from sys.sql_modules as mod " - "join sys.views as views on mod.object_id = views.object_id " - "join sys.schemas as sch on views.schema_id = sch.schema_id " - "where views.name=:viewname and sch.name=:schname" - ).bindparams( - sql.bindparam("viewname", viewname, ischema.CoerceUnicode()), - sql.bindparam("schname", owner, ischema.CoerceUnicode()), - ) - ).scalar() - if view_def: - return view_def - else: - raise exc.NoSuchTableError(f"{owner}.{viewname}") - - @reflection.cache - def get_table_comment(self, connection, table_name, schema=None, **kw): - if not self.supports_comments: - raise NotImplementedError( - "Can't get table comments on current SQL Server version in use" - ) - - schema_name = schema if schema else self.default_schema_name - COMMENT_SQL = """ - SELECT cast(com.value as nvarchar(max)) - FROM fn_listextendedproperty('MS_Description', - 'schema', :schema, 'table', :table, NULL, NULL - ) as com; - """ - - comment = connection.execute( - sql.text(COMMENT_SQL).bindparams( - sql.bindparam("schema", schema_name, ischema.CoerceUnicode()), - sql.bindparam("table", table_name, ischema.CoerceUnicode()), - ) - ).scalar() - if comment: - return {"text": comment} - else: - return self._default_or_error( - connection, - table_name, - None, - ReflectionDefaults.table_comment, - **kw, - ) - - def _temp_table_name_like_pattern(self, tablename): - # LIKE uses '%' to match zero or more characters and '_' to match any - # single character. We want to match literal underscores, so T-SQL - # requires that we enclose them in square brackets. - return tablename + ( - ("[_][_][_]%") if not tablename.startswith("##") else "" - ) - - def _get_internal_temp_table_name(self, connection, tablename): - # it's likely that schema is always "dbo", but since we can - # get it here, let's get it. - # see https://stackoverflow.com/questions/8311959/ - # specifying-schema-for-temporary-tables - - try: - return connection.execute( - sql.text( - "select table_schema, table_name " - "from tempdb.information_schema.tables " - "where table_name like :p1" - ), - {"p1": self._temp_table_name_like_pattern(tablename)}, - ).one() - except exc.MultipleResultsFound as me: - raise exc.UnreflectableTableError( - "Found more than one temporary table named '%s' in tempdb " - "at this time. Cannot reliably resolve that name to its " - "internal table name." % tablename - ) from me - except exc.NoResultFound as ne: - raise exc.NoSuchTableError( - "Unable to find a temporary table named '%s' in tempdb." - % tablename - ) from ne - - @reflection.cache - @_db_plus_owner - def get_columns(self, connection, tablename, dbname, owner, schema, **kw): - is_temp_table = tablename.startswith("#") - if is_temp_table: - owner, tablename = self._get_internal_temp_table_name( - connection, tablename - ) - - columns = ischema.mssql_temp_table_columns - else: - columns = ischema.columns - - computed_cols = ischema.computed_columns - identity_cols = ischema.identity_columns - if owner: - whereclause = sql.and_( - columns.c.table_name == tablename, - columns.c.table_schema == owner, - ) - full_name = columns.c.table_schema + "." + columns.c.table_name - else: - whereclause = columns.c.table_name == tablename - full_name = columns.c.table_name - - if self._supports_nvarchar_max: - computed_definition = computed_cols.c.definition - else: - # tds_version 4.2 does not support NVARCHAR(MAX) - computed_definition = sql.cast( - computed_cols.c.definition, NVARCHAR(4000) - ) - - object_id = func.object_id(full_name) - - s = ( - sql.select( - columns.c.column_name, - columns.c.data_type, - columns.c.is_nullable, - columns.c.character_maximum_length, - columns.c.numeric_precision, - columns.c.numeric_scale, - columns.c.column_default, - columns.c.collation_name, - computed_definition, - computed_cols.c.is_persisted, - identity_cols.c.is_identity, - identity_cols.c.seed_value, - identity_cols.c.increment_value, - ischema.extended_properties.c.value.label("comment"), - ) - .select_from(columns) - .outerjoin( - computed_cols, - onclause=sql.and_( - computed_cols.c.object_id == object_id, - computed_cols.c.name - == columns.c.column_name.collate("DATABASE_DEFAULT"), - ), - ) - .outerjoin( - identity_cols, - onclause=sql.and_( - identity_cols.c.object_id == object_id, - identity_cols.c.name - == columns.c.column_name.collate("DATABASE_DEFAULT"), - ), - ) - .outerjoin( - ischema.extended_properties, - onclause=sql.and_( - ischema.extended_properties.c["class"] == 1, - ischema.extended_properties.c.major_id == object_id, - ischema.extended_properties.c.minor_id - == columns.c.ordinal_position, - ischema.extended_properties.c.name == "MS_Description", - ), - ) - .where(whereclause) - .order_by(columns.c.ordinal_position) - ) - - c = connection.execution_options(future_result=True).execute(s) - - cols = [] - for row in c.mappings(): - name = row[columns.c.column_name] - type_ = row[columns.c.data_type] - nullable = row[columns.c.is_nullable] == "YES" - charlen = row[columns.c.character_maximum_length] - numericprec = row[columns.c.numeric_precision] - numericscale = row[columns.c.numeric_scale] - default = row[columns.c.column_default] - collation = row[columns.c.collation_name] - definition = row[computed_definition] - is_persisted = row[computed_cols.c.is_persisted] - is_identity = row[identity_cols.c.is_identity] - identity_start = row[identity_cols.c.seed_value] - identity_increment = row[identity_cols.c.increment_value] - comment = row[ischema.extended_properties.c.value] - - coltype = self.ischema_names.get(type_, None) - - kwargs = {} - if coltype in ( - MSString, - MSChar, - MSNVarchar, - MSNChar, - MSText, - MSNText, - MSBinary, - MSVarBinary, - sqltypes.LargeBinary, - ): - if charlen == -1: - charlen = None - kwargs["length"] = charlen - if collation: - kwargs["collation"] = collation - - if coltype is None: - util.warn( - "Did not recognize type '%s' of column '%s'" - % (type_, name) - ) - coltype = sqltypes.NULLTYPE - else: - if issubclass(coltype, sqltypes.Numeric): - kwargs["precision"] = numericprec - - if not issubclass(coltype, sqltypes.Float): - kwargs["scale"] = numericscale - - coltype = coltype(**kwargs) - cdict = { - "name": name, - "type": coltype, - "nullable": nullable, - "default": default, - "autoincrement": is_identity is not None, - "comment": comment, - } - - if definition is not None and is_persisted is not None: - cdict["computed"] = { - "sqltext": definition, - "persisted": is_persisted, - } - - if is_identity is not None: - # identity_start and identity_increment are Decimal or None - if identity_start is None or identity_increment is None: - cdict["identity"] = {} - else: - if isinstance(coltype, sqltypes.BigInteger): - start = int(identity_start) - increment = int(identity_increment) - elif isinstance(coltype, sqltypes.Integer): - start = int(identity_start) - increment = int(identity_increment) - else: - start = identity_start - increment = identity_increment - - cdict["identity"] = { - "start": start, - "increment": increment, - } - - cols.append(cdict) - - if cols: - return cols - else: - return self._default_or_error( - connection, tablename, owner, ReflectionDefaults.columns, **kw - ) - - @reflection.cache - @_db_plus_owner - def get_pk_constraint( - self, connection, tablename, dbname, owner, schema, **kw - ): - pkeys = [] - TC = ischema.constraints - C = ischema.key_constraints.alias("C") - - # Primary key constraints - s = ( - sql.select( - C.c.column_name, - TC.c.constraint_type, - C.c.constraint_name, - func.objectproperty( - func.object_id( - C.c.table_schema + "." + C.c.constraint_name - ), - "CnstIsClustKey", - ).label("is_clustered"), - ) - .where( - sql.and_( - TC.c.constraint_name == C.c.constraint_name, - TC.c.table_schema == C.c.table_schema, - C.c.table_name == tablename, - C.c.table_schema == owner, - ), - ) - .order_by(TC.c.constraint_name, C.c.ordinal_position) - ) - c = connection.execution_options(future_result=True).execute(s) - constraint_name = None - is_clustered = None - for row in c.mappings(): - if "PRIMARY" in row[TC.c.constraint_type.name]: - pkeys.append(row["COLUMN_NAME"]) - if constraint_name is None: - constraint_name = row[C.c.constraint_name.name] - if is_clustered is None: - is_clustered = row["is_clustered"] - if pkeys: - return { - "constrained_columns": pkeys, - "name": constraint_name, - "dialect_options": {"mssql_clustered": is_clustered}, - } - else: - return self._default_or_error( - connection, - tablename, - owner, - ReflectionDefaults.pk_constraint, - **kw, - ) - - @reflection.cache - @_db_plus_owner - def get_foreign_keys( - self, connection, tablename, dbname, owner, schema, **kw - ): - # Foreign key constraints - s = ( - text( - """\ -WITH fk_info AS ( - SELECT - ischema_ref_con.constraint_schema, - ischema_ref_con.constraint_name, - ischema_key_col.ordinal_position, - ischema_key_col.table_schema, - ischema_key_col.table_name, - ischema_ref_con.unique_constraint_schema, - ischema_ref_con.unique_constraint_name, - ischema_ref_con.match_option, - ischema_ref_con.update_rule, - ischema_ref_con.delete_rule, - ischema_key_col.column_name AS constrained_column - FROM - INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS ischema_ref_con - INNER JOIN - INFORMATION_SCHEMA.KEY_COLUMN_USAGE ischema_key_col ON - ischema_key_col.table_schema = ischema_ref_con.constraint_schema - AND ischema_key_col.constraint_name = - ischema_ref_con.constraint_name - WHERE ischema_key_col.table_name = :tablename - AND ischema_key_col.table_schema = :owner -), -constraint_info AS ( - SELECT - ischema_key_col.constraint_schema, - ischema_key_col.constraint_name, - ischema_key_col.ordinal_position, - ischema_key_col.table_schema, - ischema_key_col.table_name, - ischema_key_col.column_name - FROM - INFORMATION_SCHEMA.KEY_COLUMN_USAGE ischema_key_col -), -index_info AS ( - SELECT - sys.schemas.name AS index_schema, - sys.indexes.name AS index_name, - sys.index_columns.key_ordinal AS ordinal_position, - sys.schemas.name AS table_schema, - sys.objects.name AS table_name, - sys.columns.name AS column_name - FROM - sys.indexes - INNER JOIN - sys.objects ON - sys.objects.object_id = sys.indexes.object_id - INNER JOIN - sys.schemas ON - sys.schemas.schema_id = sys.objects.schema_id - INNER JOIN - sys.index_columns ON - sys.index_columns.object_id = sys.objects.object_id - AND sys.index_columns.index_id = sys.indexes.index_id - INNER JOIN - sys.columns ON - sys.columns.object_id = sys.indexes.object_id - AND sys.columns.column_id = sys.index_columns.column_id -) - SELECT - fk_info.constraint_schema, - fk_info.constraint_name, - fk_info.ordinal_position, - fk_info.constrained_column, - constraint_info.table_schema AS referred_table_schema, - constraint_info.table_name AS referred_table_name, - constraint_info.column_name AS referred_column, - fk_info.match_option, - fk_info.update_rule, - fk_info.delete_rule - FROM - fk_info INNER JOIN constraint_info ON - constraint_info.constraint_schema = - fk_info.unique_constraint_schema - AND constraint_info.constraint_name = - fk_info.unique_constraint_name - AND constraint_info.ordinal_position = fk_info.ordinal_position - UNION - SELECT - fk_info.constraint_schema, - fk_info.constraint_name, - fk_info.ordinal_position, - fk_info.constrained_column, - index_info.table_schema AS referred_table_schema, - index_info.table_name AS referred_table_name, - index_info.column_name AS referred_column, - fk_info.match_option, - fk_info.update_rule, - fk_info.delete_rule - FROM - fk_info INNER JOIN index_info ON - index_info.index_schema = fk_info.unique_constraint_schema - AND index_info.index_name = fk_info.unique_constraint_name - AND index_info.ordinal_position = fk_info.ordinal_position - - ORDER BY fk_info.constraint_schema, fk_info.constraint_name, - fk_info.ordinal_position -""" - ) - .bindparams( - sql.bindparam("tablename", tablename, ischema.CoerceUnicode()), - sql.bindparam("owner", owner, ischema.CoerceUnicode()), - ) - .columns( - constraint_schema=sqltypes.Unicode(), - constraint_name=sqltypes.Unicode(), - table_schema=sqltypes.Unicode(), - table_name=sqltypes.Unicode(), - constrained_column=sqltypes.Unicode(), - referred_table_schema=sqltypes.Unicode(), - referred_table_name=sqltypes.Unicode(), - referred_column=sqltypes.Unicode(), - ) - ) - - # group rows by constraint ID, to handle multi-column FKs - fkeys = [] - - def fkey_rec(): - return { - "name": None, - "constrained_columns": [], - "referred_schema": None, - "referred_table": None, - "referred_columns": [], - "options": {}, - } - - fkeys = util.defaultdict(fkey_rec) - - for r in connection.execute(s).all(): - ( - _, # constraint schema - rfknm, - _, # ordinal position - scol, - rschema, - rtbl, - rcol, - # TODO: we support match= for foreign keys so - # we can support this also, PG has match=FULL for example - # but this seems to not be a valid value for SQL Server - _, # match rule - fkuprule, - fkdelrule, - ) = r - - rec = fkeys[rfknm] - rec["name"] = rfknm - - if fkuprule != "NO ACTION": - rec["options"]["onupdate"] = fkuprule - - if fkdelrule != "NO ACTION": - rec["options"]["ondelete"] = fkdelrule - - if not rec["referred_table"]: - rec["referred_table"] = rtbl - if schema is not None or owner != rschema: - if dbname: - rschema = dbname + "." + rschema - rec["referred_schema"] = rschema - - local_cols, remote_cols = ( - rec["constrained_columns"], - rec["referred_columns"], - ) - - local_cols.append(scol) - remote_cols.append(rcol) - - if fkeys: - return list(fkeys.values()) - else: - return self._default_or_error( - connection, - tablename, - owner, - ReflectionDefaults.foreign_keys, - **kw, - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/information_schema.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/information_schema.py deleted file mode 100644 index 0c5f237..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/information_schema.py +++ /dev/null @@ -1,254 +0,0 @@ -# dialects/mssql/information_schema.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import cast -from ... import Column -from ... import MetaData -from ... import Table -from ...ext.compiler import compiles -from ...sql import expression -from ...types import Boolean -from ...types import Integer -from ...types import Numeric -from ...types import NVARCHAR -from ...types import String -from ...types import TypeDecorator -from ...types import Unicode - - -ischema = MetaData() - - -class CoerceUnicode(TypeDecorator): - impl = Unicode - cache_ok = True - - def bind_expression(self, bindvalue): - return _cast_on_2005(bindvalue) - - -class _cast_on_2005(expression.ColumnElement): - def __init__(self, bindvalue): - self.bindvalue = bindvalue - - -@compiles(_cast_on_2005) -def _compile(element, compiler, **kw): - from . import base - - if ( - compiler.dialect.server_version_info is None - or compiler.dialect.server_version_info < base.MS_2005_VERSION - ): - return compiler.process(element.bindvalue, **kw) - else: - return compiler.process(cast(element.bindvalue, Unicode), **kw) - - -schemata = Table( - "SCHEMATA", - ischema, - Column("CATALOG_NAME", CoerceUnicode, key="catalog_name"), - Column("SCHEMA_NAME", CoerceUnicode, key="schema_name"), - Column("SCHEMA_OWNER", CoerceUnicode, key="schema_owner"), - schema="INFORMATION_SCHEMA", -) - -tables = Table( - "TABLES", - ischema, - Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"), - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("TABLE_TYPE", CoerceUnicode, key="table_type"), - schema="INFORMATION_SCHEMA", -) - -columns = Table( - "COLUMNS", - ischema, - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("COLUMN_NAME", CoerceUnicode, key="column_name"), - Column("IS_NULLABLE", Integer, key="is_nullable"), - Column("DATA_TYPE", String, key="data_type"), - Column("ORDINAL_POSITION", Integer, key="ordinal_position"), - Column( - "CHARACTER_MAXIMUM_LENGTH", Integer, key="character_maximum_length" - ), - Column("NUMERIC_PRECISION", Integer, key="numeric_precision"), - Column("NUMERIC_SCALE", Integer, key="numeric_scale"), - Column("COLUMN_DEFAULT", Integer, key="column_default"), - Column("COLLATION_NAME", String, key="collation_name"), - schema="INFORMATION_SCHEMA", -) - -mssql_temp_table_columns = Table( - "COLUMNS", - ischema, - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("COLUMN_NAME", CoerceUnicode, key="column_name"), - Column("IS_NULLABLE", Integer, key="is_nullable"), - Column("DATA_TYPE", String, key="data_type"), - Column("ORDINAL_POSITION", Integer, key="ordinal_position"), - Column( - "CHARACTER_MAXIMUM_LENGTH", Integer, key="character_maximum_length" - ), - Column("NUMERIC_PRECISION", Integer, key="numeric_precision"), - Column("NUMERIC_SCALE", Integer, key="numeric_scale"), - Column("COLUMN_DEFAULT", Integer, key="column_default"), - Column("COLLATION_NAME", String, key="collation_name"), - schema="tempdb.INFORMATION_SCHEMA", -) - -constraints = Table( - "TABLE_CONSTRAINTS", - ischema, - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"), - Column("CONSTRAINT_TYPE", CoerceUnicode, key="constraint_type"), - schema="INFORMATION_SCHEMA", -) - -column_constraints = Table( - "CONSTRAINT_COLUMN_USAGE", - ischema, - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("COLUMN_NAME", CoerceUnicode, key="column_name"), - Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"), - schema="INFORMATION_SCHEMA", -) - -key_constraints = Table( - "KEY_COLUMN_USAGE", - ischema, - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("COLUMN_NAME", CoerceUnicode, key="column_name"), - Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"), - Column("CONSTRAINT_SCHEMA", CoerceUnicode, key="constraint_schema"), - Column("ORDINAL_POSITION", Integer, key="ordinal_position"), - schema="INFORMATION_SCHEMA", -) - -ref_constraints = Table( - "REFERENTIAL_CONSTRAINTS", - ischema, - Column("CONSTRAINT_CATALOG", CoerceUnicode, key="constraint_catalog"), - Column("CONSTRAINT_SCHEMA", CoerceUnicode, key="constraint_schema"), - Column("CONSTRAINT_NAME", CoerceUnicode, key="constraint_name"), - # TODO: is CATLOG misspelled ? - Column( - "UNIQUE_CONSTRAINT_CATLOG", - CoerceUnicode, - key="unique_constraint_catalog", - ), - Column( - "UNIQUE_CONSTRAINT_SCHEMA", - CoerceUnicode, - key="unique_constraint_schema", - ), - Column( - "UNIQUE_CONSTRAINT_NAME", CoerceUnicode, key="unique_constraint_name" - ), - Column("MATCH_OPTION", String, key="match_option"), - Column("UPDATE_RULE", String, key="update_rule"), - Column("DELETE_RULE", String, key="delete_rule"), - schema="INFORMATION_SCHEMA", -) - -views = Table( - "VIEWS", - ischema, - Column("TABLE_CATALOG", CoerceUnicode, key="table_catalog"), - Column("TABLE_SCHEMA", CoerceUnicode, key="table_schema"), - Column("TABLE_NAME", CoerceUnicode, key="table_name"), - Column("VIEW_DEFINITION", CoerceUnicode, key="view_definition"), - Column("CHECK_OPTION", String, key="check_option"), - Column("IS_UPDATABLE", String, key="is_updatable"), - schema="INFORMATION_SCHEMA", -) - -computed_columns = Table( - "computed_columns", - ischema, - Column("object_id", Integer), - Column("name", CoerceUnicode), - Column("is_computed", Boolean), - Column("is_persisted", Boolean), - Column("definition", CoerceUnicode), - schema="sys", -) - -sequences = Table( - "SEQUENCES", - ischema, - Column("SEQUENCE_CATALOG", CoerceUnicode, key="sequence_catalog"), - Column("SEQUENCE_SCHEMA", CoerceUnicode, key="sequence_schema"), - Column("SEQUENCE_NAME", CoerceUnicode, key="sequence_name"), - schema="INFORMATION_SCHEMA", -) - - -class NumericSqlVariant(TypeDecorator): - r"""This type casts sql_variant columns in the identity_columns view - to numeric. This is required because: - - * pyodbc does not support sql_variant - * pymssql under python 2 return the byte representation of the number, - int 1 is returned as "\x01\x00\x00\x00". On python 3 it returns the - correct value as string. - """ - - impl = Unicode - cache_ok = True - - def column_expression(self, colexpr): - return cast(colexpr, Numeric(38, 0)) - - -identity_columns = Table( - "identity_columns", - ischema, - Column("object_id", Integer), - Column("name", CoerceUnicode), - Column("is_identity", Boolean), - Column("seed_value", NumericSqlVariant), - Column("increment_value", NumericSqlVariant), - Column("last_value", NumericSqlVariant), - Column("is_not_for_replication", Boolean), - schema="sys", -) - - -class NVarcharSqlVariant(TypeDecorator): - """This type casts sql_variant columns in the extended_properties view - to nvarchar. This is required because pyodbc does not support sql_variant - """ - - impl = Unicode - cache_ok = True - - def column_expression(self, colexpr): - return cast(colexpr, NVARCHAR) - - -extended_properties = Table( - "extended_properties", - ischema, - Column("class", Integer), # TINYINT - Column("class_desc", CoerceUnicode), - Column("major_id", Integer), - Column("minor_id", Integer), - Column("name", CoerceUnicode), - Column("value", NVarcharSqlVariant), - schema="sys", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/json.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/json.py deleted file mode 100644 index 18bea09..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/json.py +++ /dev/null @@ -1,133 +0,0 @@ -# dialects/mssql/json.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import types as sqltypes - -# technically, all the dialect-specific datatypes that don't have any special -# behaviors would be private with names like _MSJson. However, we haven't been -# doing this for mysql.JSON or sqlite.JSON which both have JSON / JSONIndexType -# / JSONPathType in their json.py files, so keep consistent with that -# sub-convention for now. A future change can update them all to be -# package-private at once. - - -class JSON(sqltypes.JSON): - """MSSQL JSON type. - - MSSQL supports JSON-formatted data as of SQL Server 2016. - - The :class:`_mssql.JSON` datatype at the DDL level will represent the - datatype as ``NVARCHAR(max)``, but provides for JSON-level comparison - functions as well as Python coercion behavior. - - :class:`_mssql.JSON` is used automatically whenever the base - :class:`_types.JSON` datatype is used against a SQL Server backend. - - .. seealso:: - - :class:`_types.JSON` - main documentation for the generic - cross-platform JSON datatype. - - The :class:`_mssql.JSON` type supports persistence of JSON values - as well as the core index operations provided by :class:`_types.JSON` - datatype, by adapting the operations to render the ``JSON_VALUE`` - or ``JSON_QUERY`` functions at the database level. - - The SQL Server :class:`_mssql.JSON` type necessarily makes use of the - ``JSON_QUERY`` and ``JSON_VALUE`` functions when querying for elements - of a JSON object. These two functions have a major restriction in that - they are **mutually exclusive** based on the type of object to be returned. - The ``JSON_QUERY`` function **only** returns a JSON dictionary or list, - but not an individual string, numeric, or boolean element; the - ``JSON_VALUE`` function **only** returns an individual string, numeric, - or boolean element. **both functions either return NULL or raise - an error if they are not used against the correct expected value**. - - To handle this awkward requirement, indexed access rules are as follows: - - 1. When extracting a sub element from a JSON that is itself a JSON - dictionary or list, the :meth:`_types.JSON.Comparator.as_json` accessor - should be used:: - - stmt = select( - data_table.c.data["some key"].as_json() - ).where( - data_table.c.data["some key"].as_json() == {"sub": "structure"} - ) - - 2. When extracting a sub element from a JSON that is a plain boolean, - string, integer, or float, use the appropriate method among - :meth:`_types.JSON.Comparator.as_boolean`, - :meth:`_types.JSON.Comparator.as_string`, - :meth:`_types.JSON.Comparator.as_integer`, - :meth:`_types.JSON.Comparator.as_float`:: - - stmt = select( - data_table.c.data["some key"].as_string() - ).where( - data_table.c.data["some key"].as_string() == "some string" - ) - - .. versionadded:: 1.4 - - - """ - - # note there was a result processor here that was looking for "number", - # but none of the tests seem to exercise it. - - -# Note: these objects currently match exactly those of MySQL, however since -# these are not generalizable to all JSON implementations, remain separately -# implemented for each dialect. -class _FormatTypeMixin: - def _format_value(self, value): - raise NotImplementedError() - - def bind_processor(self, dialect): - super_proc = self.string_bind_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - def literal_processor(self, dialect): - super_proc = self.string_literal_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - -class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType): - def _format_value(self, value): - if isinstance(value, int): - value = "$[%s]" % value - else: - value = '$."%s"' % value - return value - - -class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType): - def _format_value(self, value): - return "$%s" % ( - "".join( - [ - "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem - for elem in value - ] - ) - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/provision.py deleted file mode 100644 index 143d386..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/provision.py +++ /dev/null @@ -1,155 +0,0 @@ -# dialects/mssql/provision.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from sqlalchemy import inspect -from sqlalchemy import Integer -from ... import create_engine -from ... import exc -from ...schema import Column -from ...schema import DropConstraint -from ...schema import ForeignKeyConstraint -from ...schema import MetaData -from ...schema import Table -from ...testing.provision import create_db -from ...testing.provision import drop_all_schema_objects_pre_tables -from ...testing.provision import drop_db -from ...testing.provision import generate_driver_url -from ...testing.provision import get_temp_table_name -from ...testing.provision import log -from ...testing.provision import normalize_sequence -from ...testing.provision import run_reap_dbs -from ...testing.provision import temp_table_keyword_args - - -@generate_driver_url.for_db("mssql") -def generate_driver_url(url, driver, query_str): - backend = url.get_backend_name() - - new_url = url.set(drivername="%s+%s" % (backend, driver)) - - if driver not in ("pyodbc", "aioodbc"): - new_url = new_url.set(query="") - - if driver == "aioodbc": - new_url = new_url.update_query_dict({"MARS_Connection": "Yes"}) - - if query_str: - new_url = new_url.update_query_string(query_str) - - try: - new_url.get_dialect() - except exc.NoSuchModuleError: - return None - else: - return new_url - - -@create_db.for_db("mssql") -def _mssql_create_db(cfg, eng, ident): - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - conn.exec_driver_sql("create database %s" % ident) - conn.exec_driver_sql( - "ALTER DATABASE %s SET ALLOW_SNAPSHOT_ISOLATION ON" % ident - ) - conn.exec_driver_sql( - "ALTER DATABASE %s SET READ_COMMITTED_SNAPSHOT ON" % ident - ) - conn.exec_driver_sql("use %s" % ident) - conn.exec_driver_sql("create schema test_schema") - conn.exec_driver_sql("create schema test_schema_2") - - -@drop_db.for_db("mssql") -def _mssql_drop_db(cfg, eng, ident): - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - _mssql_drop_ignore(conn, ident) - - -def _mssql_drop_ignore(conn, ident): - try: - # typically when this happens, we can't KILL the session anyway, - # so let the cleanup process drop the DBs - # for row in conn.exec_driver_sql( - # "select session_id from sys.dm_exec_sessions " - # "where database_id=db_id('%s')" % ident): - # log.info("killing SQL server session %s", row['session_id']) - # conn.exec_driver_sql("kill %s" % row['session_id']) - conn.exec_driver_sql("drop database %s" % ident) - log.info("Reaped db: %s", ident) - return True - except exc.DatabaseError as err: - log.warning("couldn't drop db: %s", err) - return False - - -@run_reap_dbs.for_db("mssql") -def _reap_mssql_dbs(url, idents): - log.info("db reaper connecting to %r", url) - eng = create_engine(url) - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - log.info("identifiers in file: %s", ", ".join(idents)) - - to_reap = conn.exec_driver_sql( - "select d.name from sys.databases as d where name " - "like 'TEST_%' and not exists (select session_id " - "from sys.dm_exec_sessions " - "where database_id=d.database_id)" - ) - all_names = {dbname.lower() for (dbname,) in to_reap} - to_drop = set() - for name in all_names: - if name in idents: - to_drop.add(name) - - dropped = total = 0 - for total, dbname in enumerate(to_drop, 1): - if _mssql_drop_ignore(conn, dbname): - dropped += 1 - log.info( - "Dropped %d out of %d stale databases detected", dropped, total - ) - - -@temp_table_keyword_args.for_db("mssql") -def _mssql_temp_table_keyword_args(cfg, eng): - return {} - - -@get_temp_table_name.for_db("mssql") -def _mssql_get_temp_table_name(cfg, eng, base_name): - return "##" + base_name - - -@drop_all_schema_objects_pre_tables.for_db("mssql") -def drop_all_schema_objects_pre_tables(cfg, eng): - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - inspector = inspect(conn) - for schema in (None, "dbo", cfg.test_schema, cfg.test_schema_2): - for tname in inspector.get_table_names(schema=schema): - tb = Table( - tname, - MetaData(), - Column("x", Integer), - Column("y", Integer), - schema=schema, - ) - for fk in inspect(conn).get_foreign_keys(tname, schema=schema): - conn.execute( - DropConstraint( - ForeignKeyConstraint( - [tb.c.x], [tb.c.y], name=fk["name"] - ) - ) - ) - - -@normalize_sequence.for_db("mssql") -def normalize_sequence(cfg, sequence): - if sequence.start is None: - sequence.start = 1 - return sequence diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pymssql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pymssql.py deleted file mode 100644 index ea1f9bd..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pymssql.py +++ /dev/null @@ -1,125 +0,0 @@ -# dialects/mssql/pymssql.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -""" -.. dialect:: mssql+pymssql - :name: pymssql - :dbapi: pymssql - :connectstring: mssql+pymssql://:@/?charset=utf8 - -pymssql is a Python module that provides a Python DBAPI interface around -`FreeTDS `_. - -.. versionchanged:: 2.0.5 - - pymssql was restored to SQLAlchemy's continuous integration testing - - -""" # noqa -import re - -from .base import MSDialect -from .base import MSIdentifierPreparer -from ... import types as sqltypes -from ... import util -from ...engine import processors - - -class _MSNumeric_pymssql(sqltypes.Numeric): - def result_processor(self, dialect, type_): - if not self.asdecimal: - return processors.to_float - else: - return sqltypes.Numeric.result_processor(self, dialect, type_) - - -class MSIdentifierPreparer_pymssql(MSIdentifierPreparer): - def __init__(self, dialect): - super().__init__(dialect) - # pymssql has the very unusual behavior that it uses pyformat - # yet does not require that percent signs be doubled - self._double_percents = False - - -class MSDialect_pymssql(MSDialect): - supports_statement_cache = True - supports_native_decimal = True - supports_native_uuid = True - driver = "pymssql" - - preparer = MSIdentifierPreparer_pymssql - - colspecs = util.update_copy( - MSDialect.colspecs, - {sqltypes.Numeric: _MSNumeric_pymssql, sqltypes.Float: sqltypes.Float}, - ) - - @classmethod - def import_dbapi(cls): - module = __import__("pymssql") - # pymmsql < 2.1.1 doesn't have a Binary method. we use string - client_ver = tuple(int(x) for x in module.__version__.split(".")) - if client_ver < (2, 1, 1): - # TODO: monkeypatching here is less than ideal - module.Binary = lambda x: x if hasattr(x, "decode") else str(x) - - if client_ver < (1,): - util.warn( - "The pymssql dialect expects at least " - "the 1.0 series of the pymssql DBAPI." - ) - return module - - def _get_server_version_info(self, connection): - vers = connection.exec_driver_sql("select @@version").scalar() - m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers) - if m: - return tuple(int(x) for x in m.group(1, 2, 3, 4)) - else: - return None - - def create_connect_args(self, url): - opts = url.translate_connect_args(username="user") - opts.update(url.query) - port = opts.pop("port", None) - if port and "host" in opts: - opts["host"] = "%s:%s" % (opts["host"], port) - return ([], opts) - - def is_disconnect(self, e, connection, cursor): - for msg in ( - "Adaptive Server connection timed out", - "Net-Lib error during Connection reset by peer", - "message 20003", # connection timeout - "Error 10054", - "Not connected to any MS SQL server", - "Connection is closed", - "message 20006", # Write to the server failed - "message 20017", # Unexpected EOF from the server - "message 20047", # DBPROCESS is dead or not enabled - ): - if msg in str(e): - return True - else: - return False - - def get_isolation_level_values(self, dbapi_connection): - return super().get_isolation_level_values(dbapi_connection) + [ - "AUTOCOMMIT" - ] - - def set_isolation_level(self, dbapi_connection, level): - if level == "AUTOCOMMIT": - dbapi_connection.autocommit(True) - else: - dbapi_connection.autocommit(False) - super().set_isolation_level(dbapi_connection, level) - - -dialect = MSDialect_pymssql diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pyodbc.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pyodbc.py deleted file mode 100644 index 76ea046..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mssql/pyodbc.py +++ /dev/null @@ -1,745 +0,0 @@ -# dialects/mssql/pyodbc.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: mssql+pyodbc - :name: PyODBC - :dbapi: pyodbc - :connectstring: mssql+pyodbc://:@ - :url: https://pypi.org/project/pyodbc/ - -Connecting to PyODBC --------------------- - -The URL here is to be translated to PyODBC connection strings, as -detailed in `ConnectionStrings `_. - -DSN Connections -^^^^^^^^^^^^^^^ - -A DSN connection in ODBC means that a pre-existing ODBC datasource is -configured on the client machine. The application then specifies the name -of this datasource, which encompasses details such as the specific ODBC driver -in use as well as the network address of the database. Assuming a datasource -is configured on the client, a basic DSN-based connection looks like:: - - engine = create_engine("mssql+pyodbc://scott:tiger@some_dsn") - -Which above, will pass the following connection string to PyODBC:: - - DSN=some_dsn;UID=scott;PWD=tiger - -If the username and password are omitted, the DSN form will also add -the ``Trusted_Connection=yes`` directive to the ODBC string. - -Hostname Connections -^^^^^^^^^^^^^^^^^^^^ - -Hostname-based connections are also supported by pyodbc. These are often -easier to use than a DSN and have the additional advantage that the specific -database name to connect towards may be specified locally in the URL, rather -than it being fixed as part of a datasource configuration. - -When using a hostname connection, the driver name must also be specified in the -query parameters of the URL. As these names usually have spaces in them, the -name must be URL encoded which means using plus signs for spaces:: - - engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=ODBC+Driver+17+for+SQL+Server") - -The ``driver`` keyword is significant to the pyodbc dialect and must be -specified in lowercase. - -Any other names passed in the query string are passed through in the pyodbc -connect string, such as ``authentication``, ``TrustServerCertificate``, etc. -Multiple keyword arguments must be separated by an ampersand (``&``); these -will be translated to semicolons when the pyodbc connect string is generated -internally:: - - e = create_engine( - "mssql+pyodbc://scott:tiger@mssql2017:1433/test?" - "driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes" - "&authentication=ActiveDirectoryIntegrated" - ) - -The equivalent URL can be constructed using :class:`_sa.engine.URL`:: - - from sqlalchemy.engine import URL - connection_url = URL.create( - "mssql+pyodbc", - username="scott", - password="tiger", - host="mssql2017", - port=1433, - database="test", - query={ - "driver": "ODBC Driver 18 for SQL Server", - "TrustServerCertificate": "yes", - "authentication": "ActiveDirectoryIntegrated", - }, - ) - - -Pass through exact Pyodbc string -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A PyODBC connection string can also be sent in pyodbc's format directly, as -specified in `the PyODBC documentation -`_, -using the parameter ``odbc_connect``. A :class:`_sa.engine.URL` object -can help make this easier:: - - from sqlalchemy.engine import URL - connection_string = "DRIVER={SQL Server Native Client 10.0};SERVER=dagger;DATABASE=test;UID=user;PWD=password" - connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string}) - - engine = create_engine(connection_url) - -.. _mssql_pyodbc_access_tokens: - -Connecting to databases with access tokens -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Some database servers are set up to only accept access tokens for login. For -example, SQL Server allows the use of Azure Active Directory tokens to connect -to databases. This requires creating a credential object using the -``azure-identity`` library. More information about the authentication step can be -found in `Microsoft's documentation -`_. - -After getting an engine, the credentials need to be sent to ``pyodbc.connect`` -each time a connection is requested. One way to do this is to set up an event -listener on the engine that adds the credential token to the dialect's connect -call. This is discussed more generally in :ref:`engines_dynamic_tokens`. For -SQL Server in particular, this is passed as an ODBC connection attribute with -a data structure `described by Microsoft -`_. - -The following code snippet will create an engine that connects to an Azure SQL -database using Azure credentials:: - - import struct - from sqlalchemy import create_engine, event - from sqlalchemy.engine.url import URL - from azure import identity - - SQL_COPT_SS_ACCESS_TOKEN = 1256 # Connection option for access tokens, as defined in msodbcsql.h - TOKEN_URL = "https://database.windows.net/" # The token URL for any Azure SQL database - - connection_string = "mssql+pyodbc://@my-server.database.windows.net/myDb?driver=ODBC+Driver+17+for+SQL+Server" - - engine = create_engine(connection_string) - - azure_credentials = identity.DefaultAzureCredential() - - @event.listens_for(engine, "do_connect") - def provide_token(dialect, conn_rec, cargs, cparams): - # remove the "Trusted_Connection" parameter that SQLAlchemy adds - cargs[0] = cargs[0].replace(";Trusted_Connection=Yes", "") - - # create token credential - raw_token = azure_credentials.get_token(TOKEN_URL).token.encode("utf-16-le") - token_struct = struct.pack(f"`_, - stating that a connection string when using an access token must not contain - ``UID``, ``PWD``, ``Authentication`` or ``Trusted_Connection`` parameters. - -.. _azure_synapse_ignore_no_transaction_on_rollback: - -Avoiding transaction-related exceptions on Azure Synapse Analytics -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Azure Synapse Analytics has a significant difference in its transaction -handling compared to plain SQL Server; in some cases an error within a Synapse -transaction can cause it to be arbitrarily terminated on the server side, which -then causes the DBAPI ``.rollback()`` method (as well as ``.commit()``) to -fail. The issue prevents the usual DBAPI contract of allowing ``.rollback()`` -to pass silently if no transaction is present as the driver does not expect -this condition. The symptom of this failure is an exception with a message -resembling 'No corresponding transaction found. (111214)' when attempting to -emit a ``.rollback()`` after an operation had a failure of some kind. - -This specific case can be handled by passing ``ignore_no_transaction_on_rollback=True`` to -the SQL Server dialect via the :func:`_sa.create_engine` function as follows:: - - engine = create_engine(connection_url, ignore_no_transaction_on_rollback=True) - -Using the above parameter, the dialect will catch ``ProgrammingError`` -exceptions raised during ``connection.rollback()`` and emit a warning -if the error message contains code ``111214``, however will not raise -an exception. - -.. versionadded:: 1.4.40 Added the - ``ignore_no_transaction_on_rollback=True`` parameter. - -Enable autocommit for Azure SQL Data Warehouse (DW) connections -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Azure SQL Data Warehouse does not support transactions, -and that can cause problems with SQLAlchemy's "autobegin" (and implicit -commit/rollback) behavior. We can avoid these problems by enabling autocommit -at both the pyodbc and engine levels:: - - connection_url = sa.engine.URL.create( - "mssql+pyodbc", - username="scott", - password="tiger", - host="dw.azure.example.com", - database="mydb", - query={ - "driver": "ODBC Driver 17 for SQL Server", - "autocommit": "True", - }, - ) - - engine = create_engine(connection_url).execution_options( - isolation_level="AUTOCOMMIT" - ) - -Avoiding sending large string parameters as TEXT/NTEXT -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -By default, for historical reasons, Microsoft's ODBC drivers for SQL Server -send long string parameters (greater than 4000 SBCS characters or 2000 Unicode -characters) as TEXT/NTEXT values. TEXT and NTEXT have been deprecated for many -years and are starting to cause compatibility issues with newer versions of -SQL_Server/Azure. For example, see `this -issue `_. - -Starting with ODBC Driver 18 for SQL Server we can override the legacy -behavior and pass long strings as varchar(max)/nvarchar(max) using the -``LongAsMax=Yes`` connection string parameter:: - - connection_url = sa.engine.URL.create( - "mssql+pyodbc", - username="scott", - password="tiger", - host="mssqlserver.example.com", - database="mydb", - query={ - "driver": "ODBC Driver 18 for SQL Server", - "LongAsMax": "Yes", - }, - ) - - -Pyodbc Pooling / connection close behavior ------------------------------------------- - -PyODBC uses internal `pooling -`_ by -default, which means connections will be longer lived than they are within -SQLAlchemy itself. As SQLAlchemy has its own pooling behavior, it is often -preferable to disable this behavior. This behavior can only be disabled -globally at the PyODBC module level, **before** any connections are made:: - - import pyodbc - - pyodbc.pooling = False - - # don't use the engine before pooling is set to False - engine = create_engine("mssql+pyodbc://user:pass@dsn") - -If this variable is left at its default value of ``True``, **the application -will continue to maintain active database connections**, even when the -SQLAlchemy engine itself fully discards a connection or if the engine is -disposed. - -.. seealso:: - - `pooling `_ - - in the PyODBC documentation. - -Driver / Unicode Support -------------------------- - -PyODBC works best with Microsoft ODBC drivers, particularly in the area -of Unicode support on both Python 2 and Python 3. - -Using the FreeTDS ODBC drivers on Linux or OSX with PyODBC is **not** -recommended; there have been historically many Unicode-related issues -in this area, including before Microsoft offered ODBC drivers for Linux -and OSX. Now that Microsoft offers drivers for all platforms, for -PyODBC support these are recommended. FreeTDS remains relevant for -non-ODBC drivers such as pymssql where it works very well. - - -Rowcount Support ----------------- - -Previous limitations with the SQLAlchemy ORM's "versioned rows" feature with -Pyodbc have been resolved as of SQLAlchemy 2.0.5. See the notes at -:ref:`mssql_rowcount_versioning`. - -.. _mssql_pyodbc_fastexecutemany: - -Fast Executemany Mode ---------------------- - -The PyODBC driver includes support for a "fast executemany" mode of execution -which greatly reduces round trips for a DBAPI ``executemany()`` call when using -Microsoft ODBC drivers, for **limited size batches that fit in memory**. The -feature is enabled by setting the attribute ``.fast_executemany`` on the DBAPI -cursor when an executemany call is to be used. The SQLAlchemy PyODBC SQL -Server dialect supports this parameter by passing the -``fast_executemany`` parameter to -:func:`_sa.create_engine` , when using the **Microsoft ODBC driver only**:: - - engine = create_engine( - "mssql+pyodbc://scott:tiger@mssql2017:1433/test?driver=ODBC+Driver+17+for+SQL+Server", - fast_executemany=True) - -.. versionchanged:: 2.0.9 - the ``fast_executemany`` parameter now has its - intended effect of this PyODBC feature taking effect for all INSERT - statements that are executed with multiple parameter sets, which don't - include RETURNING. Previously, SQLAlchemy 2.0's :term:`insertmanyvalues` - feature would cause ``fast_executemany`` to not be used in most cases - even if specified. - -.. versionadded:: 1.3 - -.. seealso:: - - `fast executemany `_ - - on github - -.. _mssql_pyodbc_setinputsizes: - -Setinputsizes Support ------------------------ - -As of version 2.0, the pyodbc ``cursor.setinputsizes()`` method is used for -all statement executions, except for ``cursor.executemany()`` calls when -fast_executemany=True where it is not supported (assuming -:ref:`insertmanyvalues ` is kept enabled, -"fastexecutemany" will not take place for INSERT statements in any case). - -The use of ``cursor.setinputsizes()`` can be disabled by passing -``use_setinputsizes=False`` to :func:`_sa.create_engine`. - -When ``use_setinputsizes`` is left at its default of ``True``, the -specific per-type symbols passed to ``cursor.setinputsizes()`` can be -programmatically customized using the :meth:`.DialectEvents.do_setinputsizes` -hook. See that method for usage examples. - -.. versionchanged:: 2.0 The mssql+pyodbc dialect now defaults to using - ``use_setinputsizes=True`` for all statement executions with the exception of - cursor.executemany() calls when fast_executemany=True. The behavior can - be turned off by passing ``use_setinputsizes=False`` to - :func:`_sa.create_engine`. - -""" # noqa - - -import datetime -import decimal -import re -import struct - -from .base import _MSDateTime -from .base import _MSUnicode -from .base import _MSUnicodeText -from .base import BINARY -from .base import DATETIMEOFFSET -from .base import MSDialect -from .base import MSExecutionContext -from .base import VARBINARY -from .json import JSON as _MSJson -from .json import JSONIndexType as _MSJsonIndexType -from .json import JSONPathType as _MSJsonPathType -from ... import exc -from ... import types as sqltypes -from ... import util -from ...connectors.pyodbc import PyODBCConnector -from ...engine import cursor as _cursor - - -class _ms_numeric_pyodbc: - """Turns Decimals with adjusted() < 0 or > 7 into strings. - - The routines here are needed for older pyodbc versions - as well as current mxODBC versions. - - """ - - def bind_processor(self, dialect): - super_process = super().bind_processor(dialect) - - if not dialect._need_decimal_fix: - return super_process - - def process(value): - if self.asdecimal and isinstance(value, decimal.Decimal): - adjusted = value.adjusted() - if adjusted < 0: - return self._small_dec_to_string(value) - elif adjusted > 7: - return self._large_dec_to_string(value) - - if super_process: - return super_process(value) - else: - return value - - return process - - # these routines needed for older versions of pyodbc. - # as of 2.1.8 this logic is integrated. - - def _small_dec_to_string(self, value): - return "%s0.%s%s" % ( - (value < 0 and "-" or ""), - "0" * (abs(value.adjusted()) - 1), - "".join([str(nint) for nint in value.as_tuple()[1]]), - ) - - def _large_dec_to_string(self, value): - _int = value.as_tuple()[1] - if "E" in str(value): - result = "%s%s%s" % ( - (value < 0 and "-" or ""), - "".join([str(s) for s in _int]), - "0" * (value.adjusted() - (len(_int) - 1)), - ) - else: - if (len(_int) - 1) > value.adjusted(): - result = "%s%s.%s" % ( - (value < 0 and "-" or ""), - "".join([str(s) for s in _int][0 : value.adjusted() + 1]), - "".join([str(s) for s in _int][value.adjusted() + 1 :]), - ) - else: - result = "%s%s" % ( - (value < 0 and "-" or ""), - "".join([str(s) for s in _int][0 : value.adjusted() + 1]), - ) - return result - - -class _MSNumeric_pyodbc(_ms_numeric_pyodbc, sqltypes.Numeric): - pass - - -class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float): - pass - - -class _ms_binary_pyodbc: - """Wraps binary values in dialect-specific Binary wrapper. - If the value is null, return a pyodbc-specific BinaryNull - object to prevent pyODBC [and FreeTDS] from defaulting binary - NULL types to SQLWCHAR and causing implicit conversion errors. - """ - - def bind_processor(self, dialect): - if dialect.dbapi is None: - return None - - DBAPIBinary = dialect.dbapi.Binary - - def process(value): - if value is not None: - return DBAPIBinary(value) - else: - # pyodbc-specific - return dialect.dbapi.BinaryNull - - return process - - -class _ODBCDateTimeBindProcessor: - """Add bind processors to handle datetimeoffset behaviors""" - - has_tz = False - - def bind_processor(self, dialect): - def process(value): - if value is None: - return None - elif isinstance(value, str): - # if a string was passed directly, allow it through - return value - elif not value.tzinfo or (not self.timezone and not self.has_tz): - # for DateTime(timezone=False) - return value - else: - # for DATETIMEOFFSET or DateTime(timezone=True) - # - # Convert to string format required by T-SQL - dto_string = value.strftime("%Y-%m-%d %H:%M:%S.%f %z") - # offset needs a colon, e.g., -0700 -> -07:00 - # "UTC offset in the form (+-)HHMM[SS[.ffffff]]" - # backend currently rejects seconds / fractional seconds - dto_string = re.sub( - r"([\+\-]\d{2})([\d\.]+)$", r"\1:\2", dto_string - ) - return dto_string - - return process - - -class _ODBCDateTime(_ODBCDateTimeBindProcessor, _MSDateTime): - pass - - -class _ODBCDATETIMEOFFSET(_ODBCDateTimeBindProcessor, DATETIMEOFFSET): - has_tz = True - - -class _VARBINARY_pyodbc(_ms_binary_pyodbc, VARBINARY): - pass - - -class _BINARY_pyodbc(_ms_binary_pyodbc, BINARY): - pass - - -class _String_pyodbc(sqltypes.String): - def get_dbapi_type(self, dbapi): - if self.length in (None, "max") or self.length >= 2000: - return (dbapi.SQL_VARCHAR, 0, 0) - else: - return dbapi.SQL_VARCHAR - - -class _Unicode_pyodbc(_MSUnicode): - def get_dbapi_type(self, dbapi): - if self.length in (None, "max") or self.length >= 2000: - return (dbapi.SQL_WVARCHAR, 0, 0) - else: - return dbapi.SQL_WVARCHAR - - -class _UnicodeText_pyodbc(_MSUnicodeText): - def get_dbapi_type(self, dbapi): - if self.length in (None, "max") or self.length >= 2000: - return (dbapi.SQL_WVARCHAR, 0, 0) - else: - return dbapi.SQL_WVARCHAR - - -class _JSON_pyodbc(_MSJson): - def get_dbapi_type(self, dbapi): - return (dbapi.SQL_WVARCHAR, 0, 0) - - -class _JSONIndexType_pyodbc(_MSJsonIndexType): - def get_dbapi_type(self, dbapi): - return dbapi.SQL_WVARCHAR - - -class _JSONPathType_pyodbc(_MSJsonPathType): - def get_dbapi_type(self, dbapi): - return dbapi.SQL_WVARCHAR - - -class MSExecutionContext_pyodbc(MSExecutionContext): - _embedded_scope_identity = False - - def pre_exec(self): - """where appropriate, issue "select scope_identity()" in the same - statement. - - Background on why "scope_identity()" is preferable to "@@identity": - https://msdn.microsoft.com/en-us/library/ms190315.aspx - - Background on why we attempt to embed "scope_identity()" into the same - statement as the INSERT: - https://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values? - - """ - - super().pre_exec() - - # don't embed the scope_identity select into an - # "INSERT .. DEFAULT VALUES" - if ( - self._select_lastrowid - and self.dialect.use_scope_identity - and len(self.parameters[0]) - ): - self._embedded_scope_identity = True - - self.statement += "; select scope_identity()" - - def post_exec(self): - if self._embedded_scope_identity: - # Fetch the last inserted id from the manipulated statement - # We may have to skip over a number of result sets with - # no data (due to triggers, etc.) - while True: - try: - # fetchall() ensures the cursor is consumed - # without closing it (FreeTDS particularly) - rows = self.cursor.fetchall() - except self.dialect.dbapi.Error: - # no way around this - nextset() consumes the previous set - # so we need to just keep flipping - self.cursor.nextset() - else: - if not rows: - # async adapter drivers just return None here - self.cursor.nextset() - continue - row = rows[0] - break - - self._lastrowid = int(row[0]) - - self.cursor_fetch_strategy = _cursor._NO_CURSOR_DML - else: - super().post_exec() - - -class MSDialect_pyodbc(PyODBCConnector, MSDialect): - supports_statement_cache = True - - # note this parameter is no longer used by the ORM or default dialect - # see #9414 - supports_sane_rowcount_returning = False - - execution_ctx_cls = MSExecutionContext_pyodbc - - colspecs = util.update_copy( - MSDialect.colspecs, - { - sqltypes.Numeric: _MSNumeric_pyodbc, - sqltypes.Float: _MSFloat_pyodbc, - BINARY: _BINARY_pyodbc, - # support DateTime(timezone=True) - sqltypes.DateTime: _ODBCDateTime, - DATETIMEOFFSET: _ODBCDATETIMEOFFSET, - # SQL Server dialect has a VARBINARY that is just to support - # "deprecate_large_types" w/ VARBINARY(max), but also we must - # handle the usual SQL standard VARBINARY - VARBINARY: _VARBINARY_pyodbc, - sqltypes.VARBINARY: _VARBINARY_pyodbc, - sqltypes.LargeBinary: _VARBINARY_pyodbc, - sqltypes.String: _String_pyodbc, - sqltypes.Unicode: _Unicode_pyodbc, - sqltypes.UnicodeText: _UnicodeText_pyodbc, - sqltypes.JSON: _JSON_pyodbc, - sqltypes.JSON.JSONIndexType: _JSONIndexType_pyodbc, - sqltypes.JSON.JSONPathType: _JSONPathType_pyodbc, - # this excludes Enum from the string/VARCHAR thing for now - # it looks like Enum's adaptation doesn't really support the - # String type itself having a dialect-level impl - sqltypes.Enum: sqltypes.Enum, - }, - ) - - def __init__( - self, - fast_executemany=False, - use_setinputsizes=True, - **params, - ): - super().__init__(use_setinputsizes=use_setinputsizes, **params) - self.use_scope_identity = ( - self.use_scope_identity - and self.dbapi - and hasattr(self.dbapi.Cursor, "nextset") - ) - self._need_decimal_fix = self.dbapi and self._dbapi_version() < ( - 2, - 1, - 8, - ) - self.fast_executemany = fast_executemany - if fast_executemany: - self.use_insertmanyvalues_wo_returning = False - - def _get_server_version_info(self, connection): - try: - # "Version of the instance of SQL Server, in the form - # of 'major.minor.build.revision'" - raw = connection.exec_driver_sql( - "SELECT CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR)" - ).scalar() - except exc.DBAPIError: - # SQL Server docs indicate this function isn't present prior to - # 2008. Before we had the VARCHAR cast above, pyodbc would also - # fail on this query. - return super()._get_server_version_info(connection) - else: - version = [] - r = re.compile(r"[.\-]") - for n in r.split(raw): - try: - version.append(int(n)) - except ValueError: - pass - return tuple(version) - - def on_connect(self): - super_ = super().on_connect() - - def on_connect(conn): - if super_ is not None: - super_(conn) - - self._setup_timestampoffset_type(conn) - - return on_connect - - def _setup_timestampoffset_type(self, connection): - # output converter function for datetimeoffset - def _handle_datetimeoffset(dto_value): - tup = struct.unpack("<6hI2h", dto_value) - return datetime.datetime( - tup[0], - tup[1], - tup[2], - tup[3], - tup[4], - tup[5], - tup[6] // 1000, - datetime.timezone( - datetime.timedelta(hours=tup[7], minutes=tup[8]) - ), - ) - - odbc_SQL_SS_TIMESTAMPOFFSET = -155 # as defined in SQLNCLI.h - connection.add_output_converter( - odbc_SQL_SS_TIMESTAMPOFFSET, _handle_datetimeoffset - ) - - def do_executemany(self, cursor, statement, parameters, context=None): - if self.fast_executemany: - cursor.fast_executemany = True - super().do_executemany(cursor, statement, parameters, context=context) - - def is_disconnect(self, e, connection, cursor): - if isinstance(e, self.dbapi.Error): - code = e.args[0] - if code in { - "08S01", - "01000", - "01002", - "08003", - "08007", - "08S02", - "08001", - "HYT00", - "HY010", - "10054", - }: - return True - return super().is_disconnect(e, connection, cursor) - - -dialect = MSDialect_pyodbc diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py deleted file mode 100644 index 60bac87..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__init__.py +++ /dev/null @@ -1,101 +0,0 @@ -# dialects/mysql/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -from . import aiomysql # noqa -from . import asyncmy # noqa -from . import base # noqa -from . import cymysql # noqa -from . import mariadbconnector # noqa -from . import mysqlconnector # noqa -from . import mysqldb # noqa -from . import pymysql # noqa -from . import pyodbc # noqa -from .base import BIGINT -from .base import BINARY -from .base import BIT -from .base import BLOB -from .base import BOOLEAN -from .base import CHAR -from .base import DATE -from .base import DATETIME -from .base import DECIMAL -from .base import DOUBLE -from .base import ENUM -from .base import FLOAT -from .base import INTEGER -from .base import JSON -from .base import LONGBLOB -from .base import LONGTEXT -from .base import MEDIUMBLOB -from .base import MEDIUMINT -from .base import MEDIUMTEXT -from .base import NCHAR -from .base import NUMERIC -from .base import NVARCHAR -from .base import REAL -from .base import SET -from .base import SMALLINT -from .base import TEXT -from .base import TIME -from .base import TIMESTAMP -from .base import TINYBLOB -from .base import TINYINT -from .base import TINYTEXT -from .base import VARBINARY -from .base import VARCHAR -from .base import YEAR -from .dml import Insert -from .dml import insert -from .expression import match -from ...util import compat - -# default dialect -base.dialect = dialect = mysqldb.dialect - -__all__ = ( - "BIGINT", - "BINARY", - "BIT", - "BLOB", - "BOOLEAN", - "CHAR", - "DATE", - "DATETIME", - "DECIMAL", - "DOUBLE", - "ENUM", - "FLOAT", - "INTEGER", - "INTEGER", - "JSON", - "LONGBLOB", - "LONGTEXT", - "MEDIUMBLOB", - "MEDIUMINT", - "MEDIUMTEXT", - "NCHAR", - "NVARCHAR", - "NUMERIC", - "SET", - "SMALLINT", - "REAL", - "TEXT", - "TIME", - "TIMESTAMP", - "TINYBLOB", - "TINYINT", - "TINYTEXT", - "VARBINARY", - "VARCHAR", - "YEAR", - "dialect", - "insert", - "Insert", - "match", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 2a39bd6..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc deleted file mode 100644 index cd8e408..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc deleted file mode 100644 index 1c1eb90..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc deleted file mode 100644 index e26259b..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc deleted file mode 100644 index e08de25..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc deleted file mode 100644 index 87e30ca..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc deleted file mode 100644 index 2df5629..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc deleted file mode 100644 index c3fd7e9..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc deleted file mode 100644 index 417677b..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc deleted file mode 100644 index 79ef157..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc deleted file mode 100644 index a01ff5f..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc deleted file mode 100644 index d7a6a76..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc deleted file mode 100644 index d572a66..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index 3865a06..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc deleted file mode 100644 index bc40e52..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc deleted file mode 100644 index 07a2fcc..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc deleted file mode 100644 index 0b1bc4c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc deleted file mode 100644 index 1ef118c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc deleted file mode 100644 index 6d20ed0..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py deleted file mode 100644 index 405fa82..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/aiomysql.py +++ /dev/null @@ -1,332 +0,0 @@ -# dialects/mysql/aiomysql.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: mysql+aiomysql - :name: aiomysql - :dbapi: aiomysql - :connectstring: mysql+aiomysql://user:password@host:port/dbname[?key=value&key=value...] - :url: https://github.com/aio-libs/aiomysql - -The aiomysql dialect is SQLAlchemy's second Python asyncio dialect. - -Using a special asyncio mediation layer, the aiomysql dialect is usable -as the backend for the :ref:`SQLAlchemy asyncio ` -extension package. - -This dialect should normally be used only with the -:func:`_asyncio.create_async_engine` engine creation function:: - - from sqlalchemy.ext.asyncio import create_async_engine - engine = create_async_engine("mysql+aiomysql://user:pass@hostname/dbname?charset=utf8mb4") - - -""" # noqa -from .pymysql import MySQLDialect_pymysql -from ... import pool -from ... import util -from ...engine import AdaptedConnection -from ...util.concurrency import asyncio -from ...util.concurrency import await_fallback -from ...util.concurrency import await_only - - -class AsyncAdapt_aiomysql_cursor: - # TODO: base on connectors/asyncio.py - # see #10415 - server_side = False - __slots__ = ( - "_adapt_connection", - "_connection", - "await_", - "_cursor", - "_rows", - ) - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self.await_ = adapt_connection.await_ - - cursor = self._connection.cursor(adapt_connection.dbapi.Cursor) - - # see https://github.com/aio-libs/aiomysql/issues/543 - self._cursor = self.await_(cursor.__aenter__()) - self._rows = [] - - @property - def description(self): - return self._cursor.description - - @property - def rowcount(self): - return self._cursor.rowcount - - @property - def arraysize(self): - return self._cursor.arraysize - - @arraysize.setter - def arraysize(self, value): - self._cursor.arraysize = value - - @property - def lastrowid(self): - return self._cursor.lastrowid - - def close(self): - # note we aren't actually closing the cursor here, - # we are just letting GC do it. to allow this to be async - # we would need the Result to change how it does "Safe close cursor". - # MySQL "cursors" don't actually have state to be "closed" besides - # exhausting rows, which we already have done for sync cursor. - # another option would be to emulate aiosqlite dialect and assign - # cursor only if we are doing server side cursor operation. - self._rows[:] = [] - - def execute(self, operation, parameters=None): - return self.await_(self._execute_async(operation, parameters)) - - def executemany(self, operation, seq_of_parameters): - return self.await_( - self._executemany_async(operation, seq_of_parameters) - ) - - async def _execute_async(self, operation, parameters): - async with self._adapt_connection._execute_mutex: - result = await self._cursor.execute(operation, parameters) - - if not self.server_side: - # aiomysql has a "fake" async result, so we have to pull it out - # of that here since our default result is not async. - # we could just as easily grab "_rows" here and be done with it - # but this is safer. - self._rows = list(await self._cursor.fetchall()) - return result - - async def _executemany_async(self, operation, seq_of_parameters): - async with self._adapt_connection._execute_mutex: - return await self._cursor.executemany(operation, seq_of_parameters) - - def setinputsizes(self, *inputsizes): - pass - - def __iter__(self): - while self._rows: - yield self._rows.pop(0) - - def fetchone(self): - if self._rows: - return self._rows.pop(0) - else: - return None - - def fetchmany(self, size=None): - if size is None: - size = self.arraysize - - retval = self._rows[0:size] - self._rows[:] = self._rows[size:] - return retval - - def fetchall(self): - retval = self._rows[:] - self._rows[:] = [] - return retval - - -class AsyncAdapt_aiomysql_ss_cursor(AsyncAdapt_aiomysql_cursor): - # TODO: base on connectors/asyncio.py - # see #10415 - __slots__ = () - server_side = True - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self.await_ = adapt_connection.await_ - - cursor = self._connection.cursor(adapt_connection.dbapi.SSCursor) - - self._cursor = self.await_(cursor.__aenter__()) - - def close(self): - if self._cursor is not None: - self.await_(self._cursor.close()) - self._cursor = None - - def fetchone(self): - return self.await_(self._cursor.fetchone()) - - def fetchmany(self, size=None): - return self.await_(self._cursor.fetchmany(size=size)) - - def fetchall(self): - return self.await_(self._cursor.fetchall()) - - -class AsyncAdapt_aiomysql_connection(AdaptedConnection): - # TODO: base on connectors/asyncio.py - # see #10415 - await_ = staticmethod(await_only) - __slots__ = ("dbapi", "_execute_mutex") - - def __init__(self, dbapi, connection): - self.dbapi = dbapi - self._connection = connection - self._execute_mutex = asyncio.Lock() - - def ping(self, reconnect): - return self.await_(self._connection.ping(reconnect)) - - def character_set_name(self): - return self._connection.character_set_name() - - def autocommit(self, value): - self.await_(self._connection.autocommit(value)) - - def cursor(self, server_side=False): - if server_side: - return AsyncAdapt_aiomysql_ss_cursor(self) - else: - return AsyncAdapt_aiomysql_cursor(self) - - def rollback(self): - self.await_(self._connection.rollback()) - - def commit(self): - self.await_(self._connection.commit()) - - def terminate(self): - # it's not awaitable. - self._connection.close() - - def close(self) -> None: - self.await_(self._connection.ensure_closed()) - - -class AsyncAdaptFallback_aiomysql_connection(AsyncAdapt_aiomysql_connection): - # TODO: base on connectors/asyncio.py - # see #10415 - __slots__ = () - - await_ = staticmethod(await_fallback) - - -class AsyncAdapt_aiomysql_dbapi: - def __init__(self, aiomysql, pymysql): - self.aiomysql = aiomysql - self.pymysql = pymysql - self.paramstyle = "format" - self._init_dbapi_attributes() - self.Cursor, self.SSCursor = self._init_cursors_subclasses() - - def _init_dbapi_attributes(self): - for name in ( - "Warning", - "Error", - "InterfaceError", - "DataError", - "DatabaseError", - "OperationalError", - "InterfaceError", - "IntegrityError", - "ProgrammingError", - "InternalError", - "NotSupportedError", - ): - setattr(self, name, getattr(self.aiomysql, name)) - - for name in ( - "NUMBER", - "STRING", - "DATETIME", - "BINARY", - "TIMESTAMP", - "Binary", - ): - setattr(self, name, getattr(self.pymysql, name)) - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - creator_fn = kw.pop("async_creator_fn", self.aiomysql.connect) - - if util.asbool(async_fallback): - return AsyncAdaptFallback_aiomysql_connection( - self, - await_fallback(creator_fn(*arg, **kw)), - ) - else: - return AsyncAdapt_aiomysql_connection( - self, - await_only(creator_fn(*arg, **kw)), - ) - - def _init_cursors_subclasses(self): - # suppress unconditional warning emitted by aiomysql - class Cursor(self.aiomysql.Cursor): - async def _show_warnings(self, conn): - pass - - class SSCursor(self.aiomysql.SSCursor): - async def _show_warnings(self, conn): - pass - - return Cursor, SSCursor - - -class MySQLDialect_aiomysql(MySQLDialect_pymysql): - driver = "aiomysql" - supports_statement_cache = True - - supports_server_side_cursors = True - _sscursor = AsyncAdapt_aiomysql_ss_cursor - - is_async = True - has_terminate = True - - @classmethod - def import_dbapi(cls): - return AsyncAdapt_aiomysql_dbapi( - __import__("aiomysql"), __import__("pymysql") - ) - - @classmethod - def get_pool_class(cls, url): - async_fallback = url.query.get("async_fallback", False) - - if util.asbool(async_fallback): - return pool.FallbackAsyncAdaptedQueuePool - else: - return pool.AsyncAdaptedQueuePool - - def do_terminate(self, dbapi_connection) -> None: - dbapi_connection.terminate() - - def create_connect_args(self, url): - return super().create_connect_args( - url, _translate_args=dict(username="user", database="db") - ) - - def is_disconnect(self, e, connection, cursor): - if super().is_disconnect(e, connection, cursor): - return True - else: - str_e = str(e).lower() - return "not connected" in str_e - - def _found_rows_client_flag(self): - from pymysql.constants import CLIENT - - return CLIENT.FOUND_ROWS - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = MySQLDialect_aiomysql diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py deleted file mode 100644 index 7360044..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/asyncmy.py +++ /dev/null @@ -1,337 +0,0 @@ -# dialects/mysql/asyncmy.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: mysql+asyncmy - :name: asyncmy - :dbapi: asyncmy - :connectstring: mysql+asyncmy://user:password@host:port/dbname[?key=value&key=value...] - :url: https://github.com/long2ice/asyncmy - -Using a special asyncio mediation layer, the asyncmy dialect is usable -as the backend for the :ref:`SQLAlchemy asyncio ` -extension package. - -This dialect should normally be used only with the -:func:`_asyncio.create_async_engine` engine creation function:: - - from sqlalchemy.ext.asyncio import create_async_engine - engine = create_async_engine("mysql+asyncmy://user:pass@hostname/dbname?charset=utf8mb4") - - -""" # noqa -from contextlib import asynccontextmanager - -from .pymysql import MySQLDialect_pymysql -from ... import pool -from ... import util -from ...engine import AdaptedConnection -from ...util.concurrency import asyncio -from ...util.concurrency import await_fallback -from ...util.concurrency import await_only - - -class AsyncAdapt_asyncmy_cursor: - # TODO: base on connectors/asyncio.py - # see #10415 - server_side = False - __slots__ = ( - "_adapt_connection", - "_connection", - "await_", - "_cursor", - "_rows", - ) - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self.await_ = adapt_connection.await_ - - cursor = self._connection.cursor() - - self._cursor = self.await_(cursor.__aenter__()) - self._rows = [] - - @property - def description(self): - return self._cursor.description - - @property - def rowcount(self): - return self._cursor.rowcount - - @property - def arraysize(self): - return self._cursor.arraysize - - @arraysize.setter - def arraysize(self, value): - self._cursor.arraysize = value - - @property - def lastrowid(self): - return self._cursor.lastrowid - - def close(self): - # note we aren't actually closing the cursor here, - # we are just letting GC do it. to allow this to be async - # we would need the Result to change how it does "Safe close cursor". - # MySQL "cursors" don't actually have state to be "closed" besides - # exhausting rows, which we already have done for sync cursor. - # another option would be to emulate aiosqlite dialect and assign - # cursor only if we are doing server side cursor operation. - self._rows[:] = [] - - def execute(self, operation, parameters=None): - return self.await_(self._execute_async(operation, parameters)) - - def executemany(self, operation, seq_of_parameters): - return self.await_( - self._executemany_async(operation, seq_of_parameters) - ) - - async def _execute_async(self, operation, parameters): - async with self._adapt_connection._mutex_and_adapt_errors(): - if parameters is None: - result = await self._cursor.execute(operation) - else: - result = await self._cursor.execute(operation, parameters) - - if not self.server_side: - # asyncmy has a "fake" async result, so we have to pull it out - # of that here since our default result is not async. - # we could just as easily grab "_rows" here and be done with it - # but this is safer. - self._rows = list(await self._cursor.fetchall()) - return result - - async def _executemany_async(self, operation, seq_of_parameters): - async with self._adapt_connection._mutex_and_adapt_errors(): - return await self._cursor.executemany(operation, seq_of_parameters) - - def setinputsizes(self, *inputsizes): - pass - - def __iter__(self): - while self._rows: - yield self._rows.pop(0) - - def fetchone(self): - if self._rows: - return self._rows.pop(0) - else: - return None - - def fetchmany(self, size=None): - if size is None: - size = self.arraysize - - retval = self._rows[0:size] - self._rows[:] = self._rows[size:] - return retval - - def fetchall(self): - retval = self._rows[:] - self._rows[:] = [] - return retval - - -class AsyncAdapt_asyncmy_ss_cursor(AsyncAdapt_asyncmy_cursor): - # TODO: base on connectors/asyncio.py - # see #10415 - __slots__ = () - server_side = True - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self.await_ = adapt_connection.await_ - - cursor = self._connection.cursor( - adapt_connection.dbapi.asyncmy.cursors.SSCursor - ) - - self._cursor = self.await_(cursor.__aenter__()) - - def close(self): - if self._cursor is not None: - self.await_(self._cursor.close()) - self._cursor = None - - def fetchone(self): - return self.await_(self._cursor.fetchone()) - - def fetchmany(self, size=None): - return self.await_(self._cursor.fetchmany(size=size)) - - def fetchall(self): - return self.await_(self._cursor.fetchall()) - - -class AsyncAdapt_asyncmy_connection(AdaptedConnection): - # TODO: base on connectors/asyncio.py - # see #10415 - await_ = staticmethod(await_only) - __slots__ = ("dbapi", "_execute_mutex") - - def __init__(self, dbapi, connection): - self.dbapi = dbapi - self._connection = connection - self._execute_mutex = asyncio.Lock() - - @asynccontextmanager - async def _mutex_and_adapt_errors(self): - async with self._execute_mutex: - try: - yield - except AttributeError: - raise self.dbapi.InternalError( - "network operation failed due to asyncmy attribute error" - ) - - def ping(self, reconnect): - assert not reconnect - return self.await_(self._do_ping()) - - async def _do_ping(self): - async with self._mutex_and_adapt_errors(): - return await self._connection.ping(False) - - def character_set_name(self): - return self._connection.character_set_name() - - def autocommit(self, value): - self.await_(self._connection.autocommit(value)) - - def cursor(self, server_side=False): - if server_side: - return AsyncAdapt_asyncmy_ss_cursor(self) - else: - return AsyncAdapt_asyncmy_cursor(self) - - def rollback(self): - self.await_(self._connection.rollback()) - - def commit(self): - self.await_(self._connection.commit()) - - def terminate(self): - # it's not awaitable. - self._connection.close() - - def close(self) -> None: - self.await_(self._connection.ensure_closed()) - - -class AsyncAdaptFallback_asyncmy_connection(AsyncAdapt_asyncmy_connection): - __slots__ = () - - await_ = staticmethod(await_fallback) - - -def _Binary(x): - """Return x as a binary type.""" - return bytes(x) - - -class AsyncAdapt_asyncmy_dbapi: - def __init__(self, asyncmy): - self.asyncmy = asyncmy - self.paramstyle = "format" - self._init_dbapi_attributes() - - def _init_dbapi_attributes(self): - for name in ( - "Warning", - "Error", - "InterfaceError", - "DataError", - "DatabaseError", - "OperationalError", - "InterfaceError", - "IntegrityError", - "ProgrammingError", - "InternalError", - "NotSupportedError", - ): - setattr(self, name, getattr(self.asyncmy.errors, name)) - - STRING = util.symbol("STRING") - NUMBER = util.symbol("NUMBER") - BINARY = util.symbol("BINARY") - DATETIME = util.symbol("DATETIME") - TIMESTAMP = util.symbol("TIMESTAMP") - Binary = staticmethod(_Binary) - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - creator_fn = kw.pop("async_creator_fn", self.asyncmy.connect) - - if util.asbool(async_fallback): - return AsyncAdaptFallback_asyncmy_connection( - self, - await_fallback(creator_fn(*arg, **kw)), - ) - else: - return AsyncAdapt_asyncmy_connection( - self, - await_only(creator_fn(*arg, **kw)), - ) - - -class MySQLDialect_asyncmy(MySQLDialect_pymysql): - driver = "asyncmy" - supports_statement_cache = True - - supports_server_side_cursors = True - _sscursor = AsyncAdapt_asyncmy_ss_cursor - - is_async = True - has_terminate = True - - @classmethod - def import_dbapi(cls): - return AsyncAdapt_asyncmy_dbapi(__import__("asyncmy")) - - @classmethod - def get_pool_class(cls, url): - async_fallback = url.query.get("async_fallback", False) - - if util.asbool(async_fallback): - return pool.FallbackAsyncAdaptedQueuePool - else: - return pool.AsyncAdaptedQueuePool - - def do_terminate(self, dbapi_connection) -> None: - dbapi_connection.terminate() - - def create_connect_args(self, url): - return super().create_connect_args( - url, _translate_args=dict(username="user", database="db") - ) - - def is_disconnect(self, e, connection, cursor): - if super().is_disconnect(e, connection, cursor): - return True - else: - str_e = str(e).lower() - return ( - "not connected" in str_e or "network operation failed" in str_e - ) - - def _found_rows_client_flag(self): - from asyncmy.constants import CLIENT - - return CLIENT.FOUND_ROWS - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = MySQLDialect_asyncmy diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py deleted file mode 100644 index dacbb7a..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/base.py +++ /dev/null @@ -1,3447 +0,0 @@ -# dialects/mysql/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" - -.. dialect:: mysql - :name: MySQL / MariaDB - :full_support: 5.6, 5.7, 8.0 / 10.8, 10.9 - :normal_support: 5.6+ / 10+ - :best_effort: 5.0.2+ / 5.0.2+ - -Supported Versions and Features -------------------------------- - -SQLAlchemy supports MySQL starting with version 5.0.2 through modern releases, -as well as all modern versions of MariaDB. See the official MySQL -documentation for detailed information about features supported in any given -server release. - -.. versionchanged:: 1.4 minimum MySQL version supported is now 5.0.2. - -MariaDB Support -~~~~~~~~~~~~~~~ - -The MariaDB variant of MySQL retains fundamental compatibility with MySQL's -protocols however the development of these two products continues to diverge. -Within the realm of SQLAlchemy, the two databases have a small number of -syntactical and behavioral differences that SQLAlchemy accommodates automatically. -To connect to a MariaDB database, no changes to the database URL are required:: - - - engine = create_engine("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4") - -Upon first connect, the SQLAlchemy dialect employs a -server version detection scheme that determines if the -backing database reports as MariaDB. Based on this flag, the dialect -can make different choices in those of areas where its behavior -must be different. - -.. _mysql_mariadb_only_mode: - -MariaDB-Only Mode -~~~~~~~~~~~~~~~~~ - -The dialect also supports an **optional** "MariaDB-only" mode of connection, which may be -useful for the case where an application makes use of MariaDB-specific features -and is not compatible with a MySQL database. To use this mode of operation, -replace the "mysql" token in the above URL with "mariadb":: - - engine = create_engine("mariadb+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4") - -The above engine, upon first connect, will raise an error if the server version -detection detects that the backing database is not MariaDB. - -When using an engine with ``"mariadb"`` as the dialect name, **all mysql-specific options -that include the name "mysql" in them are now named with "mariadb"**. This means -options like ``mysql_engine`` should be named ``mariadb_engine``, etc. Both -"mysql" and "mariadb" options can be used simultaneously for applications that -use URLs with both "mysql" and "mariadb" dialects:: - - my_table = Table( - "mytable", - metadata, - Column("id", Integer, primary_key=True), - Column("textdata", String(50)), - mariadb_engine="InnoDB", - mysql_engine="InnoDB", - ) - - Index( - "textdata_ix", - my_table.c.textdata, - mysql_prefix="FULLTEXT", - mariadb_prefix="FULLTEXT", - ) - -Similar behavior will occur when the above structures are reflected, i.e. the -"mariadb" prefix will be present in the option names when the database URL -is based on the "mariadb" name. - -.. versionadded:: 1.4 Added "mariadb" dialect name supporting "MariaDB-only mode" - for the MySQL dialect. - -.. _mysql_connection_timeouts: - -Connection Timeouts and Disconnects ------------------------------------ - -MySQL / MariaDB feature an automatic connection close behavior, for connections that -have been idle for a fixed period of time, defaulting to eight hours. -To circumvent having this issue, use -the :paramref:`_sa.create_engine.pool_recycle` option which ensures that -a connection will be discarded and replaced with a new one if it has been -present in the pool for a fixed number of seconds:: - - engine = create_engine('mysql+mysqldb://...', pool_recycle=3600) - -For more comprehensive disconnect detection of pooled connections, including -accommodation of server restarts and network issues, a pre-ping approach may -be employed. See :ref:`pool_disconnects` for current approaches. - -.. seealso:: - - :ref:`pool_disconnects` - Background on several techniques for dealing - with timed out connections as well as database restarts. - -.. _mysql_storage_engines: - -CREATE TABLE arguments including Storage Engines ------------------------------------------------- - -Both MySQL's and MariaDB's CREATE TABLE syntax includes a wide array of special options, -including ``ENGINE``, ``CHARSET``, ``MAX_ROWS``, ``ROW_FORMAT``, -``INSERT_METHOD``, and many more. -To accommodate the rendering of these arguments, specify the form -``mysql_argument_name="value"``. For example, to specify a table with -``ENGINE`` of ``InnoDB``, ``CHARSET`` of ``utf8mb4``, and ``KEY_BLOCK_SIZE`` -of ``1024``:: - - Table('mytable', metadata, - Column('data', String(32)), - mysql_engine='InnoDB', - mysql_charset='utf8mb4', - mysql_key_block_size="1024" - ) - -When supporting :ref:`mysql_mariadb_only_mode` mode, similar keys against -the "mariadb" prefix must be included as well. The values can of course -vary independently so that different settings on MySQL vs. MariaDB may -be maintained:: - - # support both "mysql" and "mariadb-only" engine URLs - - Table('mytable', metadata, - Column('data', String(32)), - - mysql_engine='InnoDB', - mariadb_engine='InnoDB', - - mysql_charset='utf8mb4', - mariadb_charset='utf8', - - mysql_key_block_size="1024" - mariadb_key_block_size="1024" - - ) - -The MySQL / MariaDB dialects will normally transfer any keyword specified as -``mysql_keyword_name`` to be rendered as ``KEYWORD_NAME`` in the -``CREATE TABLE`` statement. A handful of these names will render with a space -instead of an underscore; to support this, the MySQL dialect has awareness of -these particular names, which include ``DATA DIRECTORY`` -(e.g. ``mysql_data_directory``), ``CHARACTER SET`` (e.g. -``mysql_character_set``) and ``INDEX DIRECTORY`` (e.g. -``mysql_index_directory``). - -The most common argument is ``mysql_engine``, which refers to the storage -engine for the table. Historically, MySQL server installations would default -to ``MyISAM`` for this value, although newer versions may be defaulting -to ``InnoDB``. The ``InnoDB`` engine is typically preferred for its support -of transactions and foreign keys. - -A :class:`_schema.Table` -that is created in a MySQL / MariaDB database with a storage engine -of ``MyISAM`` will be essentially non-transactional, meaning any -INSERT/UPDATE/DELETE statement referring to this table will be invoked as -autocommit. It also will have no support for foreign key constraints; while -the ``CREATE TABLE`` statement accepts foreign key options, when using the -``MyISAM`` storage engine these arguments are discarded. Reflecting such a -table will also produce no foreign key constraint information. - -For fully atomic transactions as well as support for foreign key -constraints, all participating ``CREATE TABLE`` statements must specify a -transactional engine, which in the vast majority of cases is ``InnoDB``. - - -Case Sensitivity and Table Reflection -------------------------------------- - -Both MySQL and MariaDB have inconsistent support for case-sensitive identifier -names, basing support on specific details of the underlying -operating system. However, it has been observed that no matter -what case sensitivity behavior is present, the names of tables in -foreign key declarations are *always* received from the database -as all-lower case, making it impossible to accurately reflect a -schema where inter-related tables use mixed-case identifier names. - -Therefore it is strongly advised that table names be declared as -all lower case both within SQLAlchemy as well as on the MySQL / MariaDB -database itself, especially if database reflection features are -to be used. - -.. _mysql_isolation_level: - -Transaction Isolation Level ---------------------------- - -All MySQL / MariaDB dialects support setting of transaction isolation level both via a -dialect-specific parameter :paramref:`_sa.create_engine.isolation_level` -accepted -by :func:`_sa.create_engine`, as well as the -:paramref:`.Connection.execution_options.isolation_level` argument as passed to -:meth:`_engine.Connection.execution_options`. -This feature works by issuing the -command ``SET SESSION TRANSACTION ISOLATION LEVEL `` for each new -connection. For the special AUTOCOMMIT isolation level, DBAPI-specific -techniques are used. - -To set isolation level using :func:`_sa.create_engine`:: - - engine = create_engine( - "mysql+mysqldb://scott:tiger@localhost/test", - isolation_level="READ UNCOMMITTED" - ) - -To set using per-connection execution options:: - - connection = engine.connect() - connection = connection.execution_options( - isolation_level="READ COMMITTED" - ) - -Valid values for ``isolation_level`` include: - -* ``READ COMMITTED`` -* ``READ UNCOMMITTED`` -* ``REPEATABLE READ`` -* ``SERIALIZABLE`` -* ``AUTOCOMMIT`` - -The special ``AUTOCOMMIT`` value makes use of the various "autocommit" -attributes provided by specific DBAPIs, and is currently supported by -MySQLdb, MySQL-Client, MySQL-Connector Python, and PyMySQL. Using it, -the database connection will return true for the value of -``SELECT @@autocommit;``. - -There are also more options for isolation level configurations, such as -"sub-engine" objects linked to a main :class:`_engine.Engine` which each apply -different isolation level settings. See the discussion at -:ref:`dbapi_autocommit` for background. - -.. seealso:: - - :ref:`dbapi_autocommit` - -AUTO_INCREMENT Behavior ------------------------ - -When creating tables, SQLAlchemy will automatically set ``AUTO_INCREMENT`` on -the first :class:`.Integer` primary key column which is not marked as a -foreign key:: - - >>> t = Table('mytable', metadata, - ... Column('mytable_id', Integer, primary_key=True) - ... ) - >>> t.create() - CREATE TABLE mytable ( - id INTEGER NOT NULL AUTO_INCREMENT, - PRIMARY KEY (id) - ) - -You can disable this behavior by passing ``False`` to the -:paramref:`_schema.Column.autoincrement` argument of :class:`_schema.Column`. -This flag -can also be used to enable auto-increment on a secondary column in a -multi-column key for some storage engines:: - - Table('mytable', metadata, - Column('gid', Integer, primary_key=True, autoincrement=False), - Column('id', Integer, primary_key=True) - ) - -.. _mysql_ss_cursors: - -Server Side Cursors -------------------- - -Server-side cursor support is available for the mysqlclient, PyMySQL, -mariadbconnector dialects and may also be available in others. This makes use -of either the "buffered=True/False" flag if available or by using a class such -as ``MySQLdb.cursors.SSCursor`` or ``pymysql.cursors.SSCursor`` internally. - - -Server side cursors are enabled on a per-statement basis by using the -:paramref:`.Connection.execution_options.stream_results` connection execution -option:: - - with engine.connect() as conn: - result = conn.execution_options(stream_results=True).execute(text("select * from table")) - -Note that some kinds of SQL statements may not be supported with -server side cursors; generally, only SQL statements that return rows should be -used with this option. - -.. deprecated:: 1.4 The dialect-level server_side_cursors flag is deprecated - and will be removed in a future release. Please use the - :paramref:`_engine.Connection.stream_results` execution option for - unbuffered cursor support. - -.. seealso:: - - :ref:`engine_stream_results` - -.. _mysql_unicode: - -Unicode -------- - -Charset Selection -~~~~~~~~~~~~~~~~~ - -Most MySQL / MariaDB DBAPIs offer the option to set the client character set for -a connection. This is typically delivered using the ``charset`` parameter -in the URL, such as:: - - e = create_engine( - "mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4") - -This charset is the **client character set** for the connection. Some -MySQL DBAPIs will default this to a value such as ``latin1``, and some -will make use of the ``default-character-set`` setting in the ``my.cnf`` -file as well. Documentation for the DBAPI in use should be consulted -for specific behavior. - -The encoding used for Unicode has traditionally been ``'utf8'``. However, for -MySQL versions 5.5.3 and MariaDB 5.5 on forward, a new MySQL-specific encoding -``'utf8mb4'`` has been introduced, and as of MySQL 8.0 a warning is emitted by -the server if plain ``utf8`` is specified within any server-side directives, -replaced with ``utf8mb3``. The rationale for this new encoding is due to the -fact that MySQL's legacy utf-8 encoding only supports codepoints up to three -bytes instead of four. Therefore, when communicating with a MySQL or MariaDB -database that includes codepoints more than three bytes in size, this new -charset is preferred, if supported by both the database as well as the client -DBAPI, as in:: - - e = create_engine( - "mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4") - -All modern DBAPIs should support the ``utf8mb4`` charset. - -In order to use ``utf8mb4`` encoding for a schema that was created with legacy -``utf8``, changes to the MySQL/MariaDB schema and/or server configuration may be -required. - -.. seealso:: - - `The utf8mb4 Character Set \ - `_ - \ - in the MySQL documentation - -.. _mysql_binary_introducer: - -Dealing with Binary Data Warnings and Unicode -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MySQL versions 5.6, 5.7 and later (not MariaDB at the time of this writing) now -emit a warning when attempting to pass binary data to the database, while a -character set encoding is also in place, when the binary data itself is not -valid for that encoding:: - - default.py:509: Warning: (1300, "Invalid utf8mb4 character string: - 'F9876A'") - cursor.execute(statement, parameters) - -This warning is due to the fact that the MySQL client library is attempting to -interpret the binary string as a unicode object even if a datatype such -as :class:`.LargeBinary` is in use. To resolve this, the SQL statement requires -a binary "character set introducer" be present before any non-NULL value -that renders like this:: - - INSERT INTO table (data) VALUES (_binary %s) - -These character set introducers are provided by the DBAPI driver, assuming the -use of mysqlclient or PyMySQL (both of which are recommended). Add the query -string parameter ``binary_prefix=true`` to the URL to repair this warning:: - - # mysqlclient - engine = create_engine( - "mysql+mysqldb://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true") - - # PyMySQL - engine = create_engine( - "mysql+pymysql://scott:tiger@localhost/test?charset=utf8mb4&binary_prefix=true") - - -The ``binary_prefix`` flag may or may not be supported by other MySQL drivers. - -SQLAlchemy itself cannot render this ``_binary`` prefix reliably, as it does -not work with the NULL value, which is valid to be sent as a bound parameter. -As the MySQL driver renders parameters directly into the SQL string, it's the -most efficient place for this additional keyword to be passed. - -.. seealso:: - - `Character set introducers `_ - on the MySQL website - - -ANSI Quoting Style ------------------- - -MySQL / MariaDB feature two varieties of identifier "quoting style", one using -backticks and the other using quotes, e.g. ```some_identifier``` vs. -``"some_identifier"``. All MySQL dialects detect which version -is in use by checking the value of :ref:`sql_mode` when a connection is first -established with a particular :class:`_engine.Engine`. -This quoting style comes -into play when rendering table and column names as well as when reflecting -existing database structures. The detection is entirely automatic and -no special configuration is needed to use either quoting style. - - -.. _mysql_sql_mode: - -Changing the sql_mode ---------------------- - -MySQL supports operating in multiple -`Server SQL Modes `_ for -both Servers and Clients. To change the ``sql_mode`` for a given application, a -developer can leverage SQLAlchemy's Events system. - -In the following example, the event system is used to set the ``sql_mode`` on -the ``first_connect`` and ``connect`` events:: - - from sqlalchemy import create_engine, event - - eng = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo='debug') - - # `insert=True` will ensure this is the very first listener to run - @event.listens_for(eng, "connect", insert=True) - def connect(dbapi_connection, connection_record): - cursor = dbapi_connection.cursor() - cursor.execute("SET sql_mode = 'STRICT_ALL_TABLES'") - - conn = eng.connect() - -In the example illustrated above, the "connect" event will invoke the "SET" -statement on the connection at the moment a particular DBAPI connection is -first created for a given Pool, before the connection is made available to the -connection pool. Additionally, because the function was registered with -``insert=True``, it will be prepended to the internal list of registered -functions. - - -MySQL / MariaDB SQL Extensions ------------------------------- - -Many of the MySQL / MariaDB SQL extensions are handled through SQLAlchemy's generic -function and operator support:: - - table.select(table.c.password==func.md5('plaintext')) - table.select(table.c.username.op('regexp')('^[a-d]')) - -And of course any valid SQL statement can be executed as a string as well. - -Some limited direct support for MySQL / MariaDB extensions to SQL is currently -available. - -* INSERT..ON DUPLICATE KEY UPDATE: See - :ref:`mysql_insert_on_duplicate_key_update` - -* SELECT pragma, use :meth:`_expression.Select.prefix_with` and - :meth:`_query.Query.prefix_with`:: - - select(...).prefix_with(['HIGH_PRIORITY', 'SQL_SMALL_RESULT']) - -* UPDATE with LIMIT:: - - update(..., mysql_limit=10, mariadb_limit=10) - -* optimizer hints, use :meth:`_expression.Select.prefix_with` and - :meth:`_query.Query.prefix_with`:: - - select(...).prefix_with("/*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */") - -* index hints, use :meth:`_expression.Select.with_hint` and - :meth:`_query.Query.with_hint`:: - - select(...).with_hint(some_table, "USE INDEX xyz") - -* MATCH operator support:: - - from sqlalchemy.dialects.mysql import match - select(...).where(match(col1, col2, against="some expr").in_boolean_mode()) - - .. seealso:: - - :class:`_mysql.match` - -INSERT/DELETE...RETURNING -------------------------- - -The MariaDB dialect supports 10.5+'s ``INSERT..RETURNING`` and -``DELETE..RETURNING`` (10.0+) syntaxes. ``INSERT..RETURNING`` may be used -automatically in some cases in order to fetch newly generated identifiers in -place of the traditional approach of using ``cursor.lastrowid``, however -``cursor.lastrowid`` is currently still preferred for simple single-statement -cases for its better performance. - -To specify an explicit ``RETURNING`` clause, use the -:meth:`._UpdateBase.returning` method on a per-statement basis:: - - # INSERT..RETURNING - result = connection.execute( - table.insert(). - values(name='foo'). - returning(table.c.col1, table.c.col2) - ) - print(result.all()) - - # DELETE..RETURNING - result = connection.execute( - table.delete(). - where(table.c.name=='foo'). - returning(table.c.col1, table.c.col2) - ) - print(result.all()) - -.. versionadded:: 2.0 Added support for MariaDB RETURNING - -.. _mysql_insert_on_duplicate_key_update: - -INSERT...ON DUPLICATE KEY UPDATE (Upsert) ------------------------------------------- - -MySQL / MariaDB allow "upserts" (update or insert) -of rows into a table via the ``ON DUPLICATE KEY UPDATE`` clause of the -``INSERT`` statement. A candidate row will only be inserted if that row does -not match an existing primary or unique key in the table; otherwise, an UPDATE -will be performed. The statement allows for separate specification of the -values to INSERT versus the values for UPDATE. - -SQLAlchemy provides ``ON DUPLICATE KEY UPDATE`` support via the MySQL-specific -:func:`.mysql.insert()` function, which provides -the generative method :meth:`~.mysql.Insert.on_duplicate_key_update`: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy.dialects.mysql import insert - - >>> insert_stmt = insert(my_table).values( - ... id='some_existing_id', - ... data='inserted value') - - >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update( - ... data=insert_stmt.inserted.data, - ... status='U' - ... ) - >>> print(on_duplicate_key_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s) - ON DUPLICATE KEY UPDATE data = VALUES(data), status = %s - - -Unlike PostgreSQL's "ON CONFLICT" phrase, the "ON DUPLICATE KEY UPDATE" -phrase will always match on any primary key or unique key, and will always -perform an UPDATE if there's a match; there are no options for it to raise -an error or to skip performing an UPDATE. - -``ON DUPLICATE KEY UPDATE`` is used to perform an update of the already -existing row, using any combination of new values as well as values -from the proposed insertion. These values are normally specified using -keyword arguments passed to the -:meth:`_mysql.Insert.on_duplicate_key_update` -given column key values (usually the name of the column, unless it -specifies :paramref:`_schema.Column.key` -) as keys and literal or SQL expressions -as values: - -.. sourcecode:: pycon+sql - - >>> insert_stmt = insert(my_table).values( - ... id='some_existing_id', - ... data='inserted value') - - >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update( - ... data="some data", - ... updated_at=func.current_timestamp(), - ... ) - - >>> print(on_duplicate_key_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s) - ON DUPLICATE KEY UPDATE data = %s, updated_at = CURRENT_TIMESTAMP - -In a manner similar to that of :meth:`.UpdateBase.values`, other parameter -forms are accepted, including a single dictionary: - -.. sourcecode:: pycon+sql - - >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update( - ... {"data": "some data", "updated_at": func.current_timestamp()}, - ... ) - -as well as a list of 2-tuples, which will automatically provide -a parameter-ordered UPDATE statement in a manner similar to that described -at :ref:`tutorial_parameter_ordered_updates`. Unlike the :class:`_expression.Update` -object, -no special flag is needed to specify the intent since the argument form is -this context is unambiguous: - -.. sourcecode:: pycon+sql - - >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update( - ... [ - ... ("data", "some data"), - ... ("updated_at", func.current_timestamp()), - ... ] - ... ) - - >>> print(on_duplicate_key_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%s, %s) - ON DUPLICATE KEY UPDATE data = %s, updated_at = CURRENT_TIMESTAMP - -.. versionchanged:: 1.3 support for parameter-ordered UPDATE clause within - MySQL ON DUPLICATE KEY UPDATE - -.. warning:: - - The :meth:`_mysql.Insert.on_duplicate_key_update` - method does **not** take into - account Python-side default UPDATE values or generation functions, e.g. - e.g. those specified using :paramref:`_schema.Column.onupdate`. - These values will not be exercised for an ON DUPLICATE KEY style of UPDATE, - unless they are manually specified explicitly in the parameters. - - - -In order to refer to the proposed insertion row, the special alias -:attr:`_mysql.Insert.inserted` is available as an attribute on -the :class:`_mysql.Insert` object; this object is a -:class:`_expression.ColumnCollection` which contains all columns of the target -table: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values( - ... id='some_id', - ... data='inserted value', - ... author='jlh') - - >>> do_update_stmt = stmt.on_duplicate_key_update( - ... data="updated value", - ... author=stmt.inserted.author - ... ) - - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data, author) VALUES (%s, %s, %s) - ON DUPLICATE KEY UPDATE data = %s, author = VALUES(author) - -When rendered, the "inserted" namespace will produce the expression -``VALUES()``. - -.. versionadded:: 1.2 Added support for MySQL ON DUPLICATE KEY UPDATE clause - - - -rowcount Support ----------------- - -SQLAlchemy standardizes the DBAPI ``cursor.rowcount`` attribute to be the -usual definition of "number of rows matched by an UPDATE or DELETE" statement. -This is in contradiction to the default setting on most MySQL DBAPI drivers, -which is "number of rows actually modified/deleted". For this reason, the -SQLAlchemy MySQL dialects always add the ``constants.CLIENT.FOUND_ROWS`` -flag, or whatever is equivalent for the target dialect, upon connection. -This setting is currently hardcoded. - -.. seealso:: - - :attr:`_engine.CursorResult.rowcount` - - -.. _mysql_indexes: - -MySQL / MariaDB- Specific Index Options ------------------------------------------ - -MySQL and MariaDB-specific extensions to the :class:`.Index` construct are available. - -Index Length -~~~~~~~~~~~~~ - -MySQL and MariaDB both provide an option to create index entries with a certain length, where -"length" refers to the number of characters or bytes in each value which will -become part of the index. SQLAlchemy provides this feature via the -``mysql_length`` and/or ``mariadb_length`` parameters:: - - Index('my_index', my_table.c.data, mysql_length=10, mariadb_length=10) - - Index('a_b_idx', my_table.c.a, my_table.c.b, mysql_length={'a': 4, - 'b': 9}) - - Index('a_b_idx', my_table.c.a, my_table.c.b, mariadb_length={'a': 4, - 'b': 9}) - -Prefix lengths are given in characters for nonbinary string types and in bytes -for binary string types. The value passed to the keyword argument *must* be -either an integer (and, thus, specify the same prefix length value for all -columns of the index) or a dict in which keys are column names and values are -prefix length values for corresponding columns. MySQL and MariaDB only allow a -length for a column of an index if it is for a CHAR, VARCHAR, TEXT, BINARY, -VARBINARY and BLOB. - -Index Prefixes -~~~~~~~~~~~~~~ - -MySQL storage engines permit you to specify an index prefix when creating -an index. SQLAlchemy provides this feature via the -``mysql_prefix`` parameter on :class:`.Index`:: - - Index('my_index', my_table.c.data, mysql_prefix='FULLTEXT') - -The value passed to the keyword argument will be simply passed through to the -underlying CREATE INDEX, so it *must* be a valid index prefix for your MySQL -storage engine. - -.. seealso:: - - `CREATE INDEX `_ - MySQL documentation - -Index Types -~~~~~~~~~~~~~ - -Some MySQL storage engines permit you to specify an index type when creating -an index or primary key constraint. SQLAlchemy provides this feature via the -``mysql_using`` parameter on :class:`.Index`:: - - Index('my_index', my_table.c.data, mysql_using='hash', mariadb_using='hash') - -As well as the ``mysql_using`` parameter on :class:`.PrimaryKeyConstraint`:: - - PrimaryKeyConstraint("data", mysql_using='hash', mariadb_using='hash') - -The value passed to the keyword argument will be simply passed through to the -underlying CREATE INDEX or PRIMARY KEY clause, so it *must* be a valid index -type for your MySQL storage engine. - -More information can be found at: - -https://dev.mysql.com/doc/refman/5.0/en/create-index.html - -https://dev.mysql.com/doc/refman/5.0/en/create-table.html - -Index Parsers -~~~~~~~~~~~~~ - -CREATE FULLTEXT INDEX in MySQL also supports a "WITH PARSER" option. This -is available using the keyword argument ``mysql_with_parser``:: - - Index( - 'my_index', my_table.c.data, - mysql_prefix='FULLTEXT', mysql_with_parser="ngram", - mariadb_prefix='FULLTEXT', mariadb_with_parser="ngram", - ) - -.. versionadded:: 1.3 - - -.. _mysql_foreign_keys: - -MySQL / MariaDB Foreign Keys ------------------------------ - -MySQL and MariaDB's behavior regarding foreign keys has some important caveats. - -Foreign Key Arguments to Avoid -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Neither MySQL nor MariaDB support the foreign key arguments "DEFERRABLE", "INITIALLY", -or "MATCH". Using the ``deferrable`` or ``initially`` keyword argument with -:class:`_schema.ForeignKeyConstraint` or :class:`_schema.ForeignKey` -will have the effect of -these keywords being rendered in a DDL expression, which will then raise an -error on MySQL or MariaDB. In order to use these keywords on a foreign key while having -them ignored on a MySQL / MariaDB backend, use a custom compile rule:: - - from sqlalchemy.ext.compiler import compiles - from sqlalchemy.schema import ForeignKeyConstraint - - @compiles(ForeignKeyConstraint, "mysql", "mariadb") - def process(element, compiler, **kw): - element.deferrable = element.initially = None - return compiler.visit_foreign_key_constraint(element, **kw) - -The "MATCH" keyword is in fact more insidious, and is explicitly disallowed -by SQLAlchemy in conjunction with the MySQL or MariaDB backends. This argument is -silently ignored by MySQL / MariaDB, but in addition has the effect of ON UPDATE and ON -DELETE options also being ignored by the backend. Therefore MATCH should -never be used with the MySQL / MariaDB backends; as is the case with DEFERRABLE and -INITIALLY, custom compilation rules can be used to correct a -ForeignKeyConstraint at DDL definition time. - -Reflection of Foreign Key Constraints -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Not all MySQL / MariaDB storage engines support foreign keys. When using the -very common ``MyISAM`` MySQL storage engine, the information loaded by table -reflection will not include foreign keys. For these tables, you may supply a -:class:`~sqlalchemy.ForeignKeyConstraint` at reflection time:: - - Table('mytable', metadata, - ForeignKeyConstraint(['other_id'], ['othertable.other_id']), - autoload_with=engine - ) - -.. seealso:: - - :ref:`mysql_storage_engines` - -.. _mysql_unique_constraints: - -MySQL / MariaDB Unique Constraints and Reflection ----------------------------------------------------- - -SQLAlchemy supports both the :class:`.Index` construct with the -flag ``unique=True``, indicating a UNIQUE index, as well as the -:class:`.UniqueConstraint` construct, representing a UNIQUE constraint. -Both objects/syntaxes are supported by MySQL / MariaDB when emitting DDL to create -these constraints. However, MySQL / MariaDB does not have a unique constraint -construct that is separate from a unique index; that is, the "UNIQUE" -constraint on MySQL / MariaDB is equivalent to creating a "UNIQUE INDEX". - -When reflecting these constructs, the -:meth:`_reflection.Inspector.get_indexes` -and the :meth:`_reflection.Inspector.get_unique_constraints` -methods will **both** -return an entry for a UNIQUE index in MySQL / MariaDB. However, when performing -full table reflection using ``Table(..., autoload_with=engine)``, -the :class:`.UniqueConstraint` construct is -**not** part of the fully reflected :class:`_schema.Table` construct under any -circumstances; this construct is always represented by a :class:`.Index` -with the ``unique=True`` setting present in the :attr:`_schema.Table.indexes` -collection. - - -TIMESTAMP / DATETIME issues ---------------------------- - -.. _mysql_timestamp_onupdate: - -Rendering ON UPDATE CURRENT TIMESTAMP for MySQL / MariaDB's explicit_defaults_for_timestamp -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MySQL / MariaDB have historically expanded the DDL for the :class:`_types.TIMESTAMP` -datatype into the phrase "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE -CURRENT_TIMESTAMP", which includes non-standard SQL that automatically updates -the column with the current timestamp when an UPDATE occurs, eliminating the -usual need to use a trigger in such a case where server-side update changes are -desired. - -MySQL 5.6 introduced a new flag `explicit_defaults_for_timestamp -`_ which disables the above behavior, -and in MySQL 8 this flag defaults to true, meaning in order to get a MySQL -"on update timestamp" without changing this flag, the above DDL must be -rendered explicitly. Additionally, the same DDL is valid for use of the -``DATETIME`` datatype as well. - -SQLAlchemy's MySQL dialect does not yet have an option to generate -MySQL's "ON UPDATE CURRENT_TIMESTAMP" clause, noting that this is not a general -purpose "ON UPDATE" as there is no such syntax in standard SQL. SQLAlchemy's -:paramref:`_schema.Column.server_onupdate` parameter is currently not related -to this special MySQL behavior. - -To generate this DDL, make use of the :paramref:`_schema.Column.server_default` -parameter and pass a textual clause that also includes the ON UPDATE clause:: - - from sqlalchemy import Table, MetaData, Column, Integer, String, TIMESTAMP - from sqlalchemy import text - - metadata = MetaData() - - mytable = Table( - "mytable", - metadata, - Column('id', Integer, primary_key=True), - Column('data', String(50)), - Column( - 'last_updated', - TIMESTAMP, - server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") - ) - ) - -The same instructions apply to use of the :class:`_types.DateTime` and -:class:`_types.DATETIME` datatypes:: - - from sqlalchemy import DateTime - - mytable = Table( - "mytable", - metadata, - Column('id', Integer, primary_key=True), - Column('data', String(50)), - Column( - 'last_updated', - DateTime, - server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") - ) - ) - - -Even though the :paramref:`_schema.Column.server_onupdate` feature does not -generate this DDL, it still may be desirable to signal to the ORM that this -updated value should be fetched. This syntax looks like the following:: - - from sqlalchemy.schema import FetchedValue - - class MyClass(Base): - __tablename__ = 'mytable' - - id = Column(Integer, primary_key=True) - data = Column(String(50)) - last_updated = Column( - TIMESTAMP, - server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), - server_onupdate=FetchedValue() - ) - - -.. _mysql_timestamp_null: - -TIMESTAMP Columns and NULL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MySQL historically enforces that a column which specifies the -TIMESTAMP datatype implicitly includes a default value of -CURRENT_TIMESTAMP, even though this is not stated, and additionally -sets the column as NOT NULL, the opposite behavior vs. that of all -other datatypes:: - - mysql> CREATE TABLE ts_test ( - -> a INTEGER, - -> b INTEGER NOT NULL, - -> c TIMESTAMP, - -> d TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - -> e TIMESTAMP NULL); - Query OK, 0 rows affected (0.03 sec) - - mysql> SHOW CREATE TABLE ts_test; - +---------+----------------------------------------------------- - | Table | Create Table - +---------+----------------------------------------------------- - | ts_test | CREATE TABLE `ts_test` ( - `a` int(11) DEFAULT NULL, - `b` int(11) NOT NULL, - `c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `d` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `e` timestamp NULL DEFAULT NULL - ) ENGINE=MyISAM DEFAULT CHARSET=latin1 - -Above, we see that an INTEGER column defaults to NULL, unless it is specified -with NOT NULL. But when the column is of type TIMESTAMP, an implicit -default of CURRENT_TIMESTAMP is generated which also coerces the column -to be a NOT NULL, even though we did not specify it as such. - -This behavior of MySQL can be changed on the MySQL side using the -`explicit_defaults_for_timestamp -`_ configuration flag introduced in -MySQL 5.6. With this server setting enabled, TIMESTAMP columns behave like -any other datatype on the MySQL side with regards to defaults and nullability. - -However, to accommodate the vast majority of MySQL databases that do not -specify this new flag, SQLAlchemy emits the "NULL" specifier explicitly with -any TIMESTAMP column that does not specify ``nullable=False``. In order to -accommodate newer databases that specify ``explicit_defaults_for_timestamp``, -SQLAlchemy also emits NOT NULL for TIMESTAMP columns that do specify -``nullable=False``. The following example illustrates:: - - from sqlalchemy import MetaData, Integer, Table, Column, text - from sqlalchemy.dialects.mysql import TIMESTAMP - - m = MetaData() - t = Table('ts_test', m, - Column('a', Integer), - Column('b', Integer, nullable=False), - Column('c', TIMESTAMP), - Column('d', TIMESTAMP, nullable=False) - ) - - - from sqlalchemy import create_engine - e = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True) - m.create_all(e) - -output:: - - CREATE TABLE ts_test ( - a INTEGER, - b INTEGER NOT NULL, - c TIMESTAMP NULL, - d TIMESTAMP NOT NULL - ) - -""" # noqa -from __future__ import annotations - -from array import array as _array -from collections import defaultdict -from itertools import compress -import re -from typing import cast - -from . import reflection as _reflection -from .enumerated import ENUM -from .enumerated import SET -from .json import JSON -from .json import JSONIndexType -from .json import JSONPathType -from .reserved_words import RESERVED_WORDS_MARIADB -from .reserved_words import RESERVED_WORDS_MYSQL -from .types import _FloatType -from .types import _IntegerType -from .types import _MatchType -from .types import _NumericType -from .types import _StringType -from .types import BIGINT -from .types import BIT -from .types import CHAR -from .types import DATETIME -from .types import DECIMAL -from .types import DOUBLE -from .types import FLOAT -from .types import INTEGER -from .types import LONGBLOB -from .types import LONGTEXT -from .types import MEDIUMBLOB -from .types import MEDIUMINT -from .types import MEDIUMTEXT -from .types import NCHAR -from .types import NUMERIC -from .types import NVARCHAR -from .types import REAL -from .types import SMALLINT -from .types import TEXT -from .types import TIME -from .types import TIMESTAMP -from .types import TINYBLOB -from .types import TINYINT -from .types import TINYTEXT -from .types import VARCHAR -from .types import YEAR -from ... import exc -from ... import literal_column -from ... import log -from ... import schema as sa_schema -from ... import sql -from ... import util -from ...engine import cursor as _cursor -from ...engine import default -from ...engine import reflection -from ...engine.reflection import ReflectionDefaults -from ...sql import coercions -from ...sql import compiler -from ...sql import elements -from ...sql import functions -from ...sql import operators -from ...sql import roles -from ...sql import sqltypes -from ...sql import util as sql_util -from ...sql import visitors -from ...sql.compiler import InsertmanyvaluesSentinelOpts -from ...sql.compiler import SQLCompiler -from ...sql.schema import SchemaConst -from ...types import BINARY -from ...types import BLOB -from ...types import BOOLEAN -from ...types import DATE -from ...types import UUID -from ...types import VARBINARY -from ...util import topological - - -SET_RE = re.compile( - r"\s*SET\s+(?:(?:GLOBAL|SESSION)\s+)?\w", re.I | re.UNICODE -) - -# old names -MSTime = TIME -MSSet = SET -MSEnum = ENUM -MSLongBlob = LONGBLOB -MSMediumBlob = MEDIUMBLOB -MSTinyBlob = TINYBLOB -MSBlob = BLOB -MSBinary = BINARY -MSVarBinary = VARBINARY -MSNChar = NCHAR -MSNVarChar = NVARCHAR -MSChar = CHAR -MSString = VARCHAR -MSLongText = LONGTEXT -MSMediumText = MEDIUMTEXT -MSTinyText = TINYTEXT -MSText = TEXT -MSYear = YEAR -MSTimeStamp = TIMESTAMP -MSBit = BIT -MSSmallInteger = SMALLINT -MSTinyInteger = TINYINT -MSMediumInteger = MEDIUMINT -MSBigInteger = BIGINT -MSNumeric = NUMERIC -MSDecimal = DECIMAL -MSDouble = DOUBLE -MSReal = REAL -MSFloat = FLOAT -MSInteger = INTEGER - -colspecs = { - _IntegerType: _IntegerType, - _NumericType: _NumericType, - _FloatType: _FloatType, - sqltypes.Numeric: NUMERIC, - sqltypes.Float: FLOAT, - sqltypes.Double: DOUBLE, - sqltypes.Time: TIME, - sqltypes.Enum: ENUM, - sqltypes.MatchType: _MatchType, - sqltypes.JSON: JSON, - sqltypes.JSON.JSONIndexType: JSONIndexType, - sqltypes.JSON.JSONPathType: JSONPathType, -} - -# Everything 3.23 through 5.1 excepting OpenGIS types. -ischema_names = { - "bigint": BIGINT, - "binary": BINARY, - "bit": BIT, - "blob": BLOB, - "boolean": BOOLEAN, - "char": CHAR, - "date": DATE, - "datetime": DATETIME, - "decimal": DECIMAL, - "double": DOUBLE, - "enum": ENUM, - "fixed": DECIMAL, - "float": FLOAT, - "int": INTEGER, - "integer": INTEGER, - "json": JSON, - "longblob": LONGBLOB, - "longtext": LONGTEXT, - "mediumblob": MEDIUMBLOB, - "mediumint": MEDIUMINT, - "mediumtext": MEDIUMTEXT, - "nchar": NCHAR, - "nvarchar": NVARCHAR, - "numeric": NUMERIC, - "set": SET, - "smallint": SMALLINT, - "text": TEXT, - "time": TIME, - "timestamp": TIMESTAMP, - "tinyblob": TINYBLOB, - "tinyint": TINYINT, - "tinytext": TINYTEXT, - "uuid": UUID, - "varbinary": VARBINARY, - "varchar": VARCHAR, - "year": YEAR, -} - - -class MySQLExecutionContext(default.DefaultExecutionContext): - def post_exec(self): - if ( - self.isdelete - and cast(SQLCompiler, self.compiled).effective_returning - and not self.cursor.description - ): - # All MySQL/mariadb drivers appear to not include - # cursor.description for DELETE..RETURNING with no rows if the - # WHERE criteria is a straight "false" condition such as our EMPTY - # IN condition. manufacture an empty result in this case (issue - # #10505) - # - # taken from cx_Oracle implementation - self.cursor_fetch_strategy = ( - _cursor.FullyBufferedCursorFetchStrategy( - self.cursor, - [ - (entry.keyname, None) - for entry in cast( - SQLCompiler, self.compiled - )._result_columns - ], - [], - ) - ) - - def create_server_side_cursor(self): - if self.dialect.supports_server_side_cursors: - return self._dbapi_connection.cursor(self.dialect._sscursor) - else: - raise NotImplementedError() - - def fire_sequence(self, seq, type_): - return self._execute_scalar( - ( - "select nextval(%s)" - % self.identifier_preparer.format_sequence(seq) - ), - type_, - ) - - -class MySQLCompiler(compiler.SQLCompiler): - render_table_with_column_in_update_from = True - """Overridden from base SQLCompiler value""" - - extract_map = compiler.SQLCompiler.extract_map.copy() - extract_map.update({"milliseconds": "millisecond"}) - - def default_from(self): - """Called when a ``SELECT`` statement has no froms, - and no ``FROM`` clause is to be appended. - - """ - if self.stack: - stmt = self.stack[-1]["selectable"] - if stmt._where_criteria: - return " FROM DUAL" - - return "" - - def visit_random_func(self, fn, **kw): - return "rand%s" % self.function_argspec(fn) - - def visit_rollup_func(self, fn, **kw): - clause = ", ".join( - elem._compiler_dispatch(self, **kw) for elem in fn.clauses - ) - return f"{clause} WITH ROLLUP" - - def visit_aggregate_strings_func(self, fn, **kw): - expr, delimeter = ( - elem._compiler_dispatch(self, **kw) for elem in fn.clauses - ) - return f"group_concat({expr} SEPARATOR {delimeter})" - - def visit_sequence(self, seq, **kw): - return "nextval(%s)" % self.preparer.format_sequence(seq) - - def visit_sysdate_func(self, fn, **kw): - return "SYSDATE()" - - def _render_json_extract_from_binary(self, binary, operator, **kw): - # note we are intentionally calling upon the process() calls in the - # order in which they appear in the SQL String as this is used - # by positional parameter rendering - - if binary.type._type_affinity is sqltypes.JSON: - return "JSON_EXTRACT(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - # for non-JSON, MySQL doesn't handle JSON null at all so it has to - # be explicit - case_expression = "CASE JSON_EXTRACT(%s, %s) WHEN 'null' THEN NULL" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - if binary.type._type_affinity is sqltypes.Integer: - type_expression = ( - "ELSE CAST(JSON_EXTRACT(%s, %s) AS SIGNED INTEGER)" - % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - ) - elif binary.type._type_affinity is sqltypes.Numeric: - if ( - binary.type.scale is not None - and binary.type.precision is not None - ): - # using DECIMAL here because MySQL does not recognize NUMERIC - type_expression = ( - "ELSE CAST(JSON_EXTRACT(%s, %s) AS DECIMAL(%s, %s))" - % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - binary.type.precision, - binary.type.scale, - ) - ) - else: - # FLOAT / REAL not added in MySQL til 8.0.17 - type_expression = ( - "ELSE JSON_EXTRACT(%s, %s)+0.0000000000000000000000" - % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - ) - elif binary.type._type_affinity is sqltypes.Boolean: - # the NULL handling is particularly weird with boolean, so - # explicitly return true/false constants - type_expression = "WHEN true THEN true ELSE false" - elif binary.type._type_affinity is sqltypes.String: - # (gord): this fails with a JSON value that's a four byte unicode - # string. SQLite has the same problem at the moment - # (zzzeek): I'm not really sure. let's take a look at a test case - # that hits each backend and maybe make a requires rule for it? - type_expression = "ELSE JSON_UNQUOTE(JSON_EXTRACT(%s, %s))" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - else: - # other affinity....this is not expected right now - type_expression = "ELSE JSON_EXTRACT(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - return case_expression + " " + type_expression + " END" - - def visit_json_getitem_op_binary(self, binary, operator, **kw): - return self._render_json_extract_from_binary(binary, operator, **kw) - - def visit_json_path_getitem_op_binary(self, binary, operator, **kw): - return self._render_json_extract_from_binary(binary, operator, **kw) - - def visit_on_duplicate_key_update(self, on_duplicate, **kw): - statement = self.current_executable - - if on_duplicate._parameter_ordering: - parameter_ordering = [ - coercions.expect(roles.DMLColumnRole, key) - for key in on_duplicate._parameter_ordering - ] - ordered_keys = set(parameter_ordering) - cols = [ - statement.table.c[key] - for key in parameter_ordering - if key in statement.table.c - ] + [c for c in statement.table.c if c.key not in ordered_keys] - else: - cols = statement.table.c - - clauses = [] - - requires_mysql8_alias = ( - self.dialect._requires_alias_for_on_duplicate_key - ) - - if requires_mysql8_alias: - if statement.table.name.lower() == "new": - _on_dup_alias_name = "new_1" - else: - _on_dup_alias_name = "new" - - # traverses through all table columns to preserve table column order - for column in (col for col in cols if col.key in on_duplicate.update): - val = on_duplicate.update[column.key] - - if coercions._is_literal(val): - val = elements.BindParameter(None, val, type_=column.type) - value_text = self.process(val.self_group(), use_schema=False) - else: - - def replace(obj): - if ( - isinstance(obj, elements.BindParameter) - and obj.type._isnull - ): - obj = obj._clone() - obj.type = column.type - return obj - elif ( - isinstance(obj, elements.ColumnClause) - and obj.table is on_duplicate.inserted_alias - ): - if requires_mysql8_alias: - column_literal_clause = ( - f"{_on_dup_alias_name}." - f"{self.preparer.quote(obj.name)}" - ) - else: - column_literal_clause = ( - f"VALUES({self.preparer.quote(obj.name)})" - ) - return literal_column(column_literal_clause) - else: - # element is not replaced - return None - - val = visitors.replacement_traverse(val, {}, replace) - value_text = self.process(val.self_group(), use_schema=False) - - name_text = self.preparer.quote(column.name) - clauses.append("%s = %s" % (name_text, value_text)) - - non_matching = set(on_duplicate.update) - {c.key for c in cols} - if non_matching: - util.warn( - "Additional column names not matching " - "any column keys in table '%s': %s" - % ( - self.statement.table.name, - (", ".join("'%s'" % c for c in non_matching)), - ) - ) - - if requires_mysql8_alias: - return ( - f"AS {_on_dup_alias_name} " - f"ON DUPLICATE KEY UPDATE {', '.join(clauses)}" - ) - else: - return f"ON DUPLICATE KEY UPDATE {', '.join(clauses)}" - - def visit_concat_op_expression_clauselist( - self, clauselist, operator, **kw - ): - return "concat(%s)" % ( - ", ".join(self.process(elem, **kw) for elem in clauselist.clauses) - ) - - def visit_concat_op_binary(self, binary, operator, **kw): - return "concat(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - _match_valid_flag_combinations = frozenset( - ( - # (boolean_mode, natural_language, query_expansion) - (False, False, False), - (True, False, False), - (False, True, False), - (False, False, True), - (False, True, True), - ) - ) - - _match_flag_expressions = ( - "IN BOOLEAN MODE", - "IN NATURAL LANGUAGE MODE", - "WITH QUERY EXPANSION", - ) - - def visit_mysql_match(self, element, **kw): - return self.visit_match_op_binary(element, element.operator, **kw) - - def visit_match_op_binary(self, binary, operator, **kw): - """ - Note that `mysql_boolean_mode` is enabled by default because of - backward compatibility - """ - - modifiers = binary.modifiers - - boolean_mode = modifiers.get("mysql_boolean_mode", True) - natural_language = modifiers.get("mysql_natural_language", False) - query_expansion = modifiers.get("mysql_query_expansion", False) - - flag_combination = (boolean_mode, natural_language, query_expansion) - - if flag_combination not in self._match_valid_flag_combinations: - flags = ( - "in_boolean_mode=%s" % boolean_mode, - "in_natural_language_mode=%s" % natural_language, - "with_query_expansion=%s" % query_expansion, - ) - - flags = ", ".join(flags) - - raise exc.CompileError("Invalid MySQL match flags: %s" % flags) - - match_clause = binary.left - match_clause = self.process(match_clause, **kw) - against_clause = self.process(binary.right, **kw) - - if any(flag_combination): - flag_expressions = compress( - self._match_flag_expressions, - flag_combination, - ) - - against_clause = [against_clause] - against_clause.extend(flag_expressions) - - against_clause = " ".join(against_clause) - - return "MATCH (%s) AGAINST (%s)" % (match_clause, against_clause) - - def get_from_hint_text(self, table, text): - return text - - def visit_typeclause(self, typeclause, type_=None, **kw): - if type_ is None: - type_ = typeclause.type.dialect_impl(self.dialect) - if isinstance(type_, sqltypes.TypeDecorator): - return self.visit_typeclause(typeclause, type_.impl, **kw) - elif isinstance(type_, sqltypes.Integer): - if getattr(type_, "unsigned", False): - return "UNSIGNED INTEGER" - else: - return "SIGNED INTEGER" - elif isinstance(type_, sqltypes.TIMESTAMP): - return "DATETIME" - elif isinstance( - type_, - ( - sqltypes.DECIMAL, - sqltypes.DateTime, - sqltypes.Date, - sqltypes.Time, - ), - ): - return self.dialect.type_compiler_instance.process(type_) - elif isinstance(type_, sqltypes.String) and not isinstance( - type_, (ENUM, SET) - ): - adapted = CHAR._adapt_string_for_cast(type_) - return self.dialect.type_compiler_instance.process(adapted) - elif isinstance(type_, sqltypes._Binary): - return "BINARY" - elif isinstance(type_, sqltypes.JSON): - return "JSON" - elif isinstance(type_, sqltypes.NUMERIC): - return self.dialect.type_compiler_instance.process(type_).replace( - "NUMERIC", "DECIMAL" - ) - elif ( - isinstance(type_, sqltypes.Float) - and self.dialect._support_float_cast - ): - return self.dialect.type_compiler_instance.process(type_) - else: - return None - - def visit_cast(self, cast, **kw): - type_ = self.process(cast.typeclause) - if type_ is None: - util.warn( - "Datatype %s does not support CAST on MySQL/MariaDb; " - "the CAST will be skipped." - % self.dialect.type_compiler_instance.process( - cast.typeclause.type - ) - ) - return self.process(cast.clause.self_group(), **kw) - - return "CAST(%s AS %s)" % (self.process(cast.clause, **kw), type_) - - def render_literal_value(self, value, type_): - value = super().render_literal_value(value, type_) - if self.dialect._backslash_escapes: - value = value.replace("\\", "\\\\") - return value - - # override native_boolean=False behavior here, as - # MySQL still supports native boolean - def visit_true(self, element, **kw): - return "true" - - def visit_false(self, element, **kw): - return "false" - - def get_select_precolumns(self, select, **kw): - """Add special MySQL keywords in place of DISTINCT. - - .. deprecated:: 1.4 This usage is deprecated. - :meth:`_expression.Select.prefix_with` should be used for special - keywords at the start of a SELECT. - - """ - if isinstance(select._distinct, str): - util.warn_deprecated( - "Sending string values for 'distinct' is deprecated in the " - "MySQL dialect and will be removed in a future release. " - "Please use :meth:`.Select.prefix_with` for special keywords " - "at the start of a SELECT statement", - version="1.4", - ) - return select._distinct.upper() + " " - - return super().get_select_precolumns(select, **kw) - - def visit_join(self, join, asfrom=False, from_linter=None, **kwargs): - if from_linter: - from_linter.edges.add((join.left, join.right)) - - if join.full: - join_type = " FULL OUTER JOIN " - elif join.isouter: - join_type = " LEFT OUTER JOIN " - else: - join_type = " INNER JOIN " - - return "".join( - ( - self.process( - join.left, asfrom=True, from_linter=from_linter, **kwargs - ), - join_type, - self.process( - join.right, asfrom=True, from_linter=from_linter, **kwargs - ), - " ON ", - self.process(join.onclause, from_linter=from_linter, **kwargs), - ) - ) - - def for_update_clause(self, select, **kw): - if select._for_update_arg.read: - tmp = " LOCK IN SHARE MODE" - else: - tmp = " FOR UPDATE" - - if select._for_update_arg.of and self.dialect.supports_for_update_of: - tables = util.OrderedSet() - for c in select._for_update_arg.of: - tables.update(sql_util.surface_selectables_only(c)) - - tmp += " OF " + ", ".join( - self.process(table, ashint=True, use_schema=False, **kw) - for table in tables - ) - - if select._for_update_arg.nowait: - tmp += " NOWAIT" - - if select._for_update_arg.skip_locked: - tmp += " SKIP LOCKED" - - return tmp - - def limit_clause(self, select, **kw): - # MySQL supports: - # LIMIT - # LIMIT , - # and in server versions > 3.3: - # LIMIT OFFSET - # The latter is more readable for offsets but we're stuck with the - # former until we can refine dialects by server revision. - - limit_clause, offset_clause = ( - select._limit_clause, - select._offset_clause, - ) - - if limit_clause is None and offset_clause is None: - return "" - elif offset_clause is not None: - # As suggested by the MySQL docs, need to apply an - # artificial limit if one wasn't provided - # https://dev.mysql.com/doc/refman/5.0/en/select.html - if limit_clause is None: - # TODO: remove ?? - # hardwire the upper limit. Currently - # needed consistent with the usage of the upper - # bound as part of MySQL's "syntax" for OFFSET with - # no LIMIT. - return " \n LIMIT %s, %s" % ( - self.process(offset_clause, **kw), - "18446744073709551615", - ) - else: - return " \n LIMIT %s, %s" % ( - self.process(offset_clause, **kw), - self.process(limit_clause, **kw), - ) - else: - # No offset provided, so just use the limit - return " \n LIMIT %s" % (self.process(limit_clause, **kw),) - - def update_limit_clause(self, update_stmt): - limit = update_stmt.kwargs.get("%s_limit" % self.dialect.name, None) - if limit: - return "LIMIT %s" % limit - else: - return None - - def update_tables_clause(self, update_stmt, from_table, extra_froms, **kw): - kw["asfrom"] = True - return ", ".join( - t._compiler_dispatch(self, **kw) - for t in [from_table] + list(extra_froms) - ) - - def update_from_clause( - self, update_stmt, from_table, extra_froms, from_hints, **kw - ): - return None - - def delete_table_clause(self, delete_stmt, from_table, extra_froms, **kw): - """If we have extra froms make sure we render any alias as hint.""" - ashint = False - if extra_froms: - ashint = True - return from_table._compiler_dispatch( - self, asfrom=True, iscrud=True, ashint=ashint, **kw - ) - - def delete_extra_from_clause( - self, delete_stmt, from_table, extra_froms, from_hints, **kw - ): - """Render the DELETE .. USING clause specific to MySQL.""" - kw["asfrom"] = True - return "USING " + ", ".join( - t._compiler_dispatch(self, fromhints=from_hints, **kw) - for t in [from_table] + extra_froms - ) - - def visit_empty_set_expr(self, element_types, **kw): - return ( - "SELECT %(outer)s FROM (SELECT %(inner)s) " - "as _empty_set WHERE 1!=1" - % { - "inner": ", ".join( - "1 AS _in_%s" % idx - for idx, type_ in enumerate(element_types) - ), - "outer": ", ".join( - "_in_%s" % idx for idx, type_ in enumerate(element_types) - ), - } - ) - - def visit_is_distinct_from_binary(self, binary, operator, **kw): - return "NOT (%s <=> %s)" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_is_not_distinct_from_binary(self, binary, operator, **kw): - return "%s <=> %s" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def _mariadb_regexp_flags(self, flags, pattern, **kw): - return "CONCAT('(?', %s, ')', %s)" % ( - self.render_literal_value(flags, sqltypes.STRINGTYPE), - self.process(pattern, **kw), - ) - - def _regexp_match(self, op_string, binary, operator, **kw): - flags = binary.modifiers["flags"] - if flags is None: - return self._generate_generic_binary(binary, op_string, **kw) - elif self.dialect.is_mariadb: - return "%s%s%s" % ( - self.process(binary.left, **kw), - op_string, - self._mariadb_regexp_flags(flags, binary.right), - ) - else: - text = "REGEXP_LIKE(%s, %s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - self.render_literal_value(flags, sqltypes.STRINGTYPE), - ) - if op_string == " NOT REGEXP ": - return "NOT %s" % text - else: - return text - - def visit_regexp_match_op_binary(self, binary, operator, **kw): - return self._regexp_match(" REGEXP ", binary, operator, **kw) - - def visit_not_regexp_match_op_binary(self, binary, operator, **kw): - return self._regexp_match(" NOT REGEXP ", binary, operator, **kw) - - def visit_regexp_replace_op_binary(self, binary, operator, **kw): - flags = binary.modifiers["flags"] - if flags is None: - return "REGEXP_REPLACE(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - elif self.dialect.is_mariadb: - return "REGEXP_REPLACE(%s, %s, %s)" % ( - self.process(binary.left, **kw), - self._mariadb_regexp_flags(flags, binary.right.clauses[0]), - self.process(binary.right.clauses[1], **kw), - ) - else: - return "REGEXP_REPLACE(%s, %s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - self.render_literal_value(flags, sqltypes.STRINGTYPE), - ) - - -class MySQLDDLCompiler(compiler.DDLCompiler): - def get_column_specification(self, column, **kw): - """Builds column DDL.""" - if ( - self.dialect.is_mariadb is True - and column.computed is not None - and column._user_defined_nullable is SchemaConst.NULL_UNSPECIFIED - ): - column.nullable = True - colspec = [ - self.preparer.format_column(column), - self.dialect.type_compiler_instance.process( - column.type, type_expression=column - ), - ] - - if column.computed is not None: - colspec.append(self.process(column.computed)) - - is_timestamp = isinstance( - column.type._unwrapped_dialect_impl(self.dialect), - sqltypes.TIMESTAMP, - ) - - if not column.nullable: - colspec.append("NOT NULL") - - # see: https://docs.sqlalchemy.org/en/latest/dialects/mysql.html#mysql_timestamp_null # noqa - elif column.nullable and is_timestamp: - colspec.append("NULL") - - comment = column.comment - if comment is not None: - literal = self.sql_compiler.render_literal_value( - comment, sqltypes.String() - ) - colspec.append("COMMENT " + literal) - - if ( - column.table is not None - and column is column.table._autoincrement_column - and ( - column.server_default is None - or isinstance(column.server_default, sa_schema.Identity) - ) - and not ( - self.dialect.supports_sequences - and isinstance(column.default, sa_schema.Sequence) - and not column.default.optional - ) - ): - colspec.append("AUTO_INCREMENT") - else: - default = self.get_column_default_string(column) - if default is not None: - colspec.append("DEFAULT " + default) - return " ".join(colspec) - - def post_create_table(self, table): - """Build table-level CREATE options like ENGINE and COLLATE.""" - - table_opts = [] - - opts = { - k[len(self.dialect.name) + 1 :].upper(): v - for k, v in table.kwargs.items() - if k.startswith("%s_" % self.dialect.name) - } - - if table.comment is not None: - opts["COMMENT"] = table.comment - - partition_options = [ - "PARTITION_BY", - "PARTITIONS", - "SUBPARTITIONS", - "SUBPARTITION_BY", - ] - - nonpart_options = set(opts).difference(partition_options) - part_options = set(opts).intersection(partition_options) - - for opt in topological.sort( - [ - ("DEFAULT_CHARSET", "COLLATE"), - ("DEFAULT_CHARACTER_SET", "COLLATE"), - ("CHARSET", "COLLATE"), - ("CHARACTER_SET", "COLLATE"), - ], - nonpart_options, - ): - arg = opts[opt] - if opt in _reflection._options_of_type_string: - arg = self.sql_compiler.render_literal_value( - arg, sqltypes.String() - ) - - if opt in ( - "DATA_DIRECTORY", - "INDEX_DIRECTORY", - "DEFAULT_CHARACTER_SET", - "CHARACTER_SET", - "DEFAULT_CHARSET", - "DEFAULT_COLLATE", - ): - opt = opt.replace("_", " ") - - joiner = "=" - if opt in ( - "TABLESPACE", - "DEFAULT CHARACTER SET", - "CHARACTER SET", - "COLLATE", - ): - joiner = " " - - table_opts.append(joiner.join((opt, arg))) - - for opt in topological.sort( - [ - ("PARTITION_BY", "PARTITIONS"), - ("PARTITION_BY", "SUBPARTITION_BY"), - ("PARTITION_BY", "SUBPARTITIONS"), - ("PARTITIONS", "SUBPARTITIONS"), - ("PARTITIONS", "SUBPARTITION_BY"), - ("SUBPARTITION_BY", "SUBPARTITIONS"), - ], - part_options, - ): - arg = opts[opt] - if opt in _reflection._options_of_type_string: - arg = self.sql_compiler.render_literal_value( - arg, sqltypes.String() - ) - - opt = opt.replace("_", " ") - joiner = " " - - table_opts.append(joiner.join((opt, arg))) - - return " ".join(table_opts) - - def visit_create_index(self, create, **kw): - index = create.element - self._verify_index_table(index) - preparer = self.preparer - table = preparer.format_table(index.table) - - columns = [ - self.sql_compiler.process( - ( - elements.Grouping(expr) - if ( - isinstance(expr, elements.BinaryExpression) - or ( - isinstance(expr, elements.UnaryExpression) - and expr.modifier - not in (operators.desc_op, operators.asc_op) - ) - or isinstance(expr, functions.FunctionElement) - ) - else expr - ), - include_table=False, - literal_binds=True, - ) - for expr in index.expressions - ] - - name = self._prepared_index_name(index) - - text = "CREATE " - if index.unique: - text += "UNIQUE " - - index_prefix = index.kwargs.get("%s_prefix" % self.dialect.name, None) - if index_prefix: - text += index_prefix + " " - - text += "INDEX " - if create.if_not_exists: - text += "IF NOT EXISTS " - text += "%s ON %s " % (name, table) - - length = index.dialect_options[self.dialect.name]["length"] - if length is not None: - if isinstance(length, dict): - # length value can be a (column_name --> integer value) - # mapping specifying the prefix length for each column of the - # index - columns = ", ".join( - ( - "%s(%d)" % (expr, length[col.name]) - if col.name in length - else ( - "%s(%d)" % (expr, length[expr]) - if expr in length - else "%s" % expr - ) - ) - for col, expr in zip(index.expressions, columns) - ) - else: - # or can be an integer value specifying the same - # prefix length for all columns of the index - columns = ", ".join( - "%s(%d)" % (col, length) for col in columns - ) - else: - columns = ", ".join(columns) - text += "(%s)" % columns - - parser = index.dialect_options["mysql"]["with_parser"] - if parser is not None: - text += " WITH PARSER %s" % (parser,) - - using = index.dialect_options["mysql"]["using"] - if using is not None: - text += " USING %s" % (preparer.quote(using)) - - return text - - def visit_primary_key_constraint(self, constraint, **kw): - text = super().visit_primary_key_constraint(constraint) - using = constraint.dialect_options["mysql"]["using"] - if using: - text += " USING %s" % (self.preparer.quote(using)) - return text - - def visit_drop_index(self, drop, **kw): - index = drop.element - text = "\nDROP INDEX " - if drop.if_exists: - text += "IF EXISTS " - - return text + "%s ON %s" % ( - self._prepared_index_name(index, include_schema=False), - self.preparer.format_table(index.table), - ) - - def visit_drop_constraint(self, drop, **kw): - constraint = drop.element - if isinstance(constraint, sa_schema.ForeignKeyConstraint): - qual = "FOREIGN KEY " - const = self.preparer.format_constraint(constraint) - elif isinstance(constraint, sa_schema.PrimaryKeyConstraint): - qual = "PRIMARY KEY " - const = "" - elif isinstance(constraint, sa_schema.UniqueConstraint): - qual = "INDEX " - const = self.preparer.format_constraint(constraint) - elif isinstance(constraint, sa_schema.CheckConstraint): - if self.dialect.is_mariadb: - qual = "CONSTRAINT " - else: - qual = "CHECK " - const = self.preparer.format_constraint(constraint) - else: - qual = "" - const = self.preparer.format_constraint(constraint) - return "ALTER TABLE %s DROP %s%s" % ( - self.preparer.format_table(constraint.table), - qual, - const, - ) - - def define_constraint_match(self, constraint): - if constraint.match is not None: - raise exc.CompileError( - "MySQL ignores the 'MATCH' keyword while at the same time " - "causes ON UPDATE/ON DELETE clauses to be ignored." - ) - return "" - - def visit_set_table_comment(self, create, **kw): - return "ALTER TABLE %s COMMENT %s" % ( - self.preparer.format_table(create.element), - self.sql_compiler.render_literal_value( - create.element.comment, sqltypes.String() - ), - ) - - def visit_drop_table_comment(self, create, **kw): - return "ALTER TABLE %s COMMENT ''" % ( - self.preparer.format_table(create.element) - ) - - def visit_set_column_comment(self, create, **kw): - return "ALTER TABLE %s CHANGE %s %s" % ( - self.preparer.format_table(create.element.table), - self.preparer.format_column(create.element), - self.get_column_specification(create.element), - ) - - -class MySQLTypeCompiler(compiler.GenericTypeCompiler): - def _extend_numeric(self, type_, spec): - "Extend a numeric-type declaration with MySQL specific extensions." - - if not self._mysql_type(type_): - return spec - - if type_.unsigned: - spec += " UNSIGNED" - if type_.zerofill: - spec += " ZEROFILL" - return spec - - def _extend_string(self, type_, defaults, spec): - """Extend a string-type declaration with standard SQL CHARACTER SET / - COLLATE annotations and MySQL specific extensions. - - """ - - def attr(name): - return getattr(type_, name, defaults.get(name)) - - if attr("charset"): - charset = "CHARACTER SET %s" % attr("charset") - elif attr("ascii"): - charset = "ASCII" - elif attr("unicode"): - charset = "UNICODE" - else: - charset = None - - if attr("collation"): - collation = "COLLATE %s" % type_.collation - elif attr("binary"): - collation = "BINARY" - else: - collation = None - - if attr("national"): - # NATIONAL (aka NCHAR/NVARCHAR) trumps charsets. - return " ".join( - [c for c in ("NATIONAL", spec, collation) if c is not None] - ) - return " ".join( - [c for c in (spec, charset, collation) if c is not None] - ) - - def _mysql_type(self, type_): - return isinstance(type_, (_StringType, _NumericType)) - - def visit_NUMERIC(self, type_, **kw): - if type_.precision is None: - return self._extend_numeric(type_, "NUMERIC") - elif type_.scale is None: - return self._extend_numeric( - type_, - "NUMERIC(%(precision)s)" % {"precision": type_.precision}, - ) - else: - return self._extend_numeric( - type_, - "NUMERIC(%(precision)s, %(scale)s)" - % {"precision": type_.precision, "scale": type_.scale}, - ) - - def visit_DECIMAL(self, type_, **kw): - if type_.precision is None: - return self._extend_numeric(type_, "DECIMAL") - elif type_.scale is None: - return self._extend_numeric( - type_, - "DECIMAL(%(precision)s)" % {"precision": type_.precision}, - ) - else: - return self._extend_numeric( - type_, - "DECIMAL(%(precision)s, %(scale)s)" - % {"precision": type_.precision, "scale": type_.scale}, - ) - - def visit_DOUBLE(self, type_, **kw): - if type_.precision is not None and type_.scale is not None: - return self._extend_numeric( - type_, - "DOUBLE(%(precision)s, %(scale)s)" - % {"precision": type_.precision, "scale": type_.scale}, - ) - else: - return self._extend_numeric(type_, "DOUBLE") - - def visit_REAL(self, type_, **kw): - if type_.precision is not None and type_.scale is not None: - return self._extend_numeric( - type_, - "REAL(%(precision)s, %(scale)s)" - % {"precision": type_.precision, "scale": type_.scale}, - ) - else: - return self._extend_numeric(type_, "REAL") - - def visit_FLOAT(self, type_, **kw): - if ( - self._mysql_type(type_) - and type_.scale is not None - and type_.precision is not None - ): - return self._extend_numeric( - type_, "FLOAT(%s, %s)" % (type_.precision, type_.scale) - ) - elif type_.precision is not None: - return self._extend_numeric( - type_, "FLOAT(%s)" % (type_.precision,) - ) - else: - return self._extend_numeric(type_, "FLOAT") - - def visit_INTEGER(self, type_, **kw): - if self._mysql_type(type_) and type_.display_width is not None: - return self._extend_numeric( - type_, - "INTEGER(%(display_width)s)" - % {"display_width": type_.display_width}, - ) - else: - return self._extend_numeric(type_, "INTEGER") - - def visit_BIGINT(self, type_, **kw): - if self._mysql_type(type_) and type_.display_width is not None: - return self._extend_numeric( - type_, - "BIGINT(%(display_width)s)" - % {"display_width": type_.display_width}, - ) - else: - return self._extend_numeric(type_, "BIGINT") - - def visit_MEDIUMINT(self, type_, **kw): - if self._mysql_type(type_) and type_.display_width is not None: - return self._extend_numeric( - type_, - "MEDIUMINT(%(display_width)s)" - % {"display_width": type_.display_width}, - ) - else: - return self._extend_numeric(type_, "MEDIUMINT") - - def visit_TINYINT(self, type_, **kw): - if self._mysql_type(type_) and type_.display_width is not None: - return self._extend_numeric( - type_, "TINYINT(%s)" % type_.display_width - ) - else: - return self._extend_numeric(type_, "TINYINT") - - def visit_SMALLINT(self, type_, **kw): - if self._mysql_type(type_) and type_.display_width is not None: - return self._extend_numeric( - type_, - "SMALLINT(%(display_width)s)" - % {"display_width": type_.display_width}, - ) - else: - return self._extend_numeric(type_, "SMALLINT") - - def visit_BIT(self, type_, **kw): - if type_.length is not None: - return "BIT(%s)" % type_.length - else: - return "BIT" - - def visit_DATETIME(self, type_, **kw): - if getattr(type_, "fsp", None): - return "DATETIME(%d)" % type_.fsp - else: - return "DATETIME" - - def visit_DATE(self, type_, **kw): - return "DATE" - - def visit_TIME(self, type_, **kw): - if getattr(type_, "fsp", None): - return "TIME(%d)" % type_.fsp - else: - return "TIME" - - def visit_TIMESTAMP(self, type_, **kw): - if getattr(type_, "fsp", None): - return "TIMESTAMP(%d)" % type_.fsp - else: - return "TIMESTAMP" - - def visit_YEAR(self, type_, **kw): - if type_.display_width is None: - return "YEAR" - else: - return "YEAR(%s)" % type_.display_width - - def visit_TEXT(self, type_, **kw): - if type_.length is not None: - return self._extend_string(type_, {}, "TEXT(%d)" % type_.length) - else: - return self._extend_string(type_, {}, "TEXT") - - def visit_TINYTEXT(self, type_, **kw): - return self._extend_string(type_, {}, "TINYTEXT") - - def visit_MEDIUMTEXT(self, type_, **kw): - return self._extend_string(type_, {}, "MEDIUMTEXT") - - def visit_LONGTEXT(self, type_, **kw): - return self._extend_string(type_, {}, "LONGTEXT") - - def visit_VARCHAR(self, type_, **kw): - if type_.length is not None: - return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length) - else: - raise exc.CompileError( - "VARCHAR requires a length on dialect %s" % self.dialect.name - ) - - def visit_CHAR(self, type_, **kw): - if type_.length is not None: - return self._extend_string( - type_, {}, "CHAR(%(length)s)" % {"length": type_.length} - ) - else: - return self._extend_string(type_, {}, "CHAR") - - def visit_NVARCHAR(self, type_, **kw): - # We'll actually generate the equiv. "NATIONAL VARCHAR" instead - # of "NVARCHAR". - if type_.length is not None: - return self._extend_string( - type_, - {"national": True}, - "VARCHAR(%(length)s)" % {"length": type_.length}, - ) - else: - raise exc.CompileError( - "NVARCHAR requires a length on dialect %s" % self.dialect.name - ) - - def visit_NCHAR(self, type_, **kw): - # We'll actually generate the equiv. - # "NATIONAL CHAR" instead of "NCHAR". - if type_.length is not None: - return self._extend_string( - type_, - {"national": True}, - "CHAR(%(length)s)" % {"length": type_.length}, - ) - else: - return self._extend_string(type_, {"national": True}, "CHAR") - - def visit_UUID(self, type_, **kw): - return "UUID" - - def visit_VARBINARY(self, type_, **kw): - return "VARBINARY(%d)" % type_.length - - def visit_JSON(self, type_, **kw): - return "JSON" - - def visit_large_binary(self, type_, **kw): - return self.visit_BLOB(type_) - - def visit_enum(self, type_, **kw): - if not type_.native_enum: - return super().visit_enum(type_) - else: - return self._visit_enumerated_values("ENUM", type_, type_.enums) - - def visit_BLOB(self, type_, **kw): - if type_.length is not None: - return "BLOB(%d)" % type_.length - else: - return "BLOB" - - def visit_TINYBLOB(self, type_, **kw): - return "TINYBLOB" - - def visit_MEDIUMBLOB(self, type_, **kw): - return "MEDIUMBLOB" - - def visit_LONGBLOB(self, type_, **kw): - return "LONGBLOB" - - def _visit_enumerated_values(self, name, type_, enumerated_values): - quoted_enums = [] - for e in enumerated_values: - quoted_enums.append("'%s'" % e.replace("'", "''")) - return self._extend_string( - type_, {}, "%s(%s)" % (name, ",".join(quoted_enums)) - ) - - def visit_ENUM(self, type_, **kw): - return self._visit_enumerated_values("ENUM", type_, type_.enums) - - def visit_SET(self, type_, **kw): - return self._visit_enumerated_values("SET", type_, type_.values) - - def visit_BOOLEAN(self, type_, **kw): - return "BOOL" - - -class MySQLIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = RESERVED_WORDS_MYSQL - - def __init__(self, dialect, server_ansiquotes=False, **kw): - if not server_ansiquotes: - quote = "`" - else: - quote = '"' - - super().__init__(dialect, initial_quote=quote, escape_quote=quote) - - def _quote_free_identifiers(self, *ids): - """Unilaterally identifier-quote any number of strings.""" - - return tuple([self.quote_identifier(i) for i in ids if i is not None]) - - -class MariaDBIdentifierPreparer(MySQLIdentifierPreparer): - reserved_words = RESERVED_WORDS_MARIADB - - -@log.class_logger -class MySQLDialect(default.DefaultDialect): - """Details of the MySQL dialect. - Not used directly in application code. - """ - - name = "mysql" - supports_statement_cache = True - - supports_alter = True - - # MySQL has no true "boolean" type; we - # allow for the "true" and "false" keywords, however - supports_native_boolean = False - - # identifiers are 64, however aliases can be 255... - max_identifier_length = 255 - max_index_name_length = 64 - max_constraint_name_length = 64 - - div_is_floordiv = False - - supports_native_enum = True - - returns_native_bytes = True - - supports_sequences = False # default for MySQL ... - # ... may be updated to True for MariaDB 10.3+ in initialize() - - sequences_optional = False - - supports_for_update_of = False # default for MySQL ... - # ... may be updated to True for MySQL 8+ in initialize() - - _requires_alias_for_on_duplicate_key = False # Only available ... - # ... in MySQL 8+ - - # MySQL doesn't support "DEFAULT VALUES" but *does* support - # "VALUES (DEFAULT)" - supports_default_values = False - supports_default_metavalue = True - - use_insertmanyvalues: bool = True - insertmanyvalues_implicit_sentinel = ( - InsertmanyvaluesSentinelOpts.ANY_AUTOINCREMENT - ) - - supports_sane_rowcount = True - supports_sane_multi_rowcount = False - supports_multivalues_insert = True - insert_null_pk_still_autoincrements = True - - supports_comments = True - inline_comments = True - default_paramstyle = "format" - colspecs = colspecs - - cte_follows_insert = True - - statement_compiler = MySQLCompiler - ddl_compiler = MySQLDDLCompiler - type_compiler_cls = MySQLTypeCompiler - ischema_names = ischema_names - preparer = MySQLIdentifierPreparer - - is_mariadb = False - _mariadb_normalized_version_info = None - - # default SQL compilation settings - - # these are modified upon initialize(), - # i.e. first connect - _backslash_escapes = True - _server_ansiquotes = False - - construct_arguments = [ - (sa_schema.Table, {"*": None}), - (sql.Update, {"limit": None}), - (sa_schema.PrimaryKeyConstraint, {"using": None}), - ( - sa_schema.Index, - { - "using": None, - "length": None, - "prefix": None, - "with_parser": None, - }, - ), - ] - - def __init__( - self, - json_serializer=None, - json_deserializer=None, - is_mariadb=None, - **kwargs, - ): - kwargs.pop("use_ansiquotes", None) # legacy - default.DefaultDialect.__init__(self, **kwargs) - self._json_serializer = json_serializer - self._json_deserializer = json_deserializer - self._set_mariadb(is_mariadb, None) - - def get_isolation_level_values(self, dbapi_conn): - return ( - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - ) - - def set_isolation_level(self, dbapi_connection, level): - cursor = dbapi_connection.cursor() - cursor.execute(f"SET SESSION TRANSACTION ISOLATION LEVEL {level}") - cursor.execute("COMMIT") - cursor.close() - - def get_isolation_level(self, dbapi_connection): - cursor = dbapi_connection.cursor() - if self._is_mysql and self.server_version_info >= (5, 7, 20): - cursor.execute("SELECT @@transaction_isolation") - else: - cursor.execute("SELECT @@tx_isolation") - row = cursor.fetchone() - if row is None: - util.warn( - "Could not retrieve transaction isolation level for MySQL " - "connection." - ) - raise NotImplementedError() - val = row[0] - cursor.close() - if isinstance(val, bytes): - val = val.decode() - return val.upper().replace("-", " ") - - @classmethod - def _is_mariadb_from_url(cls, url): - dbapi = cls.import_dbapi() - dialect = cls(dbapi=dbapi) - - cargs, cparams = dialect.create_connect_args(url) - conn = dialect.connect(*cargs, **cparams) - try: - cursor = conn.cursor() - cursor.execute("SELECT VERSION() LIKE '%MariaDB%'") - val = cursor.fetchone()[0] - except: - raise - else: - return bool(val) - finally: - conn.close() - - def _get_server_version_info(self, connection): - # get database server version info explicitly over the wire - # to avoid proxy servers like MaxScale getting in the - # way with their own values, see #4205 - dbapi_con = connection.connection - cursor = dbapi_con.cursor() - cursor.execute("SELECT VERSION()") - val = cursor.fetchone()[0] - cursor.close() - if isinstance(val, bytes): - val = val.decode() - - return self._parse_server_version(val) - - def _parse_server_version(self, val): - version = [] - is_mariadb = False - - r = re.compile(r"[.\-+]") - tokens = r.split(val) - for token in tokens: - parsed_token = re.match( - r"^(?:(\d+)(?:a|b|c)?|(MariaDB\w*))$", token - ) - if not parsed_token: - continue - elif parsed_token.group(2): - self._mariadb_normalized_version_info = tuple(version[-3:]) - is_mariadb = True - else: - digit = int(parsed_token.group(1)) - version.append(digit) - - server_version_info = tuple(version) - - self._set_mariadb( - server_version_info and is_mariadb, server_version_info - ) - - if not is_mariadb: - self._mariadb_normalized_version_info = server_version_info - - if server_version_info < (5, 0, 2): - raise NotImplementedError( - "the MySQL/MariaDB dialect supports server " - "version info 5.0.2 and above." - ) - - # setting it here to help w the test suite - self.server_version_info = server_version_info - return server_version_info - - def _set_mariadb(self, is_mariadb, server_version_info): - if is_mariadb is None: - return - - if not is_mariadb and self.is_mariadb: - raise exc.InvalidRequestError( - "MySQL version %s is not a MariaDB variant." - % (".".join(map(str, server_version_info)),) - ) - if is_mariadb: - self.preparer = MariaDBIdentifierPreparer - # this would have been set by the default dialect already, - # so set it again - self.identifier_preparer = self.preparer(self) - - # this will be updated on first connect in initialize() - # if using older mariadb version - self.delete_returning = True - self.insert_returning = True - - self.is_mariadb = is_mariadb - - def do_begin_twophase(self, connection, xid): - connection.execute(sql.text("XA BEGIN :xid"), dict(xid=xid)) - - def do_prepare_twophase(self, connection, xid): - connection.execute(sql.text("XA END :xid"), dict(xid=xid)) - connection.execute(sql.text("XA PREPARE :xid"), dict(xid=xid)) - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if not is_prepared: - connection.execute(sql.text("XA END :xid"), dict(xid=xid)) - connection.execute(sql.text("XA ROLLBACK :xid"), dict(xid=xid)) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if not is_prepared: - self.do_prepare_twophase(connection, xid) - connection.execute(sql.text("XA COMMIT :xid"), dict(xid=xid)) - - def do_recover_twophase(self, connection): - resultset = connection.exec_driver_sql("XA RECOVER") - return [row["data"][0 : row["gtrid_length"]] for row in resultset] - - def is_disconnect(self, e, connection, cursor): - if isinstance( - e, - ( - self.dbapi.OperationalError, - self.dbapi.ProgrammingError, - self.dbapi.InterfaceError, - ), - ) and self._extract_error_code(e) in ( - 1927, - 2006, - 2013, - 2014, - 2045, - 2055, - 4031, - ): - return True - elif isinstance( - e, (self.dbapi.InterfaceError, self.dbapi.InternalError) - ): - # if underlying connection is closed, - # this is the error you get - return "(0, '')" in str(e) - else: - return False - - def _compat_fetchall(self, rp, charset=None): - """Proxy result rows to smooth over MySQL-Python driver - inconsistencies.""" - - return [_DecodingRow(row, charset) for row in rp.fetchall()] - - def _compat_fetchone(self, rp, charset=None): - """Proxy a result row to smooth over MySQL-Python driver - inconsistencies.""" - - row = rp.fetchone() - if row: - return _DecodingRow(row, charset) - else: - return None - - def _compat_first(self, rp, charset=None): - """Proxy a result row to smooth over MySQL-Python driver - inconsistencies.""" - - row = rp.first() - if row: - return _DecodingRow(row, charset) - else: - return None - - def _extract_error_code(self, exception): - raise NotImplementedError() - - def _get_default_schema_name(self, connection): - return connection.exec_driver_sql("SELECT DATABASE()").scalar() - - @reflection.cache - def has_table(self, connection, table_name, schema=None, **kw): - self._ensure_has_table_connection(connection) - - if schema is None: - schema = self.default_schema_name - - assert schema is not None - - full_name = ".".join( - self.identifier_preparer._quote_free_identifiers( - schema, table_name - ) - ) - - # DESCRIBE *must* be used because there is no information schema - # table that returns information on temp tables that is consistently - # available on MariaDB / MySQL / engine-agnostic etc. - # therefore we have no choice but to use DESCRIBE and an error catch - # to detect "False". See issue #9058 - - try: - with connection.exec_driver_sql( - f"DESCRIBE {full_name}", - execution_options={"skip_user_error_events": True}, - ) as rs: - return rs.fetchone() is not None - except exc.DBAPIError as e: - # https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html # noqa: E501 - # there are a lot of codes that *may* pop up here at some point - # but we continue to be fairly conservative. We include: - # 1146: Table '%s.%s' doesn't exist - what every MySQL has emitted - # for decades - # - # mysql 8 suddenly started emitting: - # 1049: Unknown database '%s' - for nonexistent schema - # - # also added: - # 1051: Unknown table '%s' - not known to emit - # - # there's more "doesn't exist" kinds of messages but they are - # less clear if mysql 8 would suddenly start using one of those - if self._extract_error_code(e.orig) in (1146, 1049, 1051): - return False - raise - - @reflection.cache - def has_sequence(self, connection, sequence_name, schema=None, **kw): - if not self.supports_sequences: - self._sequences_not_supported() - if not schema: - schema = self.default_schema_name - # MariaDB implements sequences as a special type of table - # - cursor = connection.execute( - sql.text( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " - "WHERE TABLE_TYPE='SEQUENCE' and TABLE_NAME=:name AND " - "TABLE_SCHEMA=:schema_name" - ), - dict( - name=str(sequence_name), - schema_name=str(schema), - ), - ) - return cursor.first() is not None - - def _sequences_not_supported(self): - raise NotImplementedError( - "Sequences are supported only by the " - "MariaDB series 10.3 or greater" - ) - - @reflection.cache - def get_sequence_names(self, connection, schema=None, **kw): - if not self.supports_sequences: - self._sequences_not_supported() - if not schema: - schema = self.default_schema_name - # MariaDB implements sequences as a special type of table - cursor = connection.execute( - sql.text( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " - "WHERE TABLE_TYPE='SEQUENCE' and TABLE_SCHEMA=:schema_name" - ), - dict(schema_name=schema), - ) - return [ - row[0] - for row in self._compat_fetchall( - cursor, charset=self._connection_charset - ) - ] - - def initialize(self, connection): - # this is driver-based, does not need server version info - # and is fairly critical for even basic SQL operations - self._connection_charset = self._detect_charset(connection) - - # call super().initialize() because we need to have - # server_version_info set up. in 1.4 under python 2 only this does the - # "check unicode returns" thing, which is the one area that some - # SQL gets compiled within initialize() currently - default.DefaultDialect.initialize(self, connection) - - self._detect_sql_mode(connection) - self._detect_ansiquotes(connection) # depends on sql mode - self._detect_casing(connection) - if self._server_ansiquotes: - # if ansiquotes == True, build a new IdentifierPreparer - # with the new setting - self.identifier_preparer = self.preparer( - self, server_ansiquotes=self._server_ansiquotes - ) - - self.supports_sequences = ( - self.is_mariadb and self.server_version_info >= (10, 3) - ) - - self.supports_for_update_of = ( - self._is_mysql and self.server_version_info >= (8,) - ) - - self._needs_correct_for_88718_96365 = ( - not self.is_mariadb and self.server_version_info >= (8,) - ) - - self.delete_returning = ( - self.is_mariadb and self.server_version_info >= (10, 0, 5) - ) - - self.insert_returning = ( - self.is_mariadb and self.server_version_info >= (10, 5) - ) - - self._requires_alias_for_on_duplicate_key = ( - self._is_mysql and self.server_version_info >= (8, 0, 20) - ) - - self._warn_for_known_db_issues() - - def _warn_for_known_db_issues(self): - if self.is_mariadb: - mdb_version = self._mariadb_normalized_version_info - if mdb_version > (10, 2) and mdb_version < (10, 2, 9): - util.warn( - "MariaDB %r before 10.2.9 has known issues regarding " - "CHECK constraints, which impact handling of NULL values " - "with SQLAlchemy's boolean datatype (MDEV-13596). An " - "additional issue prevents proper migrations of columns " - "with CHECK constraints (MDEV-11114). Please upgrade to " - "MariaDB 10.2.9 or greater, or use the MariaDB 10.1 " - "series, to avoid these issues." % (mdb_version,) - ) - - @property - def _support_float_cast(self): - if not self.server_version_info: - return False - elif self.is_mariadb: - # ref https://mariadb.com/kb/en/mariadb-1045-release-notes/ - return self.server_version_info >= (10, 4, 5) - else: - # ref https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-17.html#mysqld-8-0-17-feature # noqa - return self.server_version_info >= (8, 0, 17) - - @property - def _is_mariadb(self): - return self.is_mariadb - - @property - def _is_mysql(self): - return not self.is_mariadb - - @property - def _is_mariadb_102(self): - return self.is_mariadb and self._mariadb_normalized_version_info > ( - 10, - 2, - ) - - @reflection.cache - def get_schema_names(self, connection, **kw): - rp = connection.exec_driver_sql("SHOW schemas") - return [r[0] for r in rp] - - @reflection.cache - def get_table_names(self, connection, schema=None, **kw): - """Return a Unicode SHOW TABLES from a given schema.""" - if schema is not None: - current_schema = schema - else: - current_schema = self.default_schema_name - - charset = self._connection_charset - - rp = connection.exec_driver_sql( - "SHOW FULL TABLES FROM %s" - % self.identifier_preparer.quote_identifier(current_schema) - ) - - return [ - row[0] - for row in self._compat_fetchall(rp, charset=charset) - if row[1] == "BASE TABLE" - ] - - @reflection.cache - def get_view_names(self, connection, schema=None, **kw): - if schema is None: - schema = self.default_schema_name - charset = self._connection_charset - rp = connection.exec_driver_sql( - "SHOW FULL TABLES FROM %s" - % self.identifier_preparer.quote_identifier(schema) - ) - return [ - row[0] - for row in self._compat_fetchall(rp, charset=charset) - if row[1] in ("VIEW", "SYSTEM VIEW") - ] - - @reflection.cache - def get_table_options(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - if parsed_state.table_options: - return parsed_state.table_options - else: - return ReflectionDefaults.table_options() - - @reflection.cache - def get_columns(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - if parsed_state.columns: - return parsed_state.columns - else: - return ReflectionDefaults.columns() - - @reflection.cache - def get_pk_constraint(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - for key in parsed_state.keys: - if key["type"] == "PRIMARY": - # There can be only one. - cols = [s[0] for s in key["columns"]] - return {"constrained_columns": cols, "name": None} - return ReflectionDefaults.pk_constraint() - - @reflection.cache - def get_foreign_keys(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - default_schema = None - - fkeys = [] - - for spec in parsed_state.fk_constraints: - ref_name = spec["table"][-1] - ref_schema = len(spec["table"]) > 1 and spec["table"][-2] or schema - - if not ref_schema: - if default_schema is None: - default_schema = connection.dialect.default_schema_name - if schema == default_schema: - ref_schema = schema - - loc_names = spec["local"] - ref_names = spec["foreign"] - - con_kw = {} - for opt in ("onupdate", "ondelete"): - if spec.get(opt, False) not in ("NO ACTION", None): - con_kw[opt] = spec[opt] - - fkey_d = { - "name": spec["name"], - "constrained_columns": loc_names, - "referred_schema": ref_schema, - "referred_table": ref_name, - "referred_columns": ref_names, - "options": con_kw, - } - fkeys.append(fkey_d) - - if self._needs_correct_for_88718_96365: - self._correct_for_mysql_bugs_88718_96365(fkeys, connection) - - return fkeys if fkeys else ReflectionDefaults.foreign_keys() - - def _correct_for_mysql_bugs_88718_96365(self, fkeys, connection): - # Foreign key is always in lower case (MySQL 8.0) - # https://bugs.mysql.com/bug.php?id=88718 - # issue #4344 for SQLAlchemy - - # table name also for MySQL 8.0 - # https://bugs.mysql.com/bug.php?id=96365 - # issue #4751 for SQLAlchemy - - # for lower_case_table_names=2, information_schema.columns - # preserves the original table/schema casing, but SHOW CREATE - # TABLE does not. this problem is not in lower_case_table_names=1, - # but use case-insensitive matching for these two modes in any case. - - if self._casing in (1, 2): - - def lower(s): - return s.lower() - - else: - # if on case sensitive, there can be two tables referenced - # with the same name different casing, so we need to use - # case-sensitive matching. - def lower(s): - return s - - default_schema_name = connection.dialect.default_schema_name - col_tuples = [ - ( - lower(rec["referred_schema"] or default_schema_name), - lower(rec["referred_table"]), - col_name, - ) - for rec in fkeys - for col_name in rec["referred_columns"] - ] - - if col_tuples: - correct_for_wrong_fk_case = connection.execute( - sql.text( - """ - select table_schema, table_name, column_name - from information_schema.columns - where (table_schema, table_name, lower(column_name)) in - :table_data; - """ - ).bindparams(sql.bindparam("table_data", expanding=True)), - dict(table_data=col_tuples), - ) - - # in casing=0, table name and schema name come back in their - # exact case. - # in casing=1, table name and schema name come back in lower - # case. - # in casing=2, table name and schema name come back from the - # information_schema.columns view in the case - # that was used in CREATE DATABASE and CREATE TABLE, but - # SHOW CREATE TABLE converts them to *lower case*, therefore - # not matching. So for this case, case-insensitive lookup - # is necessary - d = defaultdict(dict) - for schema, tname, cname in correct_for_wrong_fk_case: - d[(lower(schema), lower(tname))]["SCHEMANAME"] = schema - d[(lower(schema), lower(tname))]["TABLENAME"] = tname - d[(lower(schema), lower(tname))][cname.lower()] = cname - - for fkey in fkeys: - rec = d[ - ( - lower(fkey["referred_schema"] or default_schema_name), - lower(fkey["referred_table"]), - ) - ] - - fkey["referred_table"] = rec["TABLENAME"] - if fkey["referred_schema"] is not None: - fkey["referred_schema"] = rec["SCHEMANAME"] - - fkey["referred_columns"] = [ - rec[col.lower()] for col in fkey["referred_columns"] - ] - - @reflection.cache - def get_check_constraints(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - - cks = [ - {"name": spec["name"], "sqltext": spec["sqltext"]} - for spec in parsed_state.ck_constraints - ] - cks.sort(key=lambda d: d["name"] or "~") # sort None as last - return cks if cks else ReflectionDefaults.check_constraints() - - @reflection.cache - def get_table_comment(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - comment = parsed_state.table_options.get(f"{self.name}_comment", None) - if comment is not None: - return {"text": comment} - else: - return ReflectionDefaults.table_comment() - - @reflection.cache - def get_indexes(self, connection, table_name, schema=None, **kw): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - - indexes = [] - - for spec in parsed_state.keys: - dialect_options = {} - unique = False - flavor = spec["type"] - if flavor == "PRIMARY": - continue - if flavor == "UNIQUE": - unique = True - elif flavor in ("FULLTEXT", "SPATIAL"): - dialect_options["%s_prefix" % self.name] = flavor - elif flavor is None: - pass - else: - self.logger.info( - "Converting unknown KEY type %s to a plain KEY", flavor - ) - pass - - if spec["parser"]: - dialect_options["%s_with_parser" % (self.name)] = spec[ - "parser" - ] - - index_d = {} - - index_d["name"] = spec["name"] - index_d["column_names"] = [s[0] for s in spec["columns"]] - mysql_length = { - s[0]: s[1] for s in spec["columns"] if s[1] is not None - } - if mysql_length: - dialect_options["%s_length" % self.name] = mysql_length - - index_d["unique"] = unique - if flavor: - index_d["type"] = flavor - - if dialect_options: - index_d["dialect_options"] = dialect_options - - indexes.append(index_d) - indexes.sort(key=lambda d: d["name"] or "~") # sort None as last - return indexes if indexes else ReflectionDefaults.indexes() - - @reflection.cache - def get_unique_constraints( - self, connection, table_name, schema=None, **kw - ): - parsed_state = self._parsed_state_or_create( - connection, table_name, schema, **kw - ) - - ucs = [ - { - "name": key["name"], - "column_names": [col[0] for col in key["columns"]], - "duplicates_index": key["name"], - } - for key in parsed_state.keys - if key["type"] == "UNIQUE" - ] - ucs.sort(key=lambda d: d["name"] or "~") # sort None as last - if ucs: - return ucs - else: - return ReflectionDefaults.unique_constraints() - - @reflection.cache - def get_view_definition(self, connection, view_name, schema=None, **kw): - charset = self._connection_charset - full_name = ".".join( - self.identifier_preparer._quote_free_identifiers(schema, view_name) - ) - sql = self._show_create_table( - connection, None, charset, full_name=full_name - ) - if sql.upper().startswith("CREATE TABLE"): - # it's a table, not a view - raise exc.NoSuchTableError(full_name) - return sql - - def _parsed_state_or_create( - self, connection, table_name, schema=None, **kw - ): - return self._setup_parser( - connection, - table_name, - schema, - info_cache=kw.get("info_cache", None), - ) - - @util.memoized_property - def _tabledef_parser(self): - """return the MySQLTableDefinitionParser, generate if needed. - - The deferred creation ensures that the dialect has - retrieved server version information first. - - """ - preparer = self.identifier_preparer - return _reflection.MySQLTableDefinitionParser(self, preparer) - - @reflection.cache - def _setup_parser(self, connection, table_name, schema=None, **kw): - charset = self._connection_charset - parser = self._tabledef_parser - full_name = ".".join( - self.identifier_preparer._quote_free_identifiers( - schema, table_name - ) - ) - sql = self._show_create_table( - connection, None, charset, full_name=full_name - ) - if parser._check_view(sql): - # Adapt views to something table-like. - columns = self._describe_table( - connection, None, charset, full_name=full_name - ) - sql = parser._describe_to_create(table_name, columns) - return parser.parse(sql, charset) - - def _fetch_setting(self, connection, setting_name): - charset = self._connection_charset - - if self.server_version_info and self.server_version_info < (5, 6): - sql = "SHOW VARIABLES LIKE '%s'" % setting_name - fetch_col = 1 - else: - sql = "SELECT @@%s" % setting_name - fetch_col = 0 - - show_var = connection.exec_driver_sql(sql) - row = self._compat_first(show_var, charset=charset) - if not row: - return None - else: - return row[fetch_col] - - def _detect_charset(self, connection): - raise NotImplementedError() - - def _detect_casing(self, connection): - """Sniff out identifier case sensitivity. - - Cached per-connection. This value can not change without a server - restart. - - """ - # https://dev.mysql.com/doc/refman/en/identifier-case-sensitivity.html - - setting = self._fetch_setting(connection, "lower_case_table_names") - if setting is None: - cs = 0 - else: - # 4.0.15 returns OFF or ON according to [ticket:489] - # 3.23 doesn't, 4.0.27 doesn't.. - if setting == "OFF": - cs = 0 - elif setting == "ON": - cs = 1 - else: - cs = int(setting) - self._casing = cs - return cs - - def _detect_collations(self, connection): - """Pull the active COLLATIONS list from the server. - - Cached per-connection. - """ - - collations = {} - charset = self._connection_charset - rs = connection.exec_driver_sql("SHOW COLLATION") - for row in self._compat_fetchall(rs, charset): - collations[row[0]] = row[1] - return collations - - def _detect_sql_mode(self, connection): - setting = self._fetch_setting(connection, "sql_mode") - - if setting is None: - util.warn( - "Could not retrieve SQL_MODE; please ensure the " - "MySQL user has permissions to SHOW VARIABLES" - ) - self._sql_mode = "" - else: - self._sql_mode = setting or "" - - def _detect_ansiquotes(self, connection): - """Detect and adjust for the ANSI_QUOTES sql mode.""" - - mode = self._sql_mode - if not mode: - mode = "" - elif mode.isdigit(): - mode_no = int(mode) - mode = (mode_no | 4 == mode_no) and "ANSI_QUOTES" or "" - - self._server_ansiquotes = "ANSI_QUOTES" in mode - - # as of MySQL 5.0.1 - self._backslash_escapes = "NO_BACKSLASH_ESCAPES" not in mode - - def _show_create_table( - self, connection, table, charset=None, full_name=None - ): - """Run SHOW CREATE TABLE for a ``Table``.""" - - if full_name is None: - full_name = self.identifier_preparer.format_table(table) - st = "SHOW CREATE TABLE %s" % full_name - - rp = None - try: - rp = connection.execution_options( - skip_user_error_events=True - ).exec_driver_sql(st) - except exc.DBAPIError as e: - if self._extract_error_code(e.orig) == 1146: - raise exc.NoSuchTableError(full_name) from e - else: - raise - row = self._compat_first(rp, charset=charset) - if not row: - raise exc.NoSuchTableError(full_name) - return row[1].strip() - - def _describe_table(self, connection, table, charset=None, full_name=None): - """Run DESCRIBE for a ``Table`` and return processed rows.""" - - if full_name is None: - full_name = self.identifier_preparer.format_table(table) - st = "DESCRIBE %s" % full_name - - rp, rows = None, None - try: - try: - rp = connection.execution_options( - skip_user_error_events=True - ).exec_driver_sql(st) - except exc.DBAPIError as e: - code = self._extract_error_code(e.orig) - if code == 1146: - raise exc.NoSuchTableError(full_name) from e - - elif code == 1356: - raise exc.UnreflectableTableError( - "Table or view named %s could not be " - "reflected: %s" % (full_name, e) - ) from e - - else: - raise - rows = self._compat_fetchall(rp, charset=charset) - finally: - if rp: - rp.close() - return rows - - -class _DecodingRow: - """Return unicode-decoded values based on type inspection. - - Smooth over data type issues (esp. with alpha driver versions) and - normalize strings as Unicode regardless of user-configured driver - encoding settings. - - """ - - # Some MySQL-python versions can return some columns as - # sets.Set(['value']) (seriously) but thankfully that doesn't - # seem to come up in DDL queries. - - _encoding_compat = { - "koi8r": "koi8_r", - "koi8u": "koi8_u", - "utf16": "utf-16-be", # MySQL's uft16 is always bigendian - "utf8mb4": "utf8", # real utf8 - "utf8mb3": "utf8", # real utf8; saw this happen on CI but I cannot - # reproduce, possibly mariadb10.6 related - "eucjpms": "ujis", - } - - def __init__(self, rowproxy, charset): - self.rowproxy = rowproxy - self.charset = self._encoding_compat.get(charset, charset) - - def __getitem__(self, index): - item = self.rowproxy[index] - if isinstance(item, _array): - item = item.tostring() - - if self.charset and isinstance(item, bytes): - return item.decode(self.charset) - else: - return item - - def __getattr__(self, attr): - item = getattr(self.rowproxy, attr) - if isinstance(item, _array): - item = item.tostring() - if self.charset and isinstance(item, bytes): - return item.decode(self.charset) - else: - return item diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py deleted file mode 100644 index f199aa4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/cymysql.py +++ /dev/null @@ -1,84 +0,0 @@ -# dialects/mysql/cymysql.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" - -.. dialect:: mysql+cymysql - :name: CyMySQL - :dbapi: cymysql - :connectstring: mysql+cymysql://:@/[?] - :url: https://github.com/nakagami/CyMySQL - -.. note:: - - The CyMySQL dialect is **not tested as part of SQLAlchemy's continuous - integration** and may have unresolved issues. The recommended MySQL - dialects are mysqlclient and PyMySQL. - -""" # noqa - -from .base import BIT -from .base import MySQLDialect -from .mysqldb import MySQLDialect_mysqldb -from ... import util - - -class _cymysqlBIT(BIT): - def result_processor(self, dialect, coltype): - """Convert MySQL's 64 bit, variable length binary string to a long.""" - - def process(value): - if value is not None: - v = 0 - for i in iter(value): - v = v << 8 | i - return v - return value - - return process - - -class MySQLDialect_cymysql(MySQLDialect_mysqldb): - driver = "cymysql" - supports_statement_cache = True - - description_encoding = None - supports_sane_rowcount = True - supports_sane_multi_rowcount = False - supports_unicode_statements = True - - colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _cymysqlBIT}) - - @classmethod - def import_dbapi(cls): - return __import__("cymysql") - - def _detect_charset(self, connection): - return connection.connection.charset - - def _extract_error_code(self, exception): - return exception.errno - - def is_disconnect(self, e, connection, cursor): - if isinstance(e, self.dbapi.OperationalError): - return self._extract_error_code(e) in ( - 2006, - 2013, - 2014, - 2045, - 2055, - ) - elif isinstance(e, self.dbapi.InterfaceError): - # if underlying connection is closed, - # this is the error you get - return True - else: - return False - - -dialect = MySQLDialect_cymysql diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py deleted file mode 100644 index e4005c2..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/dml.py +++ /dev/null @@ -1,219 +0,0 @@ -# dialects/mysql/dml.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -from typing import Any -from typing import List -from typing import Mapping -from typing import Optional -from typing import Tuple -from typing import Union - -from ... import exc -from ... import util -from ...sql._typing import _DMLTableArgument -from ...sql.base import _exclusive_against -from ...sql.base import _generative -from ...sql.base import ColumnCollection -from ...sql.base import ReadOnlyColumnCollection -from ...sql.dml import Insert as StandardInsert -from ...sql.elements import ClauseElement -from ...sql.elements import KeyedColumnElement -from ...sql.expression import alias -from ...sql.selectable import NamedFromClause -from ...util.typing import Self - - -__all__ = ("Insert", "insert") - - -def insert(table: _DMLTableArgument) -> Insert: - """Construct a MySQL/MariaDB-specific variant :class:`_mysql.Insert` - construct. - - .. container:: inherited_member - - The :func:`sqlalchemy.dialects.mysql.insert` function creates - a :class:`sqlalchemy.dialects.mysql.Insert`. This class is based - on the dialect-agnostic :class:`_sql.Insert` construct which may - be constructed using the :func:`_sql.insert` function in - SQLAlchemy Core. - - The :class:`_mysql.Insert` construct includes additional methods - :meth:`_mysql.Insert.on_duplicate_key_update`. - - """ - return Insert(table) - - -class Insert(StandardInsert): - """MySQL-specific implementation of INSERT. - - Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE. - - The :class:`~.mysql.Insert` object is created using the - :func:`sqlalchemy.dialects.mysql.insert` function. - - .. versionadded:: 1.2 - - """ - - stringify_dialect = "mysql" - inherit_cache = False - - @property - def inserted( - self, - ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: - """Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE - statement - - MySQL's ON DUPLICATE KEY UPDATE clause allows reference to the row - that would be inserted, via a special function called ``VALUES()``. - This attribute provides all columns in this row to be referenceable - such that they will render within a ``VALUES()`` function inside the - ON DUPLICATE KEY UPDATE clause. The attribute is named ``.inserted`` - so as not to conflict with the existing - :meth:`_expression.Insert.values` method. - - .. tip:: The :attr:`_mysql.Insert.inserted` attribute is an instance - of :class:`_expression.ColumnCollection`, which provides an - interface the same as that of the :attr:`_schema.Table.c` - collection described at :ref:`metadata_tables_and_columns`. - With this collection, ordinary names are accessible like attributes - (e.g. ``stmt.inserted.some_column``), but special names and - dictionary method names should be accessed using indexed access, - such as ``stmt.inserted["column name"]`` or - ``stmt.inserted["values"]``. See the docstring for - :class:`_expression.ColumnCollection` for further examples. - - .. seealso:: - - :ref:`mysql_insert_on_duplicate_key_update` - example of how - to use :attr:`_expression.Insert.inserted` - - """ - return self.inserted_alias.columns - - @util.memoized_property - def inserted_alias(self) -> NamedFromClause: - return alias(self.table, name="inserted") - - @_generative - @_exclusive_against( - "_post_values_clause", - msgs={ - "_post_values_clause": "This Insert construct already " - "has an ON DUPLICATE KEY clause present" - }, - ) - def on_duplicate_key_update(self, *args: _UpdateArg, **kw: Any) -> Self: - r""" - Specifies the ON DUPLICATE KEY UPDATE clause. - - :param \**kw: Column keys linked to UPDATE values. The - values may be any SQL expression or supported literal Python - values. - - .. warning:: This dictionary does **not** take into account - Python-specified default UPDATE values or generation functions, - e.g. those specified using :paramref:`_schema.Column.onupdate`. - These values will not be exercised for an ON DUPLICATE KEY UPDATE - style of UPDATE, unless values are manually specified here. - - :param \*args: As an alternative to passing key/value parameters, - a dictionary or list of 2-tuples can be passed as a single positional - argument. - - Passing a single dictionary is equivalent to the keyword argument - form:: - - insert().on_duplicate_key_update({"name": "some name"}) - - Passing a list of 2-tuples indicates that the parameter assignments - in the UPDATE clause should be ordered as sent, in a manner similar - to that described for the :class:`_expression.Update` - construct overall - in :ref:`tutorial_parameter_ordered_updates`:: - - insert().on_duplicate_key_update( - [("name", "some name"), ("value", "some value")]) - - .. versionchanged:: 1.3 parameters can be specified as a dictionary - or list of 2-tuples; the latter form provides for parameter - ordering. - - - .. versionadded:: 1.2 - - .. seealso:: - - :ref:`mysql_insert_on_duplicate_key_update` - - """ - if args and kw: - raise exc.ArgumentError( - "Can't pass kwargs and positional arguments simultaneously" - ) - - if args: - if len(args) > 1: - raise exc.ArgumentError( - "Only a single dictionary or list of tuples " - "is accepted positionally." - ) - values = args[0] - else: - values = kw - - self._post_values_clause = OnDuplicateClause( - self.inserted_alias, values - ) - return self - - -class OnDuplicateClause(ClauseElement): - __visit_name__ = "on_duplicate_key_update" - - _parameter_ordering: Optional[List[str]] = None - - stringify_dialect = "mysql" - - def __init__( - self, inserted_alias: NamedFromClause, update: _UpdateArg - ) -> None: - self.inserted_alias = inserted_alias - - # auto-detect that parameters should be ordered. This is copied from - # Update._proces_colparams(), however we don't look for a special flag - # in this case since we are not disambiguating from other use cases as - # we are in Update.values(). - if isinstance(update, list) and ( - update and isinstance(update[0], tuple) - ): - self._parameter_ordering = [key for key, value in update] - update = dict(update) - - if isinstance(update, dict): - if not update: - raise ValueError( - "update parameter dictionary must not be empty" - ) - elif isinstance(update, ColumnCollection): - update = dict(update) - else: - raise ValueError( - "update parameter must be a non-empty dictionary " - "or a ColumnCollection such as the `.c.` collection " - "of a Table object" - ) - self.update = update - - -_UpdateArg = Union[ - Mapping[Any, Any], List[Tuple[str, Any]], ColumnCollection[Any, Any] -] diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py deleted file mode 100644 index 96499d7..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/enumerated.py +++ /dev/null @@ -1,244 +0,0 @@ -# dialects/mysql/enumerated.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -import re - -from .types import _StringType -from ... import exc -from ... import sql -from ... import util -from ...sql import sqltypes - - -class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum, _StringType): - """MySQL ENUM type.""" - - __visit_name__ = "ENUM" - - native_enum = True - - def __init__(self, *enums, **kw): - """Construct an ENUM. - - E.g.:: - - Column('myenum', ENUM("foo", "bar", "baz")) - - :param enums: The range of valid values for this ENUM. Values in - enums are not quoted, they will be escaped and surrounded by single - quotes when generating the schema. This object may also be a - PEP-435-compliant enumerated type. - - .. versionadded: 1.1 added support for PEP-435-compliant enumerated - types. - - :param strict: This flag has no effect. - - .. versionchanged:: The MySQL ENUM type as well as the base Enum - type now validates all Python data values. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - kw.pop("strict", None) - self._enum_init(enums, kw) - _StringType.__init__(self, length=self.length, **kw) - - @classmethod - def adapt_emulated_to_native(cls, impl, **kw): - """Produce a MySQL native :class:`.mysql.ENUM` from plain - :class:`.Enum`. - - """ - kw.setdefault("validate_strings", impl.validate_strings) - kw.setdefault("values_callable", impl.values_callable) - kw.setdefault("omit_aliases", impl._omit_aliases) - return cls(**kw) - - def _object_value_for_elem(self, elem): - # mysql sends back a blank string for any value that - # was persisted that was not in the enums; that is, it does no - # validation on the incoming data, it "truncates" it to be - # the blank string. Return it straight. - if elem == "": - return elem - else: - return super()._object_value_for_elem(elem) - - def __repr__(self): - return util.generic_repr( - self, to_inspect=[ENUM, _StringType, sqltypes.Enum] - ) - - -class SET(_StringType): - """MySQL SET type.""" - - __visit_name__ = "SET" - - def __init__(self, *values, **kw): - """Construct a SET. - - E.g.:: - - Column('myset', SET("foo", "bar", "baz")) - - - The list of potential values is required in the case that this - set will be used to generate DDL for a table, or if the - :paramref:`.SET.retrieve_as_bitwise` flag is set to True. - - :param values: The range of valid values for this SET. The values - are not quoted, they will be escaped and surrounded by single - quotes when generating the schema. - - :param convert_unicode: Same flag as that of - :paramref:`.String.convert_unicode`. - - :param collation: same as that of :paramref:`.String.collation` - - :param charset: same as that of :paramref:`.VARCHAR.charset`. - - :param ascii: same as that of :paramref:`.VARCHAR.ascii`. - - :param unicode: same as that of :paramref:`.VARCHAR.unicode`. - - :param binary: same as that of :paramref:`.VARCHAR.binary`. - - :param retrieve_as_bitwise: if True, the data for the set type will be - persisted and selected using an integer value, where a set is coerced - into a bitwise mask for persistence. MySQL allows this mode which - has the advantage of being able to store values unambiguously, - such as the blank string ``''``. The datatype will appear - as the expression ``col + 0`` in a SELECT statement, so that the - value is coerced into an integer value in result sets. - This flag is required if one wishes - to persist a set that can store the blank string ``''`` as a value. - - .. warning:: - - When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is - essential that the list of set values is expressed in the - **exact same order** as exists on the MySQL database. - - """ - self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False) - self.values = tuple(values) - if not self.retrieve_as_bitwise and "" in values: - raise exc.ArgumentError( - "Can't use the blank value '' in a SET without " - "setting retrieve_as_bitwise=True" - ) - if self.retrieve_as_bitwise: - self._bitmap = { - value: 2**idx for idx, value in enumerate(self.values) - } - self._bitmap.update( - (2**idx, value) for idx, value in enumerate(self.values) - ) - length = max([len(v) for v in values] + [0]) - kw.setdefault("length", length) - super().__init__(**kw) - - def column_expression(self, colexpr): - if self.retrieve_as_bitwise: - return sql.type_coerce( - sql.type_coerce(colexpr, sqltypes.Integer) + 0, self - ) - else: - return colexpr - - def result_processor(self, dialect, coltype): - if self.retrieve_as_bitwise: - - def process(value): - if value is not None: - value = int(value) - - return set(util.map_bits(self._bitmap.__getitem__, value)) - else: - return None - - else: - super_convert = super().result_processor(dialect, coltype) - - def process(value): - if isinstance(value, str): - # MySQLdb returns a string, let's parse - if super_convert: - value = super_convert(value) - return set(re.findall(r"[^,]+", value)) - else: - # mysql-connector-python does a naive - # split(",") which throws in an empty string - if value is not None: - value.discard("") - return value - - return process - - def bind_processor(self, dialect): - super_convert = super().bind_processor(dialect) - if self.retrieve_as_bitwise: - - def process(value): - if value is None: - return None - elif isinstance(value, (int, str)): - if super_convert: - return super_convert(value) - else: - return value - else: - int_value = 0 - for v in value: - int_value |= self._bitmap[v] - return int_value - - else: - - def process(value): - # accept strings and int (actually bitflag) values directly - if value is not None and not isinstance(value, (int, str)): - value = ",".join(value) - - if super_convert: - return super_convert(value) - else: - return value - - return process - - def adapt(self, impltype, **kw): - kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise - return util.constructor_copy(self, impltype, *self.values, **kw) - - def __repr__(self): - return util.generic_repr( - self, - to_inspect=[SET, _StringType], - additional_kw=[ - ("retrieve_as_bitwise", False), - ], - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py deleted file mode 100644 index b81b58a..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/expression.py +++ /dev/null @@ -1,141 +0,0 @@ -# dialects/mysql/expression.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -from ... import exc -from ... import util -from ...sql import coercions -from ...sql import elements -from ...sql import operators -from ...sql import roles -from ...sql.base import _generative -from ...sql.base import Generative -from ...util.typing import Self - - -class match(Generative, elements.BinaryExpression): - """Produce a ``MATCH (X, Y) AGAINST ('TEXT')`` clause. - - E.g.:: - - from sqlalchemy import desc - from sqlalchemy.dialects.mysql import match - - match_expr = match( - users_table.c.firstname, - users_table.c.lastname, - against="Firstname Lastname", - ) - - stmt = ( - select(users_table) - .where(match_expr.in_boolean_mode()) - .order_by(desc(match_expr)) - ) - - Would produce SQL resembling:: - - SELECT id, firstname, lastname - FROM user - WHERE MATCH(firstname, lastname) AGAINST (:param_1 IN BOOLEAN MODE) - ORDER BY MATCH(firstname, lastname) AGAINST (:param_2) DESC - - The :func:`_mysql.match` function is a standalone version of the - :meth:`_sql.ColumnElement.match` method available on all - SQL expressions, as when :meth:`_expression.ColumnElement.match` is - used, but allows to pass multiple columns - - :param cols: column expressions to match against - - :param against: expression to be compared towards - - :param in_boolean_mode: boolean, set "boolean mode" to true - - :param in_natural_language_mode: boolean , set "natural language" to true - - :param with_query_expansion: boolean, set "query expansion" to true - - .. versionadded:: 1.4.19 - - .. seealso:: - - :meth:`_expression.ColumnElement.match` - - """ - - __visit_name__ = "mysql_match" - - inherit_cache = True - - def __init__(self, *cols, **kw): - if not cols: - raise exc.ArgumentError("columns are required") - - against = kw.pop("against", None) - - if against is None: - raise exc.ArgumentError("against is required") - against = coercions.expect( - roles.ExpressionElementRole, - against, - ) - - left = elements.BooleanClauseList._construct_raw( - operators.comma_op, - clauses=cols, - ) - left.group = False - - flags = util.immutabledict( - { - "mysql_boolean_mode": kw.pop("in_boolean_mode", False), - "mysql_natural_language": kw.pop( - "in_natural_language_mode", False - ), - "mysql_query_expansion": kw.pop("with_query_expansion", False), - } - ) - - if kw: - raise exc.ArgumentError("unknown arguments: %s" % (", ".join(kw))) - - super().__init__(left, against, operators.match_op, modifiers=flags) - - @_generative - def in_boolean_mode(self) -> Self: - """Apply the "IN BOOLEAN MODE" modifier to the MATCH expression. - - :return: a new :class:`_mysql.match` instance with modifications - applied. - """ - - self.modifiers = self.modifiers.union({"mysql_boolean_mode": True}) - return self - - @_generative - def in_natural_language_mode(self) -> Self: - """Apply the "IN NATURAL LANGUAGE MODE" modifier to the MATCH - expression. - - :return: a new :class:`_mysql.match` instance with modifications - applied. - """ - - self.modifiers = self.modifiers.union({"mysql_natural_language": True}) - return self - - @_generative - def with_query_expansion(self) -> Self: - """Apply the "WITH QUERY EXPANSION" modifier to the MATCH expression. - - :return: a new :class:`_mysql.match` instance with modifications - applied. - """ - - self.modifiers = self.modifiers.union({"mysql_query_expansion": True}) - return self diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py deleted file mode 100644 index ebe4a34..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/json.py +++ /dev/null @@ -1,81 +0,0 @@ -# dialects/mysql/json.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import types as sqltypes - - -class JSON(sqltypes.JSON): - """MySQL JSON type. - - MySQL supports JSON as of version 5.7. - MariaDB supports JSON (as an alias for LONGTEXT) as of version 10.2. - - :class:`_mysql.JSON` is used automatically whenever the base - :class:`_types.JSON` datatype is used against a MySQL or MariaDB backend. - - .. seealso:: - - :class:`_types.JSON` - main documentation for the generic - cross-platform JSON datatype. - - The :class:`.mysql.JSON` type supports persistence of JSON values - as well as the core index operations provided by :class:`_types.JSON` - datatype, by adapting the operations to render the ``JSON_EXTRACT`` - function at the database level. - - """ - - pass - - -class _FormatTypeMixin: - def _format_value(self, value): - raise NotImplementedError() - - def bind_processor(self, dialect): - super_proc = self.string_bind_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - def literal_processor(self, dialect): - super_proc = self.string_literal_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - -class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType): - def _format_value(self, value): - if isinstance(value, int): - value = "$[%s]" % value - else: - value = '$."%s"' % value - return value - - -class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType): - def _format_value(self, value): - return "$%s" % ( - "".join( - [ - "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem - for elem in value - ] - ) - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py deleted file mode 100644 index 10a05f9..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadb.py +++ /dev/null @@ -1,32 +0,0 @@ -# dialects/mysql/mariadb.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from .base import MariaDBIdentifierPreparer -from .base import MySQLDialect - - -class MariaDBDialect(MySQLDialect): - is_mariadb = True - supports_statement_cache = True - name = "mariadb" - preparer = MariaDBIdentifierPreparer - - -def loader(driver): - driver_mod = __import__( - "sqlalchemy.dialects.mysql.%s" % driver - ).dialects.mysql - driver_cls = getattr(driver_mod, driver).dialect - - return type( - "MariaDBDialect_%s" % driver, - ( - MariaDBDialect, - driver_cls, - ), - {"supports_statement_cache": True}, - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py deleted file mode 100644 index 9bb3fa4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py +++ /dev/null @@ -1,275 +0,0 @@ -# dialects/mysql/mariadbconnector.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -""" - -.. dialect:: mysql+mariadbconnector - :name: MariaDB Connector/Python - :dbapi: mariadb - :connectstring: mariadb+mariadbconnector://:@[:]/ - :url: https://pypi.org/project/mariadb/ - -Driver Status -------------- - -MariaDB Connector/Python enables Python programs to access MariaDB and MySQL -databases using an API which is compliant with the Python DB API 2.0 (PEP-249). -It is written in C and uses MariaDB Connector/C client library for client server -communication. - -Note that the default driver for a ``mariadb://`` connection URI continues to -be ``mysqldb``. ``mariadb+mariadbconnector://`` is required to use this driver. - -.. mariadb: https://github.com/mariadb-corporation/mariadb-connector-python - -""" # noqa -import re -from uuid import UUID as _python_UUID - -from .base import MySQLCompiler -from .base import MySQLDialect -from .base import MySQLExecutionContext -from ... import sql -from ... import util -from ...sql import sqltypes - - -mariadb_cpy_minimum_version = (1, 0, 1) - - -class _MariaDBUUID(sqltypes.UUID[sqltypes._UUID_RETURN]): - # work around JIRA issue - # https://jira.mariadb.org/browse/CONPY-270. When that issue is fixed, - # this type can be removed. - def result_processor(self, dialect, coltype): - if self.as_uuid: - - def process(value): - if value is not None: - if hasattr(value, "decode"): - value = value.decode("ascii") - value = _python_UUID(value) - return value - - return process - else: - - def process(value): - if value is not None: - if hasattr(value, "decode"): - value = value.decode("ascii") - value = str(_python_UUID(value)) - return value - - return process - - -class MySQLExecutionContext_mariadbconnector(MySQLExecutionContext): - _lastrowid = None - - def create_server_side_cursor(self): - return self._dbapi_connection.cursor(buffered=False) - - def create_default_cursor(self): - return self._dbapi_connection.cursor(buffered=True) - - def post_exec(self): - super().post_exec() - - self._rowcount = self.cursor.rowcount - - if self.isinsert and self.compiled.postfetch_lastrowid: - self._lastrowid = self.cursor.lastrowid - - def get_lastrowid(self): - return self._lastrowid - - -class MySQLCompiler_mariadbconnector(MySQLCompiler): - pass - - -class MySQLDialect_mariadbconnector(MySQLDialect): - driver = "mariadbconnector" - supports_statement_cache = True - - # set this to True at the module level to prevent the driver from running - # against a backend that server detects as MySQL. currently this appears to - # be unnecessary as MariaDB client libraries have always worked against - # MySQL databases. However, if this changes at some point, this can be - # adjusted, but PLEASE ADD A TEST in test/dialect/mysql/test_dialect.py if - # this change is made at some point to ensure the correct exception - # is raised at the correct point when running the driver against - # a MySQL backend. - # is_mariadb = True - - supports_unicode_statements = True - encoding = "utf8mb4" - convert_unicode = True - supports_sane_rowcount = True - supports_sane_multi_rowcount = True - supports_native_decimal = True - default_paramstyle = "qmark" - execution_ctx_cls = MySQLExecutionContext_mariadbconnector - statement_compiler = MySQLCompiler_mariadbconnector - - supports_server_side_cursors = True - - colspecs = util.update_copy( - MySQLDialect.colspecs, {sqltypes.Uuid: _MariaDBUUID} - ) - - @util.memoized_property - def _dbapi_version(self): - if self.dbapi and hasattr(self.dbapi, "__version__"): - return tuple( - [ - int(x) - for x in re.findall( - r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__ - ) - ] - ) - else: - return (99, 99, 99) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.paramstyle = "qmark" - if self.dbapi is not None: - if self._dbapi_version < mariadb_cpy_minimum_version: - raise NotImplementedError( - "The minimum required version for MariaDB " - "Connector/Python is %s" - % ".".join(str(x) for x in mariadb_cpy_minimum_version) - ) - - @classmethod - def import_dbapi(cls): - return __import__("mariadb") - - def is_disconnect(self, e, connection, cursor): - if super().is_disconnect(e, connection, cursor): - return True - elif isinstance(e, self.dbapi.Error): - str_e = str(e).lower() - return "not connected" in str_e or "isn't valid" in str_e - else: - return False - - def create_connect_args(self, url): - opts = url.translate_connect_args() - - int_params = [ - "connect_timeout", - "read_timeout", - "write_timeout", - "client_flag", - "port", - "pool_size", - ] - bool_params = [ - "local_infile", - "ssl_verify_cert", - "ssl", - "pool_reset_connection", - ] - - for key in int_params: - util.coerce_kw_type(opts, key, int) - for key in bool_params: - util.coerce_kw_type(opts, key, bool) - - # FOUND_ROWS must be set in CLIENT_FLAGS to enable - # supports_sane_rowcount. - client_flag = opts.get("client_flag", 0) - if self.dbapi is not None: - try: - CLIENT_FLAGS = __import__( - self.dbapi.__name__ + ".constants.CLIENT" - ).constants.CLIENT - client_flag |= CLIENT_FLAGS.FOUND_ROWS - except (AttributeError, ImportError): - self.supports_sane_rowcount = False - opts["client_flag"] = client_flag - return [[], opts] - - def _extract_error_code(self, exception): - try: - rc = exception.errno - except: - rc = -1 - return rc - - def _detect_charset(self, connection): - return "utf8mb4" - - def get_isolation_level_values(self, dbapi_connection): - return ( - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "AUTOCOMMIT", - ) - - def set_isolation_level(self, connection, level): - if level == "AUTOCOMMIT": - connection.autocommit = True - else: - connection.autocommit = False - super().set_isolation_level(connection, level) - - def do_begin_twophase(self, connection, xid): - connection.execute( - sql.text("XA BEGIN :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - - def do_prepare_twophase(self, connection, xid): - connection.execute( - sql.text("XA END :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - connection.execute( - sql.text("XA PREPARE :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if not is_prepared: - connection.execute( - sql.text("XA END :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - connection.execute( - sql.text("XA ROLLBACK :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if not is_prepared: - self.do_prepare_twophase(connection, xid) - connection.execute( - sql.text("XA COMMIT :xid").bindparams( - sql.bindparam("xid", xid, literal_execute=True) - ) - ) - - -dialect = MySQLDialect_mariadbconnector diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py deleted file mode 100644 index b152339..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py +++ /dev/null @@ -1,179 +0,0 @@ -# dialects/mysql/mysqlconnector.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" -.. dialect:: mysql+mysqlconnector - :name: MySQL Connector/Python - :dbapi: myconnpy - :connectstring: mysql+mysqlconnector://:@[:]/ - :url: https://pypi.org/project/mysql-connector-python/ - -.. note:: - - The MySQL Connector/Python DBAPI has had many issues since its release, - some of which may remain unresolved, and the mysqlconnector dialect is - **not tested as part of SQLAlchemy's continuous integration**. - The recommended MySQL dialects are mysqlclient and PyMySQL. - -""" # noqa - -import re - -from .base import BIT -from .base import MySQLCompiler -from .base import MySQLDialect -from .base import MySQLIdentifierPreparer -from ... import util - - -class MySQLCompiler_mysqlconnector(MySQLCompiler): - def visit_mod_binary(self, binary, operator, **kw): - return ( - self.process(binary.left, **kw) - + " % " - + self.process(binary.right, **kw) - ) - - -class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer): - @property - def _double_percents(self): - return False - - @_double_percents.setter - def _double_percents(self, value): - pass - - def _escape_identifier(self, value): - value = value.replace(self.escape_quote, self.escape_to_quote) - return value - - -class _myconnpyBIT(BIT): - def result_processor(self, dialect, coltype): - """MySQL-connector already converts mysql bits, so.""" - - return None - - -class MySQLDialect_mysqlconnector(MySQLDialect): - driver = "mysqlconnector" - supports_statement_cache = True - - supports_sane_rowcount = True - supports_sane_multi_rowcount = True - - supports_native_decimal = True - - default_paramstyle = "format" - statement_compiler = MySQLCompiler_mysqlconnector - - preparer = MySQLIdentifierPreparer_mysqlconnector - - colspecs = util.update_copy(MySQLDialect.colspecs, {BIT: _myconnpyBIT}) - - @classmethod - def import_dbapi(cls): - from mysql import connector - - return connector - - def do_ping(self, dbapi_connection): - dbapi_connection.ping(False) - return True - - def create_connect_args(self, url): - opts = url.translate_connect_args(username="user") - - opts.update(url.query) - - util.coerce_kw_type(opts, "allow_local_infile", bool) - util.coerce_kw_type(opts, "autocommit", bool) - util.coerce_kw_type(opts, "buffered", bool) - util.coerce_kw_type(opts, "compress", bool) - util.coerce_kw_type(opts, "connection_timeout", int) - util.coerce_kw_type(opts, "connect_timeout", int) - util.coerce_kw_type(opts, "consume_results", bool) - util.coerce_kw_type(opts, "force_ipv6", bool) - util.coerce_kw_type(opts, "get_warnings", bool) - util.coerce_kw_type(opts, "pool_reset_session", bool) - util.coerce_kw_type(opts, "pool_size", int) - util.coerce_kw_type(opts, "raise_on_warnings", bool) - util.coerce_kw_type(opts, "raw", bool) - util.coerce_kw_type(opts, "ssl_verify_cert", bool) - util.coerce_kw_type(opts, "use_pure", bool) - util.coerce_kw_type(opts, "use_unicode", bool) - - # unfortunately, MySQL/connector python refuses to release a - # cursor without reading fully, so non-buffered isn't an option - opts.setdefault("buffered", True) - - # FOUND_ROWS must be set in ClientFlag to enable - # supports_sane_rowcount. - if self.dbapi is not None: - try: - from mysql.connector.constants import ClientFlag - - client_flags = opts.get( - "client_flags", ClientFlag.get_default() - ) - client_flags |= ClientFlag.FOUND_ROWS - opts["client_flags"] = client_flags - except Exception: - pass - return [[], opts] - - @util.memoized_property - def _mysqlconnector_version_info(self): - if self.dbapi and hasattr(self.dbapi, "__version__"): - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__) - if m: - return tuple(int(x) for x in m.group(1, 2, 3) if x is not None) - - def _detect_charset(self, connection): - return connection.connection.charset - - def _extract_error_code(self, exception): - return exception.errno - - def is_disconnect(self, e, connection, cursor): - errnos = (2006, 2013, 2014, 2045, 2055, 2048) - exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError) - if isinstance(e, exceptions): - return ( - e.errno in errnos - or "MySQL Connection not available." in str(e) - or "Connection to MySQL is not available" in str(e) - ) - else: - return False - - def _compat_fetchall(self, rp, charset=None): - return rp.fetchall() - - def _compat_fetchone(self, rp, charset=None): - return rp.fetchone() - - _isolation_lookup = { - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "AUTOCOMMIT", - } - - def _set_isolation_level(self, connection, level): - if level == "AUTOCOMMIT": - connection.autocommit = True - else: - connection.autocommit = False - super()._set_isolation_level(connection, level) - - -dialect = MySQLDialect_mysqlconnector diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py deleted file mode 100644 index 0c632b6..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/mysqldb.py +++ /dev/null @@ -1,303 +0,0 @@ -# dialects/mysql/mysqldb.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -""" - -.. dialect:: mysql+mysqldb - :name: mysqlclient (maintained fork of MySQL-Python) - :dbapi: mysqldb - :connectstring: mysql+mysqldb://:@[:]/ - :url: https://pypi.org/project/mysqlclient/ - -Driver Status -------------- - -The mysqlclient DBAPI is a maintained fork of the -`MySQL-Python `_ DBAPI -that is no longer maintained. `mysqlclient`_ supports Python 2 and Python 3 -and is very stable. - -.. _mysqlclient: https://github.com/PyMySQL/mysqlclient-python - -.. _mysqldb_unicode: - -Unicode -------- - -Please see :ref:`mysql_unicode` for current recommendations on unicode -handling. - -.. _mysqldb_ssl: - -SSL Connections ----------------- - -The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the -key "ssl", which may be specified using the -:paramref:`_sa.create_engine.connect_args` dictionary:: - - engine = create_engine( - "mysql+mysqldb://scott:tiger@192.168.0.134/test", - connect_args={ - "ssl": { - "ca": "/home/gord/client-ssl/ca.pem", - "cert": "/home/gord/client-ssl/client-cert.pem", - "key": "/home/gord/client-ssl/client-key.pem" - } - } - ) - -For convenience, the following keys may also be specified inline within the URL -where they will be interpreted into the "ssl" dictionary automatically: -"ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher", -"ssl_check_hostname". An example is as follows:: - - connection_uri = ( - "mysql+mysqldb://scott:tiger@192.168.0.134/test" - "?ssl_ca=/home/gord/client-ssl/ca.pem" - "&ssl_cert=/home/gord/client-ssl/client-cert.pem" - "&ssl_key=/home/gord/client-ssl/client-key.pem" - ) - -.. seealso:: - - :ref:`pymysql_ssl` in the PyMySQL dialect - - -Using MySQLdb with Google Cloud SQL ------------------------------------ - -Google Cloud SQL now recommends use of the MySQLdb dialect. Connect -using a URL like the following:: - - mysql+mysqldb://root@/?unix_socket=/cloudsql/: - -Server Side Cursors -------------------- - -The mysqldb dialect supports server-side cursors. See :ref:`mysql_ss_cursors`. - -""" - -import re - -from .base import MySQLCompiler -from .base import MySQLDialect -from .base import MySQLExecutionContext -from .base import MySQLIdentifierPreparer -from .base import TEXT -from ... import sql -from ... import util - - -class MySQLExecutionContext_mysqldb(MySQLExecutionContext): - pass - - -class MySQLCompiler_mysqldb(MySQLCompiler): - pass - - -class MySQLDialect_mysqldb(MySQLDialect): - driver = "mysqldb" - supports_statement_cache = True - supports_unicode_statements = True - supports_sane_rowcount = True - supports_sane_multi_rowcount = True - - supports_native_decimal = True - - default_paramstyle = "format" - execution_ctx_cls = MySQLExecutionContext_mysqldb - statement_compiler = MySQLCompiler_mysqldb - preparer = MySQLIdentifierPreparer - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._mysql_dbapi_version = ( - self._parse_dbapi_version(self.dbapi.__version__) - if self.dbapi is not None and hasattr(self.dbapi, "__version__") - else (0, 0, 0) - ) - - def _parse_dbapi_version(self, version): - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version) - if m: - return tuple(int(x) for x in m.group(1, 2, 3) if x is not None) - else: - return (0, 0, 0) - - @util.langhelpers.memoized_property - def supports_server_side_cursors(self): - try: - cursors = __import__("MySQLdb.cursors").cursors - self._sscursor = cursors.SSCursor - return True - except (ImportError, AttributeError): - return False - - @classmethod - def import_dbapi(cls): - return __import__("MySQLdb") - - def on_connect(self): - super_ = super().on_connect() - - def on_connect(conn): - if super_ is not None: - super_(conn) - - charset_name = conn.character_set_name() - - if charset_name is not None: - cursor = conn.cursor() - cursor.execute("SET NAMES %s" % charset_name) - cursor.close() - - return on_connect - - def do_ping(self, dbapi_connection): - dbapi_connection.ping() - return True - - def do_executemany(self, cursor, statement, parameters, context=None): - rowcount = cursor.executemany(statement, parameters) - if context is not None: - context._rowcount = rowcount - - def _check_unicode_returns(self, connection): - # work around issue fixed in - # https://github.com/farcepest/MySQLdb1/commit/cd44524fef63bd3fcb71947392326e9742d520e8 - # specific issue w/ the utf8mb4_bin collation and unicode returns - - collation = connection.exec_driver_sql( - "show collation where %s = 'utf8mb4' and %s = 'utf8mb4_bin'" - % ( - self.identifier_preparer.quote("Charset"), - self.identifier_preparer.quote("Collation"), - ) - ).scalar() - has_utf8mb4_bin = self.server_version_info > (5,) and collation - if has_utf8mb4_bin: - additional_tests = [ - sql.collate( - sql.cast( - sql.literal_column("'test collated returns'"), - TEXT(charset="utf8mb4"), - ), - "utf8mb4_bin", - ) - ] - else: - additional_tests = [] - return super()._check_unicode_returns(connection, additional_tests) - - def create_connect_args(self, url, _translate_args=None): - if _translate_args is None: - _translate_args = dict( - database="db", username="user", password="passwd" - ) - - opts = url.translate_connect_args(**_translate_args) - opts.update(url.query) - - util.coerce_kw_type(opts, "compress", bool) - util.coerce_kw_type(opts, "connect_timeout", int) - util.coerce_kw_type(opts, "read_timeout", int) - util.coerce_kw_type(opts, "write_timeout", int) - util.coerce_kw_type(opts, "client_flag", int) - util.coerce_kw_type(opts, "local_infile", int) - # Note: using either of the below will cause all strings to be - # returned as Unicode, both in raw SQL operations and with column - # types like String and MSString. - util.coerce_kw_type(opts, "use_unicode", bool) - util.coerce_kw_type(opts, "charset", str) - - # Rich values 'cursorclass' and 'conv' are not supported via - # query string. - - ssl = {} - keys = [ - ("ssl_ca", str), - ("ssl_key", str), - ("ssl_cert", str), - ("ssl_capath", str), - ("ssl_cipher", str), - ("ssl_check_hostname", bool), - ] - for key, kw_type in keys: - if key in opts: - ssl[key[4:]] = opts[key] - util.coerce_kw_type(ssl, key[4:], kw_type) - del opts[key] - if ssl: - opts["ssl"] = ssl - - # FOUND_ROWS must be set in CLIENT_FLAGS to enable - # supports_sane_rowcount. - client_flag = opts.get("client_flag", 0) - - client_flag_found_rows = self._found_rows_client_flag() - if client_flag_found_rows is not None: - client_flag |= client_flag_found_rows - opts["client_flag"] = client_flag - return [[], opts] - - def _found_rows_client_flag(self): - if self.dbapi is not None: - try: - CLIENT_FLAGS = __import__( - self.dbapi.__name__ + ".constants.CLIENT" - ).constants.CLIENT - except (AttributeError, ImportError): - return None - else: - return CLIENT_FLAGS.FOUND_ROWS - else: - return None - - def _extract_error_code(self, exception): - return exception.args[0] - - def _detect_charset(self, connection): - """Sniff out the character set in use for connection results.""" - - try: - # note: the SQL here would be - # "SHOW VARIABLES LIKE 'character_set%%'" - cset_name = connection.connection.character_set_name - except AttributeError: - util.warn( - "No 'character_set_name' can be detected with " - "this MySQL-Python version; " - "please upgrade to a recent version of MySQL-Python. " - "Assuming latin1." - ) - return "latin1" - else: - return cset_name() - - def get_isolation_level_values(self, dbapi_connection): - return ( - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "AUTOCOMMIT", - ) - - def set_isolation_level(self, dbapi_connection, level): - if level == "AUTOCOMMIT": - dbapi_connection.autocommit(True) - else: - dbapi_connection.autocommit(False) - super().set_isolation_level(dbapi_connection, level) - - -dialect = MySQLDialect_mysqldb diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py deleted file mode 100644 index 3f05bce..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/provision.py +++ /dev/null @@ -1,107 +0,0 @@ -# dialects/mysql/provision.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import exc -from ...testing.provision import configure_follower -from ...testing.provision import create_db -from ...testing.provision import drop_db -from ...testing.provision import generate_driver_url -from ...testing.provision import temp_table_keyword_args -from ...testing.provision import upsert - - -@generate_driver_url.for_db("mysql", "mariadb") -def generate_driver_url(url, driver, query_str): - backend = url.get_backend_name() - - # NOTE: at the moment, tests are running mariadbconnector - # against both mariadb and mysql backends. if we want this to be - # limited, do the decision making here to reject a "mysql+mariadbconnector" - # URL. Optionally also re-enable the module level - # MySQLDialect_mariadbconnector.is_mysql flag as well, which must include - # a unit and/or functional test. - - # all the Jenkins tests have been running mysqlclient Python library - # built against mariadb client drivers for years against all MySQL / - # MariaDB versions going back to MySQL 5.6, currently they can talk - # to MySQL databases without problems. - - if backend == "mysql": - dialect_cls = url.get_dialect() - if dialect_cls._is_mariadb_from_url(url): - backend = "mariadb" - - new_url = url.set( - drivername="%s+%s" % (backend, driver) - ).update_query_string(query_str) - - try: - new_url.get_dialect() - except exc.NoSuchModuleError: - return None - else: - return new_url - - -@create_db.for_db("mysql", "mariadb") -def _mysql_create_db(cfg, eng, ident): - with eng.begin() as conn: - try: - _mysql_drop_db(cfg, conn, ident) - except Exception: - pass - - with eng.begin() as conn: - conn.exec_driver_sql( - "CREATE DATABASE %s CHARACTER SET utf8mb4" % ident - ) - conn.exec_driver_sql( - "CREATE DATABASE %s_test_schema CHARACTER SET utf8mb4" % ident - ) - conn.exec_driver_sql( - "CREATE DATABASE %s_test_schema_2 CHARACTER SET utf8mb4" % ident - ) - - -@configure_follower.for_db("mysql", "mariadb") -def _mysql_configure_follower(config, ident): - config.test_schema = "%s_test_schema" % ident - config.test_schema_2 = "%s_test_schema_2" % ident - - -@drop_db.for_db("mysql", "mariadb") -def _mysql_drop_db(cfg, eng, ident): - with eng.begin() as conn: - conn.exec_driver_sql("DROP DATABASE %s_test_schema" % ident) - conn.exec_driver_sql("DROP DATABASE %s_test_schema_2" % ident) - conn.exec_driver_sql("DROP DATABASE %s" % ident) - - -@temp_table_keyword_args.for_db("mysql", "mariadb") -def _mysql_temp_table_keyword_args(cfg, eng): - return {"prefixes": ["TEMPORARY"]} - - -@upsert.for_db("mariadb") -def _upsert( - cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False -): - from sqlalchemy.dialects.mysql import insert - - stmt = insert(table) - - if set_lambda: - stmt = stmt.on_duplicate_key_update(**set_lambda(stmt.inserted)) - else: - pk1 = table.primary_key.c[0] - stmt = stmt.on_duplicate_key_update({pk1.key: pk1}) - - stmt = stmt.returning( - *returning, sort_by_parameter_order=sort_by_parameter_order - ) - return stmt diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py deleted file mode 100644 index 830e441..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pymysql.py +++ /dev/null @@ -1,137 +0,0 @@ -# dialects/mysql/pymysql.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" - -.. dialect:: mysql+pymysql - :name: PyMySQL - :dbapi: pymysql - :connectstring: mysql+pymysql://:@/[?] - :url: https://pymysql.readthedocs.io/ - -Unicode -------- - -Please see :ref:`mysql_unicode` for current recommendations on unicode -handling. - -.. _pymysql_ssl: - -SSL Connections ------------------- - -The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb, -described at :ref:`mysqldb_ssl`. See that section for additional examples. - -If the server uses an automatically-generated certificate that is self-signed -or does not match the host name (as seen from the client), it may also be -necessary to indicate ``ssl_check_hostname=false`` in PyMySQL:: - - connection_uri = ( - "mysql+pymysql://scott:tiger@192.168.0.134/test" - "?ssl_ca=/home/gord/client-ssl/ca.pem" - "&ssl_cert=/home/gord/client-ssl/client-cert.pem" - "&ssl_key=/home/gord/client-ssl/client-key.pem" - "&ssl_check_hostname=false" - ) - - -MySQL-Python Compatibility --------------------------- - -The pymysql DBAPI is a pure Python port of the MySQL-python (MySQLdb) driver, -and targets 100% compatibility. Most behavioral notes for MySQL-python apply -to the pymysql driver as well. - -""" # noqa - -from .mysqldb import MySQLDialect_mysqldb -from ...util import langhelpers - - -class MySQLDialect_pymysql(MySQLDialect_mysqldb): - driver = "pymysql" - supports_statement_cache = True - - description_encoding = None - - @langhelpers.memoized_property - def supports_server_side_cursors(self): - try: - cursors = __import__("pymysql.cursors").cursors - self._sscursor = cursors.SSCursor - return True - except (ImportError, AttributeError): - return False - - @classmethod - def import_dbapi(cls): - return __import__("pymysql") - - @langhelpers.memoized_property - def _send_false_to_ping(self): - """determine if pymysql has deprecated, changed the default of, - or removed the 'reconnect' argument of connection.ping(). - - See #10492 and - https://github.com/PyMySQL/mysqlclient/discussions/651#discussioncomment-7308971 - for background. - - """ # noqa: E501 - - try: - Connection = __import__( - "pymysql.connections" - ).connections.Connection - except (ImportError, AttributeError): - return True - else: - insp = langhelpers.get_callable_argspec(Connection.ping) - try: - reconnect_arg = insp.args[1] - except IndexError: - return False - else: - return reconnect_arg == "reconnect" and ( - not insp.defaults or insp.defaults[0] is not False - ) - - def do_ping(self, dbapi_connection): - if self._send_false_to_ping: - dbapi_connection.ping(False) - else: - dbapi_connection.ping() - - return True - - def create_connect_args(self, url, _translate_args=None): - if _translate_args is None: - _translate_args = dict(username="user") - return super().create_connect_args( - url, _translate_args=_translate_args - ) - - def is_disconnect(self, e, connection, cursor): - if super().is_disconnect(e, connection, cursor): - return True - elif isinstance(e, self.dbapi.Error): - str_e = str(e).lower() - return ( - "already closed" in str_e or "connection was killed" in str_e - ) - else: - return False - - def _extract_error_code(self, exception): - if isinstance(exception.args[0], Exception): - exception = exception.args[0] - return exception.args[0] - - -dialect = MySQLDialect_pymysql diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py deleted file mode 100644 index 428c8df..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/pyodbc.py +++ /dev/null @@ -1,138 +0,0 @@ -# dialects/mysql/pyodbc.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" - - -.. dialect:: mysql+pyodbc - :name: PyODBC - :dbapi: pyodbc - :connectstring: mysql+pyodbc://:@ - :url: https://pypi.org/project/pyodbc/ - -.. note:: - - The PyODBC for MySQL dialect is **not tested as part of - SQLAlchemy's continuous integration**. - The recommended MySQL dialects are mysqlclient and PyMySQL. - However, if you want to use the mysql+pyodbc dialect and require - full support for ``utf8mb4`` characters (including supplementary - characters like emoji) be sure to use a current release of - MySQL Connector/ODBC and specify the "ANSI" (**not** "Unicode") - version of the driver in your DSN or connection string. - -Pass through exact pyodbc connection string:: - - import urllib - connection_string = ( - 'DRIVER=MySQL ODBC 8.0 ANSI Driver;' - 'SERVER=localhost;' - 'PORT=3307;' - 'DATABASE=mydb;' - 'UID=root;' - 'PWD=(whatever);' - 'charset=utf8mb4;' - ) - params = urllib.parse.quote_plus(connection_string) - connection_uri = "mysql+pyodbc:///?odbc_connect=%s" % params - -""" # noqa - -import re - -from .base import MySQLDialect -from .base import MySQLExecutionContext -from .types import TIME -from ... import exc -from ... import util -from ...connectors.pyodbc import PyODBCConnector -from ...sql.sqltypes import Time - - -class _pyodbcTIME(TIME): - def result_processor(self, dialect, coltype): - def process(value): - # pyodbc returns a datetime.time object; no need to convert - return value - - return process - - -class MySQLExecutionContext_pyodbc(MySQLExecutionContext): - def get_lastrowid(self): - cursor = self.create_cursor() - cursor.execute("SELECT LAST_INSERT_ID()") - lastrowid = cursor.fetchone()[0] - cursor.close() - return lastrowid - - -class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect): - supports_statement_cache = True - colspecs = util.update_copy(MySQLDialect.colspecs, {Time: _pyodbcTIME}) - supports_unicode_statements = True - execution_ctx_cls = MySQLExecutionContext_pyodbc - - pyodbc_driver_name = "MySQL" - - def _detect_charset(self, connection): - """Sniff out the character set in use for connection results.""" - - # Prefer 'character_set_results' for the current connection over the - # value in the driver. SET NAMES or individual variable SETs will - # change the charset without updating the driver's view of the world. - # - # If it's decided that issuing that sort of SQL leaves you SOL, then - # this can prefer the driver value. - - # set this to None as _fetch_setting attempts to use it (None is OK) - self._connection_charset = None - try: - value = self._fetch_setting(connection, "character_set_client") - if value: - return value - except exc.DBAPIError: - pass - - util.warn( - "Could not detect the connection character set. " - "Assuming latin1." - ) - return "latin1" - - def _get_server_version_info(self, connection): - return MySQLDialect._get_server_version_info(self, connection) - - def _extract_error_code(self, exception): - m = re.compile(r"\((\d+)\)").search(str(exception.args)) - c = m.group(1) - if c: - return int(c) - else: - return None - - def on_connect(self): - super_ = super().on_connect() - - def on_connect(conn): - if super_ is not None: - super_(conn) - - # declare Unicode encoding for pyodbc as per - # https://github.com/mkleehammer/pyodbc/wiki/Unicode - pyodbc_SQL_CHAR = 1 # pyodbc.SQL_CHAR - pyodbc_SQL_WCHAR = -8 # pyodbc.SQL_WCHAR - conn.setdecoding(pyodbc_SQL_CHAR, encoding="utf-8") - conn.setdecoding(pyodbc_SQL_WCHAR, encoding="utf-8") - conn.setencoding(encoding="utf-8") - - return on_connect - - -dialect = MySQLDialect_pyodbc diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py deleted file mode 100644 index c764e8c..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reflection.py +++ /dev/null @@ -1,677 +0,0 @@ -# dialects/mysql/reflection.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -import re - -from .enumerated import ENUM -from .enumerated import SET -from .types import DATETIME -from .types import TIME -from .types import TIMESTAMP -from ... import log -from ... import types as sqltypes -from ... import util - - -class ReflectedState: - """Stores raw information about a SHOW CREATE TABLE statement.""" - - def __init__(self): - self.columns = [] - self.table_options = {} - self.table_name = None - self.keys = [] - self.fk_constraints = [] - self.ck_constraints = [] - - -@log.class_logger -class MySQLTableDefinitionParser: - """Parses the results of a SHOW CREATE TABLE statement.""" - - def __init__(self, dialect, preparer): - self.dialect = dialect - self.preparer = preparer - self._prep_regexes() - - def parse(self, show_create, charset): - state = ReflectedState() - state.charset = charset - for line in re.split(r"\r?\n", show_create): - if line.startswith(" " + self.preparer.initial_quote): - self._parse_column(line, state) - # a regular table options line - elif line.startswith(") "): - self._parse_table_options(line, state) - # an ANSI-mode table options line - elif line == ")": - pass - elif line.startswith("CREATE "): - self._parse_table_name(line, state) - elif "PARTITION" in line: - self._parse_partition_options(line, state) - # Not present in real reflection, but may be if - # loading from a file. - elif not line: - pass - else: - type_, spec = self._parse_constraints(line) - if type_ is None: - util.warn("Unknown schema content: %r" % line) - elif type_ == "key": - state.keys.append(spec) - elif type_ == "fk_constraint": - state.fk_constraints.append(spec) - elif type_ == "ck_constraint": - state.ck_constraints.append(spec) - else: - pass - return state - - def _check_view(self, sql: str) -> bool: - return bool(self._re_is_view.match(sql)) - - def _parse_constraints(self, line): - """Parse a KEY or CONSTRAINT line. - - :param line: A line of SHOW CREATE TABLE output - """ - - # KEY - m = self._re_key.match(line) - if m: - spec = m.groupdict() - # convert columns into name, length pairs - # NOTE: we may want to consider SHOW INDEX as the - # format of indexes in MySQL becomes more complex - spec["columns"] = self._parse_keyexprs(spec["columns"]) - if spec["version_sql"]: - m2 = self._re_key_version_sql.match(spec["version_sql"]) - if m2 and m2.groupdict()["parser"]: - spec["parser"] = m2.groupdict()["parser"] - if spec["parser"]: - spec["parser"] = self.preparer.unformat_identifiers( - spec["parser"] - )[0] - return "key", spec - - # FOREIGN KEY CONSTRAINT - m = self._re_fk_constraint.match(line) - if m: - spec = m.groupdict() - spec["table"] = self.preparer.unformat_identifiers(spec["table"]) - spec["local"] = [c[0] for c in self._parse_keyexprs(spec["local"])] - spec["foreign"] = [ - c[0] for c in self._parse_keyexprs(spec["foreign"]) - ] - return "fk_constraint", spec - - # CHECK constraint - m = self._re_ck_constraint.match(line) - if m: - spec = m.groupdict() - return "ck_constraint", spec - - # PARTITION and SUBPARTITION - m = self._re_partition.match(line) - if m: - # Punt! - return "partition", line - - # No match. - return (None, line) - - def _parse_table_name(self, line, state): - """Extract the table name. - - :param line: The first line of SHOW CREATE TABLE - """ - - regex, cleanup = self._pr_name - m = regex.match(line) - if m: - state.table_name = cleanup(m.group("name")) - - def _parse_table_options(self, line, state): - """Build a dictionary of all reflected table-level options. - - :param line: The final line of SHOW CREATE TABLE output. - """ - - options = {} - - if line and line != ")": - rest_of_line = line - for regex, cleanup in self._pr_options: - m = regex.search(rest_of_line) - if not m: - continue - directive, value = m.group("directive"), m.group("val") - if cleanup: - value = cleanup(value) - options[directive.lower()] = value - rest_of_line = regex.sub("", rest_of_line) - - for nope in ("auto_increment", "data directory", "index directory"): - options.pop(nope, None) - - for opt, val in options.items(): - state.table_options["%s_%s" % (self.dialect.name, opt)] = val - - def _parse_partition_options(self, line, state): - options = {} - new_line = line[:] - - while new_line.startswith("(") or new_line.startswith(" "): - new_line = new_line[1:] - - for regex, cleanup in self._pr_options: - m = regex.search(new_line) - if not m or "PARTITION" not in regex.pattern: - continue - - directive = m.group("directive") - directive = directive.lower() - is_subpartition = directive == "subpartition" - - if directive == "partition" or is_subpartition: - new_line = new_line.replace(") */", "") - new_line = new_line.replace(",", "") - if is_subpartition and new_line.endswith(")"): - new_line = new_line[:-1] - if self.dialect.name == "mariadb" and new_line.endswith(")"): - if ( - "MAXVALUE" in new_line - or "MINVALUE" in new_line - or "ENGINE" in new_line - ): - # final line of MariaDB partition endswith ")" - new_line = new_line[:-1] - - defs = "%s_%s_definitions" % (self.dialect.name, directive) - options[defs] = new_line - - else: - directive = directive.replace(" ", "_") - value = m.group("val") - if cleanup: - value = cleanup(value) - options[directive] = value - break - - for opt, val in options.items(): - part_def = "%s_partition_definitions" % (self.dialect.name) - subpart_def = "%s_subpartition_definitions" % (self.dialect.name) - if opt == part_def or opt == subpart_def: - # builds a string of definitions - if opt not in state.table_options: - state.table_options[opt] = val - else: - state.table_options[opt] = "%s, %s" % ( - state.table_options[opt], - val, - ) - else: - state.table_options["%s_%s" % (self.dialect.name, opt)] = val - - def _parse_column(self, line, state): - """Extract column details. - - Falls back to a 'minimal support' variant if full parse fails. - - :param line: Any column-bearing line from SHOW CREATE TABLE - """ - - spec = None - m = self._re_column.match(line) - if m: - spec = m.groupdict() - spec["full"] = True - else: - m = self._re_column_loose.match(line) - if m: - spec = m.groupdict() - spec["full"] = False - if not spec: - util.warn("Unknown column definition %r" % line) - return - if not spec["full"]: - util.warn("Incomplete reflection of column definition %r" % line) - - name, type_, args = spec["name"], spec["coltype"], spec["arg"] - - try: - col_type = self.dialect.ischema_names[type_] - except KeyError: - util.warn( - "Did not recognize type '%s' of column '%s'" % (type_, name) - ) - col_type = sqltypes.NullType - - # Column type positional arguments eg. varchar(32) - if args is None or args == "": - type_args = [] - elif args[0] == "'" and args[-1] == "'": - type_args = self._re_csv_str.findall(args) - else: - type_args = [int(v) for v in self._re_csv_int.findall(args)] - - # Column type keyword options - type_kw = {} - - if issubclass(col_type, (DATETIME, TIME, TIMESTAMP)): - if type_args: - type_kw["fsp"] = type_args.pop(0) - - for kw in ("unsigned", "zerofill"): - if spec.get(kw, False): - type_kw[kw] = True - for kw in ("charset", "collate"): - if spec.get(kw, False): - type_kw[kw] = spec[kw] - if issubclass(col_type, (ENUM, SET)): - type_args = _strip_values(type_args) - - if issubclass(col_type, SET) and "" in type_args: - type_kw["retrieve_as_bitwise"] = True - - type_instance = col_type(*type_args, **type_kw) - - col_kw = {} - - # NOT NULL - col_kw["nullable"] = True - # this can be "NULL" in the case of TIMESTAMP - if spec.get("notnull", False) == "NOT NULL": - col_kw["nullable"] = False - # For generated columns, the nullability is marked in a different place - if spec.get("notnull_generated", False) == "NOT NULL": - col_kw["nullable"] = False - - # AUTO_INCREMENT - if spec.get("autoincr", False): - col_kw["autoincrement"] = True - elif issubclass(col_type, sqltypes.Integer): - col_kw["autoincrement"] = False - - # DEFAULT - default = spec.get("default", None) - - if default == "NULL": - # eliminates the need to deal with this later. - default = None - - comment = spec.get("comment", None) - - if comment is not None: - comment = cleanup_text(comment) - - sqltext = spec.get("generated") - if sqltext is not None: - computed = dict(sqltext=sqltext) - persisted = spec.get("persistence") - if persisted is not None: - computed["persisted"] = persisted == "STORED" - col_kw["computed"] = computed - - col_d = dict( - name=name, type=type_instance, default=default, comment=comment - ) - col_d.update(col_kw) - state.columns.append(col_d) - - def _describe_to_create(self, table_name, columns): - """Re-format DESCRIBE output as a SHOW CREATE TABLE string. - - DESCRIBE is a much simpler reflection and is sufficient for - reflecting views for runtime use. This method formats DDL - for columns only- keys are omitted. - - :param columns: A sequence of DESCRIBE or SHOW COLUMNS 6-tuples. - SHOW FULL COLUMNS FROM rows must be rearranged for use with - this function. - """ - - buffer = [] - for row in columns: - (name, col_type, nullable, default, extra) = ( - row[i] for i in (0, 1, 2, 4, 5) - ) - - line = [" "] - line.append(self.preparer.quote_identifier(name)) - line.append(col_type) - if not nullable: - line.append("NOT NULL") - if default: - if "auto_increment" in default: - pass - elif col_type.startswith("timestamp") and default.startswith( - "C" - ): - line.append("DEFAULT") - line.append(default) - elif default == "NULL": - line.append("DEFAULT") - line.append(default) - else: - line.append("DEFAULT") - line.append("'%s'" % default.replace("'", "''")) - if extra: - line.append(extra) - - buffer.append(" ".join(line)) - - return "".join( - [ - ( - "CREATE TABLE %s (\n" - % self.preparer.quote_identifier(table_name) - ), - ",\n".join(buffer), - "\n) ", - ] - ) - - def _parse_keyexprs(self, identifiers): - """Unpack '"col"(2),"col" ASC'-ish strings into components.""" - - return [ - (colname, int(length) if length else None, modifiers) - for colname, length, modifiers in self._re_keyexprs.findall( - identifiers - ) - ] - - def _prep_regexes(self): - """Pre-compile regular expressions.""" - - self._re_columns = [] - self._pr_options = [] - - _final = self.preparer.final_quote - - quotes = dict( - zip( - ("iq", "fq", "esc_fq"), - [ - re.escape(s) - for s in ( - self.preparer.initial_quote, - _final, - self.preparer._escape_identifier(_final), - ) - ], - ) - ) - - self._pr_name = _pr_compile( - r"^CREATE (?:\w+ +)?TABLE +" - r"%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +\($" % quotes, - self.preparer._unescape_identifier, - ) - - self._re_is_view = _re_compile(r"^CREATE(?! TABLE)(\s.*)?\sVIEW") - - # `col`,`col2`(32),`col3`(15) DESC - # - self._re_keyexprs = _re_compile( - r"(?:" - r"(?:%(iq)s((?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)" - r"(?:\((\d+)\))?(?: +(ASC|DESC))?(?=\,|$))+" % quotes - ) - - # 'foo' or 'foo','bar' or 'fo,o','ba''a''r' - self._re_csv_str = _re_compile(r"\x27(?:\x27\x27|[^\x27])*\x27") - - # 123 or 123,456 - self._re_csv_int = _re_compile(r"\d+") - - # `colname` [type opts] - # (NOT NULL | NULL) - # DEFAULT ('value' | CURRENT_TIMESTAMP...) - # COMMENT 'comment' - # COLUMN_FORMAT (FIXED|DYNAMIC|DEFAULT) - # STORAGE (DISK|MEMORY) - self._re_column = _re_compile( - r" " - r"%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +" - r"(?P\w+)" - r"(?:\((?P(?:\d+|\d+,\d+|" - r"(?:'(?:''|[^'])*',?)+))\))?" - r"(?: +(?PUNSIGNED))?" - r"(?: +(?PZEROFILL))?" - r"(?: +CHARACTER SET +(?P[\w_]+))?" - r"(?: +COLLATE +(?P[\w_]+))?" - r"(?: +(?P(?:NOT )?NULL))?" - r"(?: +DEFAULT +(?P" - r"(?:NULL|'(?:''|[^'])*'|[\-\w\.\(\)]+" - r"(?: +ON UPDATE [\-\w\.\(\)]+)?)" - r"))?" - r"(?: +(?:GENERATED ALWAYS)? ?AS +(?P\(" - r".*\))? ?(?PVIRTUAL|STORED)?" - r"(?: +(?P(?:NOT )?NULL))?" - r")?" - r"(?: +(?PAUTO_INCREMENT))?" - r"(?: +COMMENT +'(?P(?:''|[^'])*)')?" - r"(?: +COLUMN_FORMAT +(?P\w+))?" - r"(?: +STORAGE +(?P\w+))?" - r"(?: +(?P.*))?" - r",?$" % quotes - ) - - # Fallback, try to parse as little as possible - self._re_column_loose = _re_compile( - r" " - r"%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +" - r"(?P\w+)" - r"(?:\((?P(?:\d+|\d+,\d+|\x27(?:\x27\x27|[^\x27])+\x27))\))?" - r".*?(?P(?:NOT )NULL)?" % quotes - ) - - # (PRIMARY|UNIQUE|FULLTEXT|SPATIAL) INDEX `name` (USING (BTREE|HASH))? - # (`col` (ASC|DESC)?, `col` (ASC|DESC)?) - # KEY_BLOCK_SIZE size | WITH PARSER name /*!50100 WITH PARSER name */ - self._re_key = _re_compile( - r" " - r"(?:(?P\S+) )?KEY" - r"(?: +%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s)?" - r"(?: +USING +(?P\S+))?" - r" +\((?P.+?)\)" - r"(?: +USING +(?P\S+))?" - r"(?: +KEY_BLOCK_SIZE *[ =]? *(?P\S+))?" - r"(?: +WITH PARSER +(?P\S+))?" - r"(?: +COMMENT +(?P(\x27\x27|\x27([^\x27])*?\x27)+))?" - r"(?: +/\*(?P.+)\*/ *)?" - r",?$" % quotes - ) - - # https://forums.mysql.com/read.php?20,567102,567111#msg-567111 - # It means if the MySQL version >= \d+, execute what's in the comment - self._re_key_version_sql = _re_compile( - r"\!\d+ " r"(?: *WITH PARSER +(?P\S+) *)?" - ) - - # CONSTRAINT `name` FOREIGN KEY (`local_col`) - # REFERENCES `remote` (`remote_col`) - # MATCH FULL | MATCH PARTIAL | MATCH SIMPLE - # ON DELETE CASCADE ON UPDATE RESTRICT - # - # unique constraints come back as KEYs - kw = quotes.copy() - kw["on"] = "RESTRICT|CASCADE|SET NULL|NO ACTION" - self._re_fk_constraint = _re_compile( - r" " - r"CONSTRAINT +" - r"%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +" - r"FOREIGN KEY +" - r"\((?P[^\)]+?)\) REFERENCES +" - r"(?P%(iq)s[^%(fq)s]+%(fq)s" - r"(?:\.%(iq)s[^%(fq)s]+%(fq)s)?) +" - r"\((?P(?:%(iq)s[^%(fq)s]+%(fq)s(?: *, *)?)+)\)" - r"(?: +(?PMATCH \w+))?" - r"(?: +ON DELETE (?P%(on)s))?" - r"(?: +ON UPDATE (?P%(on)s))?" % kw - ) - - # CONSTRAINT `CONSTRAINT_1` CHECK (`x` > 5)' - # testing on MariaDB 10.2 shows that the CHECK constraint - # is returned on a line by itself, so to match without worrying - # about parenthesis in the expression we go to the end of the line - self._re_ck_constraint = _re_compile( - r" " - r"CONSTRAINT +" - r"%(iq)s(?P(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s +" - r"CHECK +" - r"\((?P.+)\),?" % kw - ) - - # PARTITION - # - # punt! - self._re_partition = _re_compile(r"(?:.*)(?:SUB)?PARTITION(?:.*)") - - # Table-level options (COLLATE, ENGINE, etc.) - # Do the string options first, since they have quoted - # strings we need to get rid of. - for option in _options_of_type_string: - self._add_option_string(option) - - for option in ( - "ENGINE", - "TYPE", - "AUTO_INCREMENT", - "AVG_ROW_LENGTH", - "CHARACTER SET", - "DEFAULT CHARSET", - "CHECKSUM", - "COLLATE", - "DELAY_KEY_WRITE", - "INSERT_METHOD", - "MAX_ROWS", - "MIN_ROWS", - "PACK_KEYS", - "ROW_FORMAT", - "KEY_BLOCK_SIZE", - "STATS_SAMPLE_PAGES", - ): - self._add_option_word(option) - - for option in ( - "PARTITION BY", - "SUBPARTITION BY", - "PARTITIONS", - "SUBPARTITIONS", - "PARTITION", - "SUBPARTITION", - ): - self._add_partition_option_word(option) - - self._add_option_regex("UNION", r"\([^\)]+\)") - self._add_option_regex("TABLESPACE", r".*? STORAGE DISK") - self._add_option_regex( - "RAID_TYPE", - r"\w+\s+RAID_CHUNKS\s*\=\s*\w+RAID_CHUNKSIZE\s*=\s*\w+", - ) - - _optional_equals = r"(?:\s*(?:=\s*)|\s+)" - - def _add_option_string(self, directive): - regex = r"(?P%s)%s" r"'(?P(?:[^']|'')*?)'(?!')" % ( - re.escape(directive), - self._optional_equals, - ) - self._pr_options.append(_pr_compile(regex, cleanup_text)) - - def _add_option_word(self, directive): - regex = r"(?P%s)%s" r"(?P\w+)" % ( - re.escape(directive), - self._optional_equals, - ) - self._pr_options.append(_pr_compile(regex)) - - def _add_partition_option_word(self, directive): - if directive == "PARTITION BY" or directive == "SUBPARTITION BY": - regex = r"(?%s)%s" r"(?P\w+.*)" % ( - re.escape(directive), - self._optional_equals, - ) - elif directive == "SUBPARTITIONS" or directive == "PARTITIONS": - regex = r"(?%s)%s" r"(?P\d+)" % ( - re.escape(directive), - self._optional_equals, - ) - else: - regex = r"(?%s)(?!\S)" % (re.escape(directive),) - self._pr_options.append(_pr_compile(regex)) - - def _add_option_regex(self, directive, regex): - regex = r"(?P%s)%s" r"(?P%s)" % ( - re.escape(directive), - self._optional_equals, - regex, - ) - self._pr_options.append(_pr_compile(regex)) - - -_options_of_type_string = ( - "COMMENT", - "DATA DIRECTORY", - "INDEX DIRECTORY", - "PASSWORD", - "CONNECTION", -) - - -def _pr_compile(regex, cleanup=None): - """Prepare a 2-tuple of compiled regex and callable.""" - - return (_re_compile(regex), cleanup) - - -def _re_compile(regex): - """Compile a string to regex, I and UNICODE.""" - - return re.compile(regex, re.I | re.UNICODE) - - -def _strip_values(values): - "Strip reflected values quotes" - strip_values = [] - for a in values: - if a[0:1] == '"' or a[0:1] == "'": - # strip enclosing quotes and unquote interior - a = a[1:-1].replace(a[0] * 2, a[0]) - strip_values.append(a) - return strip_values - - -def cleanup_text(raw_text: str) -> str: - if "\\" in raw_text: - raw_text = re.sub( - _control_char_regexp, lambda s: _control_char_map[s[0]], raw_text - ) - return raw_text.replace("''", "'") - - -_control_char_map = { - "\\\\": "\\", - "\\0": "\0", - "\\a": "\a", - "\\b": "\b", - "\\t": "\t", - "\\n": "\n", - "\\v": "\v", - "\\f": "\f", - "\\r": "\r", - # '\\e':'\e', -} -_control_char_regexp = re.compile( - "|".join(re.escape(k) for k in _control_char_map) -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py deleted file mode 100644 index 04764c1..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/reserved_words.py +++ /dev/null @@ -1,571 +0,0 @@ -# dialects/mysql/reserved_words.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - -# generated using: -# https://gist.github.com/kkirsche/4f31f2153ed7a3248be1ec44ca6ddbc9 -# -# https://mariadb.com/kb/en/reserved-words/ -# includes: Reserved Words, Oracle Mode (separate set unioned) -# excludes: Exceptions, Function Names -# mypy: ignore-errors - -RESERVED_WORDS_MARIADB = { - "accessible", - "add", - "all", - "alter", - "analyze", - "and", - "as", - "asc", - "asensitive", - "before", - "between", - "bigint", - "binary", - "blob", - "both", - "by", - "call", - "cascade", - "case", - "change", - "char", - "character", - "check", - "collate", - "column", - "condition", - "constraint", - "continue", - "convert", - "create", - "cross", - "current_date", - "current_role", - "current_time", - "current_timestamp", - "current_user", - "cursor", - "database", - "databases", - "day_hour", - "day_microsecond", - "day_minute", - "day_second", - "dec", - "decimal", - "declare", - "default", - "delayed", - "delete", - "desc", - "describe", - "deterministic", - "distinct", - "distinctrow", - "div", - "do_domain_ids", - "double", - "drop", - "dual", - "each", - "else", - "elseif", - "enclosed", - "escaped", - "except", - "exists", - "exit", - "explain", - "false", - "fetch", - "float", - "float4", - "float8", - "for", - "force", - "foreign", - "from", - "fulltext", - "general", - "grant", - "group", - "having", - "high_priority", - "hour_microsecond", - "hour_minute", - "hour_second", - "if", - "ignore", - "ignore_domain_ids", - "ignore_server_ids", - "in", - "index", - "infile", - "inner", - "inout", - "insensitive", - "insert", - "int", - "int1", - "int2", - "int3", - "int4", - "int8", - "integer", - "intersect", - "interval", - "into", - "is", - "iterate", - "join", - "key", - "keys", - "kill", - "leading", - "leave", - "left", - "like", - "limit", - "linear", - "lines", - "load", - "localtime", - "localtimestamp", - "lock", - "long", - "longblob", - "longtext", - "loop", - "low_priority", - "master_heartbeat_period", - "master_ssl_verify_server_cert", - "match", - "maxvalue", - "mediumblob", - "mediumint", - "mediumtext", - "middleint", - "minute_microsecond", - "minute_second", - "mod", - "modifies", - "natural", - "no_write_to_binlog", - "not", - "null", - "numeric", - "offset", - "on", - "optimize", - "option", - "optionally", - "or", - "order", - "out", - "outer", - "outfile", - "over", - "page_checksum", - "parse_vcol_expr", - "partition", - "position", - "precision", - "primary", - "procedure", - "purge", - "range", - "read", - "read_write", - "reads", - "real", - "recursive", - "ref_system_id", - "references", - "regexp", - "release", - "rename", - "repeat", - "replace", - "require", - "resignal", - "restrict", - "return", - "returning", - "revoke", - "right", - "rlike", - "rows", - "row_number", - "schema", - "schemas", - "second_microsecond", - "select", - "sensitive", - "separator", - "set", - "show", - "signal", - "slow", - "smallint", - "spatial", - "specific", - "sql", - "sql_big_result", - "sql_calc_found_rows", - "sql_small_result", - "sqlexception", - "sqlstate", - "sqlwarning", - "ssl", - "starting", - "stats_auto_recalc", - "stats_persistent", - "stats_sample_pages", - "straight_join", - "table", - "terminated", - "then", - "tinyblob", - "tinyint", - "tinytext", - "to", - "trailing", - "trigger", - "true", - "undo", - "union", - "unique", - "unlock", - "unsigned", - "update", - "usage", - "use", - "using", - "utc_date", - "utc_time", - "utc_timestamp", - "values", - "varbinary", - "varchar", - "varcharacter", - "varying", - "when", - "where", - "while", - "window", - "with", - "write", - "xor", - "year_month", - "zerofill", -}.union( - { - "body", - "elsif", - "goto", - "history", - "others", - "package", - "period", - "raise", - "rowtype", - "system", - "system_time", - "versioning", - "without", - } -) - -# https://dev.mysql.com/doc/refman/8.3/en/keywords.html -# https://dev.mysql.com/doc/refman/8.0/en/keywords.html -# https://dev.mysql.com/doc/refman/5.7/en/keywords.html -# https://dev.mysql.com/doc/refman/5.6/en/keywords.html -# includes: MySQL x.0 Keywords and Reserved Words -# excludes: MySQL x.0 New Keywords and Reserved Words, -# MySQL x.0 Removed Keywords and Reserved Words -RESERVED_WORDS_MYSQL = { - "accessible", - "add", - "admin", - "all", - "alter", - "analyze", - "and", - "array", - "as", - "asc", - "asensitive", - "before", - "between", - "bigint", - "binary", - "blob", - "both", - "by", - "call", - "cascade", - "case", - "change", - "char", - "character", - "check", - "collate", - "column", - "condition", - "constraint", - "continue", - "convert", - "create", - "cross", - "cube", - "cume_dist", - "current_date", - "current_time", - "current_timestamp", - "current_user", - "cursor", - "database", - "databases", - "day_hour", - "day_microsecond", - "day_minute", - "day_second", - "dec", - "decimal", - "declare", - "default", - "delayed", - "delete", - "dense_rank", - "desc", - "describe", - "deterministic", - "distinct", - "distinctrow", - "div", - "double", - "drop", - "dual", - "each", - "else", - "elseif", - "empty", - "enclosed", - "escaped", - "except", - "exists", - "exit", - "explain", - "false", - "fetch", - "first_value", - "float", - "float4", - "float8", - "for", - "force", - "foreign", - "from", - "fulltext", - "function", - "general", - "generated", - "get", - "get_master_public_key", - "grant", - "group", - "grouping", - "groups", - "having", - "high_priority", - "hour_microsecond", - "hour_minute", - "hour_second", - "if", - "ignore", - "ignore_server_ids", - "in", - "index", - "infile", - "inner", - "inout", - "insensitive", - "insert", - "int", - "int1", - "int2", - "int3", - "int4", - "int8", - "integer", - "intersect", - "interval", - "into", - "io_after_gtids", - "io_before_gtids", - "is", - "iterate", - "join", - "json_table", - "key", - "keys", - "kill", - "lag", - "last_value", - "lateral", - "lead", - "leading", - "leave", - "left", - "like", - "limit", - "linear", - "lines", - "load", - "localtime", - "localtimestamp", - "lock", - "long", - "longblob", - "longtext", - "loop", - "low_priority", - "master_bind", - "master_heartbeat_period", - "master_ssl_verify_server_cert", - "match", - "maxvalue", - "mediumblob", - "mediumint", - "mediumtext", - "member", - "middleint", - "minute_microsecond", - "minute_second", - "mod", - "modifies", - "natural", - "no_write_to_binlog", - "not", - "nth_value", - "ntile", - "null", - "numeric", - "of", - "on", - "optimize", - "optimizer_costs", - "option", - "optionally", - "or", - "order", - "out", - "outer", - "outfile", - "over", - "parse_gcol_expr", - "parallel", - "partition", - "percent_rank", - "persist", - "persist_only", - "precision", - "primary", - "procedure", - "purge", - "qualify", - "range", - "rank", - "read", - "read_write", - "reads", - "real", - "recursive", - "references", - "regexp", - "release", - "rename", - "repeat", - "replace", - "require", - "resignal", - "restrict", - "return", - "revoke", - "right", - "rlike", - "role", - "row", - "row_number", - "rows", - "schema", - "schemas", - "second_microsecond", - "select", - "sensitive", - "separator", - "set", - "show", - "signal", - "slow", - "smallint", - "spatial", - "specific", - "sql", - "sql_after_gtids", - "sql_before_gtids", - "sql_big_result", - "sql_calc_found_rows", - "sql_small_result", - "sqlexception", - "sqlstate", - "sqlwarning", - "ssl", - "starting", - "stored", - "straight_join", - "system", - "table", - "terminated", - "then", - "tinyblob", - "tinyint", - "tinytext", - "to", - "trailing", - "trigger", - "true", - "undo", - "union", - "unique", - "unlock", - "unsigned", - "update", - "usage", - "use", - "using", - "utc_date", - "utc_time", - "utc_timestamp", - "values", - "varbinary", - "varchar", - "varcharacter", - "varying", - "virtual", - "when", - "where", - "while", - "window", - "with", - "write", - "xor", - "year_month", - "zerofill", -} diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py deleted file mode 100644 index 734f6ae..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/mysql/types.py +++ /dev/null @@ -1,774 +0,0 @@ -# dialects/mysql/types.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -import datetime - -from ... import exc -from ... import util -from ...sql import sqltypes - - -class _NumericType: - """Base for MySQL numeric types. - - This is the base both for NUMERIC as well as INTEGER, hence - it's a mixin. - - """ - - def __init__(self, unsigned=False, zerofill=False, **kw): - self.unsigned = unsigned - self.zerofill = zerofill - super().__init__(**kw) - - def __repr__(self): - return util.generic_repr( - self, to_inspect=[_NumericType, sqltypes.Numeric] - ) - - -class _FloatType(_NumericType, sqltypes.Float): - def __init__(self, precision=None, scale=None, asdecimal=True, **kw): - if isinstance(self, (REAL, DOUBLE)) and ( - (precision is None and scale is not None) - or (precision is not None and scale is None) - ): - raise exc.ArgumentError( - "You must specify both precision and scale or omit " - "both altogether." - ) - super().__init__(precision=precision, asdecimal=asdecimal, **kw) - self.scale = scale - - def __repr__(self): - return util.generic_repr( - self, to_inspect=[_FloatType, _NumericType, sqltypes.Float] - ) - - -class _IntegerType(_NumericType, sqltypes.Integer): - def __init__(self, display_width=None, **kw): - self.display_width = display_width - super().__init__(**kw) - - def __repr__(self): - return util.generic_repr( - self, to_inspect=[_IntegerType, _NumericType, sqltypes.Integer] - ) - - -class _StringType(sqltypes.String): - """Base for MySQL string types.""" - - def __init__( - self, - charset=None, - collation=None, - ascii=False, # noqa - binary=False, - unicode=False, - national=False, - **kw, - ): - self.charset = charset - - # allow collate= or collation= - kw.setdefault("collation", kw.pop("collate", collation)) - - self.ascii = ascii - self.unicode = unicode - self.binary = binary - self.national = national - super().__init__(**kw) - - def __repr__(self): - return util.generic_repr( - self, to_inspect=[_StringType, sqltypes.String] - ) - - -class _MatchType(sqltypes.Float, sqltypes.MatchType): - def __init__(self, **kw): - # TODO: float arguments? - sqltypes.Float.__init__(self) - sqltypes.MatchType.__init__(self) - - -class NUMERIC(_NumericType, sqltypes.NUMERIC): - """MySQL NUMERIC type.""" - - __visit_name__ = "NUMERIC" - - def __init__(self, precision=None, scale=None, asdecimal=True, **kw): - """Construct a NUMERIC. - - :param precision: Total digits in this number. If scale and precision - are both None, values are stored to limits allowed by the server. - - :param scale: The number of digits after the decimal point. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__( - precision=precision, scale=scale, asdecimal=asdecimal, **kw - ) - - -class DECIMAL(_NumericType, sqltypes.DECIMAL): - """MySQL DECIMAL type.""" - - __visit_name__ = "DECIMAL" - - def __init__(self, precision=None, scale=None, asdecimal=True, **kw): - """Construct a DECIMAL. - - :param precision: Total digits in this number. If scale and precision - are both None, values are stored to limits allowed by the server. - - :param scale: The number of digits after the decimal point. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__( - precision=precision, scale=scale, asdecimal=asdecimal, **kw - ) - - -class DOUBLE(_FloatType, sqltypes.DOUBLE): - """MySQL DOUBLE type.""" - - __visit_name__ = "DOUBLE" - - def __init__(self, precision=None, scale=None, asdecimal=True, **kw): - """Construct a DOUBLE. - - .. note:: - - The :class:`.DOUBLE` type by default converts from float - to Decimal, using a truncation that defaults to 10 digits. - Specify either ``scale=n`` or ``decimal_return_scale=n`` in order - to change this scale, or ``asdecimal=False`` to return values - directly as Python floating points. - - :param precision: Total digits in this number. If scale and precision - are both None, values are stored to limits allowed by the server. - - :param scale: The number of digits after the decimal point. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__( - precision=precision, scale=scale, asdecimal=asdecimal, **kw - ) - - -class REAL(_FloatType, sqltypes.REAL): - """MySQL REAL type.""" - - __visit_name__ = "REAL" - - def __init__(self, precision=None, scale=None, asdecimal=True, **kw): - """Construct a REAL. - - .. note:: - - The :class:`.REAL` type by default converts from float - to Decimal, using a truncation that defaults to 10 digits. - Specify either ``scale=n`` or ``decimal_return_scale=n`` in order - to change this scale, or ``asdecimal=False`` to return values - directly as Python floating points. - - :param precision: Total digits in this number. If scale and precision - are both None, values are stored to limits allowed by the server. - - :param scale: The number of digits after the decimal point. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__( - precision=precision, scale=scale, asdecimal=asdecimal, **kw - ) - - -class FLOAT(_FloatType, sqltypes.FLOAT): - """MySQL FLOAT type.""" - - __visit_name__ = "FLOAT" - - def __init__(self, precision=None, scale=None, asdecimal=False, **kw): - """Construct a FLOAT. - - :param precision: Total digits in this number. If scale and precision - are both None, values are stored to limits allowed by the server. - - :param scale: The number of digits after the decimal point. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__( - precision=precision, scale=scale, asdecimal=asdecimal, **kw - ) - - def bind_processor(self, dialect): - return None - - -class INTEGER(_IntegerType, sqltypes.INTEGER): - """MySQL INTEGER type.""" - - __visit_name__ = "INTEGER" - - def __init__(self, display_width=None, **kw): - """Construct an INTEGER. - - :param display_width: Optional, maximum display width for this number. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__(display_width=display_width, **kw) - - -class BIGINT(_IntegerType, sqltypes.BIGINT): - """MySQL BIGINTEGER type.""" - - __visit_name__ = "BIGINT" - - def __init__(self, display_width=None, **kw): - """Construct a BIGINTEGER. - - :param display_width: Optional, maximum display width for this number. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__(display_width=display_width, **kw) - - -class MEDIUMINT(_IntegerType): - """MySQL MEDIUMINTEGER type.""" - - __visit_name__ = "MEDIUMINT" - - def __init__(self, display_width=None, **kw): - """Construct a MEDIUMINTEGER - - :param display_width: Optional, maximum display width for this number. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__(display_width=display_width, **kw) - - -class TINYINT(_IntegerType): - """MySQL TINYINT type.""" - - __visit_name__ = "TINYINT" - - def __init__(self, display_width=None, **kw): - """Construct a TINYINT. - - :param display_width: Optional, maximum display width for this number. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__(display_width=display_width, **kw) - - -class SMALLINT(_IntegerType, sqltypes.SMALLINT): - """MySQL SMALLINTEGER type.""" - - __visit_name__ = "SMALLINT" - - def __init__(self, display_width=None, **kw): - """Construct a SMALLINTEGER. - - :param display_width: Optional, maximum display width for this number. - - :param unsigned: a boolean, optional. - - :param zerofill: Optional. If true, values will be stored as strings - left-padded with zeros. Note that this does not effect the values - returned by the underlying database API, which continue to be - numeric. - - """ - super().__init__(display_width=display_width, **kw) - - -class BIT(sqltypes.TypeEngine): - """MySQL BIT type. - - This type is for MySQL 5.0.3 or greater for MyISAM, and 5.0.5 or greater - for MyISAM, MEMORY, InnoDB and BDB. For older versions, use a - MSTinyInteger() type. - - """ - - __visit_name__ = "BIT" - - def __init__(self, length=None): - """Construct a BIT. - - :param length: Optional, number of bits. - - """ - self.length = length - - def result_processor(self, dialect, coltype): - """Convert a MySQL's 64 bit, variable length binary string to a long. - - TODO: this is MySQL-db, pyodbc specific. OurSQL and mysqlconnector - already do this, so this logic should be moved to those dialects. - - """ - - def process(value): - if value is not None: - v = 0 - for i in value: - if not isinstance(i, int): - i = ord(i) # convert byte to int on Python 2 - v = v << 8 | i - return v - return value - - return process - - -class TIME(sqltypes.TIME): - """MySQL TIME type.""" - - __visit_name__ = "TIME" - - def __init__(self, timezone=False, fsp=None): - """Construct a MySQL TIME type. - - :param timezone: not used by the MySQL dialect. - :param fsp: fractional seconds precision value. - MySQL 5.6 supports storage of fractional seconds; - this parameter will be used when emitting DDL - for the TIME type. - - .. note:: - - DBAPI driver support for fractional seconds may - be limited; current support includes - MySQL Connector/Python. - - """ - super().__init__(timezone=timezone) - self.fsp = fsp - - def result_processor(self, dialect, coltype): - time = datetime.time - - def process(value): - # convert from a timedelta value - if value is not None: - microseconds = value.microseconds - seconds = value.seconds - minutes = seconds // 60 - return time( - minutes // 60, - minutes % 60, - seconds - minutes * 60, - microsecond=microseconds, - ) - else: - return None - - return process - - -class TIMESTAMP(sqltypes.TIMESTAMP): - """MySQL TIMESTAMP type.""" - - __visit_name__ = "TIMESTAMP" - - def __init__(self, timezone=False, fsp=None): - """Construct a MySQL TIMESTAMP type. - - :param timezone: not used by the MySQL dialect. - :param fsp: fractional seconds precision value. - MySQL 5.6.4 supports storage of fractional seconds; - this parameter will be used when emitting DDL - for the TIMESTAMP type. - - .. note:: - - DBAPI driver support for fractional seconds may - be limited; current support includes - MySQL Connector/Python. - - """ - super().__init__(timezone=timezone) - self.fsp = fsp - - -class DATETIME(sqltypes.DATETIME): - """MySQL DATETIME type.""" - - __visit_name__ = "DATETIME" - - def __init__(self, timezone=False, fsp=None): - """Construct a MySQL DATETIME type. - - :param timezone: not used by the MySQL dialect. - :param fsp: fractional seconds precision value. - MySQL 5.6.4 supports storage of fractional seconds; - this parameter will be used when emitting DDL - for the DATETIME type. - - .. note:: - - DBAPI driver support for fractional seconds may - be limited; current support includes - MySQL Connector/Python. - - """ - super().__init__(timezone=timezone) - self.fsp = fsp - - -class YEAR(sqltypes.TypeEngine): - """MySQL YEAR type, for single byte storage of years 1901-2155.""" - - __visit_name__ = "YEAR" - - def __init__(self, display_width=None): - self.display_width = display_width - - -class TEXT(_StringType, sqltypes.TEXT): - """MySQL TEXT type, for character storage encoded up to 2^16 bytes.""" - - __visit_name__ = "TEXT" - - def __init__(self, length=None, **kw): - """Construct a TEXT. - - :param length: Optional, if provided the server may optimize storage - by substituting the smallest TEXT type sufficient to store - ``length`` bytes of characters. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param national: Optional. If true, use the server's configured - national character set. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - super().__init__(length=length, **kw) - - -class TINYTEXT(_StringType): - """MySQL TINYTEXT type, for character storage encoded up to 2^8 bytes.""" - - __visit_name__ = "TINYTEXT" - - def __init__(self, **kwargs): - """Construct a TINYTEXT. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param national: Optional. If true, use the server's configured - national character set. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - super().__init__(**kwargs) - - -class MEDIUMTEXT(_StringType): - """MySQL MEDIUMTEXT type, for character storage encoded up - to 2^24 bytes.""" - - __visit_name__ = "MEDIUMTEXT" - - def __init__(self, **kwargs): - """Construct a MEDIUMTEXT. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param national: Optional. If true, use the server's configured - national character set. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - super().__init__(**kwargs) - - -class LONGTEXT(_StringType): - """MySQL LONGTEXT type, for character storage encoded up to 2^32 bytes.""" - - __visit_name__ = "LONGTEXT" - - def __init__(self, **kwargs): - """Construct a LONGTEXT. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param national: Optional. If true, use the server's configured - national character set. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - super().__init__(**kwargs) - - -class VARCHAR(_StringType, sqltypes.VARCHAR): - """MySQL VARCHAR type, for variable-length character data.""" - - __visit_name__ = "VARCHAR" - - def __init__(self, length=None, **kwargs): - """Construct a VARCHAR. - - :param charset: Optional, a column-level character set for this string - value. Takes precedence to 'ascii' or 'unicode' short-hand. - - :param collation: Optional, a column-level collation for this string - value. Takes precedence to 'binary' short-hand. - - :param ascii: Defaults to False: short-hand for the ``latin1`` - character set, generates ASCII in schema. - - :param unicode: Defaults to False: short-hand for the ``ucs2`` - character set, generates UNICODE in schema. - - :param national: Optional. If true, use the server's configured - national character set. - - :param binary: Defaults to False: short-hand, pick the binary - collation type that matches the column's character set. Generates - BINARY in schema. This does not affect the type of data stored, - only the collation of character data. - - """ - super().__init__(length=length, **kwargs) - - -class CHAR(_StringType, sqltypes.CHAR): - """MySQL CHAR type, for fixed-length character data.""" - - __visit_name__ = "CHAR" - - def __init__(self, length=None, **kwargs): - """Construct a CHAR. - - :param length: Maximum data length, in characters. - - :param binary: Optional, use the default binary collation for the - national character set. This does not affect the type of data - stored, use a BINARY type for binary data. - - :param collation: Optional, request a particular collation. Must be - compatible with the national character set. - - """ - super().__init__(length=length, **kwargs) - - @classmethod - def _adapt_string_for_cast(cls, type_): - # copy the given string type into a CHAR - # for the purposes of rendering a CAST expression - type_ = sqltypes.to_instance(type_) - if isinstance(type_, sqltypes.CHAR): - return type_ - elif isinstance(type_, _StringType): - return CHAR( - length=type_.length, - charset=type_.charset, - collation=type_.collation, - ascii=type_.ascii, - binary=type_.binary, - unicode=type_.unicode, - national=False, # not supported in CAST - ) - else: - return CHAR(length=type_.length) - - -class NVARCHAR(_StringType, sqltypes.NVARCHAR): - """MySQL NVARCHAR type. - - For variable-length character data in the server's configured national - character set. - """ - - __visit_name__ = "NVARCHAR" - - def __init__(self, length=None, **kwargs): - """Construct an NVARCHAR. - - :param length: Maximum data length, in characters. - - :param binary: Optional, use the default binary collation for the - national character set. This does not affect the type of data - stored, use a BINARY type for binary data. - - :param collation: Optional, request a particular collation. Must be - compatible with the national character set. - - """ - kwargs["national"] = True - super().__init__(length=length, **kwargs) - - -class NCHAR(_StringType, sqltypes.NCHAR): - """MySQL NCHAR type. - - For fixed-length character data in the server's configured national - character set. - """ - - __visit_name__ = "NCHAR" - - def __init__(self, length=None, **kwargs): - """Construct an NCHAR. - - :param length: Maximum data length, in characters. - - :param binary: Optional, use the default binary collation for the - national character set. This does not affect the type of data - stored, use a BINARY type for binary data. - - :param collation: Optional, request a particular collation. Must be - compatible with the national character set. - - """ - kwargs["national"] = True - super().__init__(length=length, **kwargs) - - -class TINYBLOB(sqltypes._Binary): - """MySQL TINYBLOB type, for binary data up to 2^8 bytes.""" - - __visit_name__ = "TINYBLOB" - - -class MEDIUMBLOB(sqltypes._Binary): - """MySQL MEDIUMBLOB type, for binary data up to 2^24 bytes.""" - - __visit_name__ = "MEDIUMBLOB" - - -class LONGBLOB(sqltypes._Binary): - """MySQL LONGBLOB type, for binary data up to 2^32 bytes.""" - - __visit_name__ = "LONGBLOB" diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__init__.py deleted file mode 100644 index d855122..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -# dialects/oracle/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from types import ModuleType - -from . import base # noqa -from . import cx_oracle # noqa -from . import oracledb # noqa -from .base import BFILE -from .base import BINARY_DOUBLE -from .base import BINARY_FLOAT -from .base import BLOB -from .base import CHAR -from .base import CLOB -from .base import DATE -from .base import DOUBLE_PRECISION -from .base import FLOAT -from .base import INTERVAL -from .base import LONG -from .base import NCHAR -from .base import NCLOB -from .base import NUMBER -from .base import NVARCHAR -from .base import NVARCHAR2 -from .base import RAW -from .base import REAL -from .base import ROWID -from .base import TIMESTAMP -from .base import VARCHAR -from .base import VARCHAR2 - -# Alias oracledb also as oracledb_async -oracledb_async = type( - "oracledb_async", (ModuleType,), {"dialect": oracledb.dialect_async} -) - -base.dialect = dialect = cx_oracle.dialect - -__all__ = ( - "VARCHAR", - "NVARCHAR", - "CHAR", - "NCHAR", - "DATE", - "NUMBER", - "BLOB", - "BFILE", - "CLOB", - "NCLOB", - "TIMESTAMP", - "RAW", - "FLOAT", - "DOUBLE_PRECISION", - "BINARY_DOUBLE", - "BINARY_FLOAT", - "LONG", - "dialect", - "INTERVAL", - "VARCHAR2", - "NVARCHAR2", - "ROWID", - "REAL", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index cc02ead..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc deleted file mode 100644 index bf594ef..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc deleted file mode 100644 index 9e8e947..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-311.pyc deleted file mode 100644 index 89ce69c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-311.pyc deleted file mode 100644 index 9325524..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index 6d3c52d..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-311.pyc deleted file mode 100644 index 24bfa8d..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/base.py deleted file mode 100644 index a548b34..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/base.py +++ /dev/null @@ -1,3240 +0,0 @@ -# dialects/oracle/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" -.. dialect:: oracle - :name: Oracle - :full_support: 18c - :normal_support: 11+ - :best_effort: 9+ - - -Auto Increment Behavior ------------------------ - -SQLAlchemy Table objects which include integer primary keys are usually -assumed to have "autoincrementing" behavior, meaning they can generate their -own primary key values upon INSERT. For use within Oracle, two options are -available, which are the use of IDENTITY columns (Oracle 12 and above only) -or the association of a SEQUENCE with the column. - -Specifying GENERATED AS IDENTITY (Oracle 12 and above) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Starting from version 12 Oracle can make use of identity columns using -the :class:`_sql.Identity` to specify the autoincrementing behavior:: - - t = Table('mytable', metadata, - Column('id', Integer, Identity(start=3), primary_key=True), - Column(...), ... - ) - -The CREATE TABLE for the above :class:`_schema.Table` object would be: - -.. sourcecode:: sql - - CREATE TABLE mytable ( - id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 3), - ..., - PRIMARY KEY (id) - ) - -The :class:`_schema.Identity` object support many options to control the -"autoincrementing" behavior of the column, like the starting value, the -incrementing value, etc. -In addition to the standard options, Oracle supports setting -:paramref:`_schema.Identity.always` to ``None`` to use the default -generated mode, rendering GENERATED AS IDENTITY in the DDL. It also supports -setting :paramref:`_schema.Identity.on_null` to ``True`` to specify ON NULL -in conjunction with a 'BY DEFAULT' identity column. - -Using a SEQUENCE (all Oracle versions) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Older version of Oracle had no "autoincrement" -feature, SQLAlchemy relies upon sequences to produce these values. With the -older Oracle versions, *a sequence must always be explicitly specified to -enable autoincrement*. This is divergent with the majority of documentation -examples which assume the usage of an autoincrement-capable database. To -specify sequences, use the sqlalchemy.schema.Sequence object which is passed -to a Column construct:: - - t = Table('mytable', metadata, - Column('id', Integer, Sequence('id_seq', start=1), primary_key=True), - Column(...), ... - ) - -This step is also required when using table reflection, i.e. autoload_with=engine:: - - t = Table('mytable', metadata, - Column('id', Integer, Sequence('id_seq', start=1), primary_key=True), - autoload_with=engine - ) - -.. versionchanged:: 1.4 Added :class:`_schema.Identity` construct - in a :class:`_schema.Column` to specify the option of an autoincrementing - column. - -.. _oracle_isolation_level: - -Transaction Isolation Level / Autocommit ----------------------------------------- - -The Oracle database supports "READ COMMITTED" and "SERIALIZABLE" modes of -isolation. The AUTOCOMMIT isolation level is also supported by the cx_Oracle -dialect. - -To set using per-connection execution options:: - - connection = engine.connect() - connection = connection.execution_options( - isolation_level="AUTOCOMMIT" - ) - -For ``READ COMMITTED`` and ``SERIALIZABLE``, the Oracle dialect sets the -level at the session level using ``ALTER SESSION``, which is reverted back -to its default setting when the connection is returned to the connection -pool. - -Valid values for ``isolation_level`` include: - -* ``READ COMMITTED`` -* ``AUTOCOMMIT`` -* ``SERIALIZABLE`` - -.. note:: The implementation for the - :meth:`_engine.Connection.get_isolation_level` method as implemented by the - Oracle dialect necessarily forces the start of a transaction using the - Oracle LOCAL_TRANSACTION_ID function; otherwise no level is normally - readable. - - Additionally, the :meth:`_engine.Connection.get_isolation_level` method will - raise an exception if the ``v$transaction`` view is not available due to - permissions or other reasons, which is a common occurrence in Oracle - installations. - - The cx_Oracle dialect attempts to call the - :meth:`_engine.Connection.get_isolation_level` method when the dialect makes - its first connection to the database in order to acquire the - "default"isolation level. This default level is necessary so that the level - can be reset on a connection after it has been temporarily modified using - :meth:`_engine.Connection.execution_options` method. In the common event - that the :meth:`_engine.Connection.get_isolation_level` method raises an - exception due to ``v$transaction`` not being readable as well as any other - database-related failure, the level is assumed to be "READ COMMITTED". No - warning is emitted for this initial first-connect condition as it is - expected to be a common restriction on Oracle databases. - -.. versionadded:: 1.3.16 added support for AUTOCOMMIT to the cx_oracle dialect - as well as the notion of a default isolation level - -.. versionadded:: 1.3.21 Added support for SERIALIZABLE as well as live - reading of the isolation level. - -.. versionchanged:: 1.3.22 In the event that the default isolation - level cannot be read due to permissions on the v$transaction view as - is common in Oracle installations, the default isolation level is hardcoded - to "READ COMMITTED" which was the behavior prior to 1.3.21. - -.. seealso:: - - :ref:`dbapi_autocommit` - -Identifier Casing ------------------ - -In Oracle, the data dictionary represents all case insensitive identifier -names using UPPERCASE text. SQLAlchemy on the other hand considers an -all-lower case identifier name to be case insensitive. The Oracle dialect -converts all case insensitive identifiers to and from those two formats during -schema level communication, such as reflection of tables and indexes. Using -an UPPERCASE name on the SQLAlchemy side indicates a case sensitive -identifier, and SQLAlchemy will quote the name - this will cause mismatches -against data dictionary data received from Oracle, so unless identifier names -have been truly created as case sensitive (i.e. using quoted names), all -lowercase names should be used on the SQLAlchemy side. - -.. _oracle_max_identifier_lengths: - -Max Identifier Lengths ----------------------- - -Oracle has changed the default max identifier length as of Oracle Server -version 12.2. Prior to this version, the length was 30, and for 12.2 and -greater it is now 128. This change impacts SQLAlchemy in the area of -generated SQL label names as well as the generation of constraint names, -particularly in the case where the constraint naming convention feature -described at :ref:`constraint_naming_conventions` is being used. - -To assist with this change and others, Oracle includes the concept of a -"compatibility" version, which is a version number that is independent of the -actual server version in order to assist with migration of Oracle databases, -and may be configured within the Oracle server itself. This compatibility -version is retrieved using the query ``SELECT value FROM v$parameter WHERE -name = 'compatible';``. The SQLAlchemy Oracle dialect, when tasked with -determining the default max identifier length, will attempt to use this query -upon first connect in order to determine the effective compatibility version of -the server, which determines what the maximum allowed identifier length is for -the server. If the table is not available, the server version information is -used instead. - -As of SQLAlchemy 1.4, the default max identifier length for the Oracle dialect -is 128 characters. Upon first connect, the compatibility version is detected -and if it is less than Oracle version 12.2, the max identifier length is -changed to be 30 characters. In all cases, setting the -:paramref:`_sa.create_engine.max_identifier_length` parameter will bypass this -change and the value given will be used as is:: - - engine = create_engine( - "oracle+cx_oracle://scott:tiger@oracle122", - max_identifier_length=30) - -The maximum identifier length comes into play both when generating anonymized -SQL labels in SELECT statements, but more crucially when generating constraint -names from a naming convention. It is this area that has created the need for -SQLAlchemy to change this default conservatively. For example, the following -naming convention produces two very different constraint names based on the -identifier length:: - - from sqlalchemy import Column - from sqlalchemy import Index - from sqlalchemy import Integer - from sqlalchemy import MetaData - from sqlalchemy import Table - from sqlalchemy.dialects import oracle - from sqlalchemy.schema import CreateIndex - - m = MetaData(naming_convention={"ix": "ix_%(column_0N_name)s"}) - - t = Table( - "t", - m, - Column("some_column_name_1", Integer), - Column("some_column_name_2", Integer), - Column("some_column_name_3", Integer), - ) - - ix = Index( - None, - t.c.some_column_name_1, - t.c.some_column_name_2, - t.c.some_column_name_3, - ) - - oracle_dialect = oracle.dialect(max_identifier_length=30) - print(CreateIndex(ix).compile(dialect=oracle_dialect)) - -With an identifier length of 30, the above CREATE INDEX looks like:: - - CREATE INDEX ix_some_column_name_1s_70cd ON t - (some_column_name_1, some_column_name_2, some_column_name_3) - -However with length=128, it becomes:: - - CREATE INDEX ix_some_column_name_1some_column_name_2some_column_name_3 ON t - (some_column_name_1, some_column_name_2, some_column_name_3) - -Applications which have run versions of SQLAlchemy prior to 1.4 on an Oracle -server version 12.2 or greater are therefore subject to the scenario of a -database migration that wishes to "DROP CONSTRAINT" on a name that was -previously generated with the shorter length. This migration will fail when -the identifier length is changed without the name of the index or constraint -first being adjusted. Such applications are strongly advised to make use of -:paramref:`_sa.create_engine.max_identifier_length` -in order to maintain control -of the generation of truncated names, and to fully review and test all database -migrations in a staging environment when changing this value to ensure that the -impact of this change has been mitigated. - -.. versionchanged:: 1.4 the default max_identifier_length for Oracle is 128 - characters, which is adjusted down to 30 upon first connect if an older - version of Oracle server (compatibility version < 12.2) is detected. - - -LIMIT/OFFSET/FETCH Support --------------------------- - -Methods like :meth:`_sql.Select.limit` and :meth:`_sql.Select.offset` make -use of ``FETCH FIRST N ROW / OFFSET N ROWS`` syntax assuming -Oracle 12c or above, and assuming the SELECT statement is not embedded within -a compound statement like UNION. This syntax is also available directly by using -the :meth:`_sql.Select.fetch` method. - -.. versionchanged:: 2.0 the Oracle dialect now uses - ``FETCH FIRST N ROW / OFFSET N ROWS`` for all - :meth:`_sql.Select.limit` and :meth:`_sql.Select.offset` usage including - within the ORM and legacy :class:`_orm.Query`. To force the legacy - behavior using window functions, specify the ``enable_offset_fetch=False`` - dialect parameter to :func:`_sa.create_engine`. - -The use of ``FETCH FIRST / OFFSET`` may be disabled on any Oracle version -by passing ``enable_offset_fetch=False`` to :func:`_sa.create_engine`, which -will force the use of "legacy" mode that makes use of window functions. -This mode is also selected automatically when using a version of Oracle -prior to 12c. - -When using legacy mode, or when a :class:`.Select` statement -with limit/offset is embedded in a compound statement, an emulated approach for -LIMIT / OFFSET based on window functions is used, which involves creation of a -subquery using ``ROW_NUMBER`` that is prone to performance issues as well as -SQL construction issues for complex statements. However, this approach is -supported by all Oracle versions. See notes below. - -Notes on LIMIT / OFFSET emulation (when fetch() method cannot be used) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If using :meth:`_sql.Select.limit` and :meth:`_sql.Select.offset`, or with the -ORM the :meth:`_orm.Query.limit` and :meth:`_orm.Query.offset` methods on an -Oracle version prior to 12c, the following notes apply: - -* SQLAlchemy currently makes use of ROWNUM to achieve - LIMIT/OFFSET; the exact methodology is taken from - https://blogs.oracle.com/oraclemagazine/on-rownum-and-limiting-results . - -* the "FIRST_ROWS()" optimization keyword is not used by default. To enable - the usage of this optimization directive, specify ``optimize_limits=True`` - to :func:`_sa.create_engine`. - - .. versionchanged:: 1.4 - The Oracle dialect renders limit/offset integer values using a "post - compile" scheme which renders the integer directly before passing the - statement to the cursor for execution. The ``use_binds_for_limits`` flag - no longer has an effect. - - .. seealso:: - - :ref:`change_4808`. - -.. _oracle_returning: - -RETURNING Support ------------------ - -The Oracle database supports RETURNING fully for INSERT, UPDATE and DELETE -statements that are invoked with a single collection of bound parameters -(that is, a ``cursor.execute()`` style statement; SQLAlchemy does not generally -support RETURNING with :term:`executemany` statements). Multiple rows may be -returned as well. - -.. versionchanged:: 2.0 the Oracle backend has full support for RETURNING - on parity with other backends. - - - -ON UPDATE CASCADE ------------------ - -Oracle doesn't have native ON UPDATE CASCADE functionality. A trigger based -solution is available at -https://asktom.oracle.com/tkyte/update_cascade/index.html . - -When using the SQLAlchemy ORM, the ORM has limited ability to manually issue -cascading updates - specify ForeignKey objects using the -"deferrable=True, initially='deferred'" keyword arguments, -and specify "passive_updates=False" on each relationship(). - -Oracle 8 Compatibility ----------------------- - -.. warning:: The status of Oracle 8 compatibility is not known for SQLAlchemy - 2.0. - -When Oracle 8 is detected, the dialect internally configures itself to the -following behaviors: - -* the use_ansi flag is set to False. This has the effect of converting all - JOIN phrases into the WHERE clause, and in the case of LEFT OUTER JOIN - makes use of Oracle's (+) operator. - -* the NVARCHAR2 and NCLOB datatypes are no longer generated as DDL when - the :class:`~sqlalchemy.types.Unicode` is used - VARCHAR2 and CLOB are issued - instead. This because these types don't seem to work correctly on Oracle 8 - even though they are available. The :class:`~sqlalchemy.types.NVARCHAR` and - :class:`~sqlalchemy.dialects.oracle.NCLOB` types will always generate - NVARCHAR2 and NCLOB. - - -Synonym/DBLINK Reflection -------------------------- - -When using reflection with Table objects, the dialect can optionally search -for tables indicated by synonyms, either in local or remote schemas or -accessed over DBLINK, by passing the flag ``oracle_resolve_synonyms=True`` as -a keyword argument to the :class:`_schema.Table` construct:: - - some_table = Table('some_table', autoload_with=some_engine, - oracle_resolve_synonyms=True) - -When this flag is set, the given name (such as ``some_table`` above) will -be searched not just in the ``ALL_TABLES`` view, but also within the -``ALL_SYNONYMS`` view to see if this name is actually a synonym to another -name. If the synonym is located and refers to a DBLINK, the oracle dialect -knows how to locate the table's information using DBLINK syntax(e.g. -``@dblink``). - -``oracle_resolve_synonyms`` is accepted wherever reflection arguments are -accepted, including methods such as :meth:`_schema.MetaData.reflect` and -:meth:`_reflection.Inspector.get_columns`. - -If synonyms are not in use, this flag should be left disabled. - -.. _oracle_constraint_reflection: - -Constraint Reflection ---------------------- - -The Oracle dialect can return information about foreign key, unique, and -CHECK constraints, as well as indexes on tables. - -Raw information regarding these constraints can be acquired using -:meth:`_reflection.Inspector.get_foreign_keys`, -:meth:`_reflection.Inspector.get_unique_constraints`, -:meth:`_reflection.Inspector.get_check_constraints`, and -:meth:`_reflection.Inspector.get_indexes`. - -.. versionchanged:: 1.2 The Oracle dialect can now reflect UNIQUE and - CHECK constraints. - -When using reflection at the :class:`_schema.Table` level, the -:class:`_schema.Table` -will also include these constraints. - -Note the following caveats: - -* When using the :meth:`_reflection.Inspector.get_check_constraints` method, - Oracle - builds a special "IS NOT NULL" constraint for columns that specify - "NOT NULL". This constraint is **not** returned by default; to include - the "IS NOT NULL" constraints, pass the flag ``include_all=True``:: - - from sqlalchemy import create_engine, inspect - - engine = create_engine("oracle+cx_oracle://s:t@dsn") - inspector = inspect(engine) - all_check_constraints = inspector.get_check_constraints( - "some_table", include_all=True) - -* in most cases, when reflecting a :class:`_schema.Table`, - a UNIQUE constraint will - **not** be available as a :class:`.UniqueConstraint` object, as Oracle - mirrors unique constraints with a UNIQUE index in most cases (the exception - seems to be when two or more unique constraints represent the same columns); - the :class:`_schema.Table` will instead represent these using - :class:`.Index` - with the ``unique=True`` flag set. - -* Oracle creates an implicit index for the primary key of a table; this index - is **excluded** from all index results. - -* the list of columns reflected for an index will not include column names - that start with SYS_NC. - -Table names with SYSTEM/SYSAUX tablespaces -------------------------------------------- - -The :meth:`_reflection.Inspector.get_table_names` and -:meth:`_reflection.Inspector.get_temp_table_names` -methods each return a list of table names for the current engine. These methods -are also part of the reflection which occurs within an operation such as -:meth:`_schema.MetaData.reflect`. By default, -these operations exclude the ``SYSTEM`` -and ``SYSAUX`` tablespaces from the operation. In order to change this, the -default list of tablespaces excluded can be changed at the engine level using -the ``exclude_tablespaces`` parameter:: - - # exclude SYSAUX and SOME_TABLESPACE, but not SYSTEM - e = create_engine( - "oracle+cx_oracle://scott:tiger@xe", - exclude_tablespaces=["SYSAUX", "SOME_TABLESPACE"]) - -DateTime Compatibility ----------------------- - -Oracle has no datatype known as ``DATETIME``, it instead has only ``DATE``, -which can actually store a date and time value. For this reason, the Oracle -dialect provides a type :class:`_oracle.DATE` which is a subclass of -:class:`.DateTime`. This type has no special behavior, and is only -present as a "marker" for this type; additionally, when a database column -is reflected and the type is reported as ``DATE``, the time-supporting -:class:`_oracle.DATE` type is used. - -.. _oracle_table_options: - -Oracle Table Options -------------------------- - -The CREATE TABLE phrase supports the following options with Oracle -in conjunction with the :class:`_schema.Table` construct: - - -* ``ON COMMIT``:: - - Table( - "some_table", metadata, ..., - prefixes=['GLOBAL TEMPORARY'], oracle_on_commit='PRESERVE ROWS') - -* ``COMPRESS``:: - - Table('mytable', metadata, Column('data', String(32)), - oracle_compress=True) - - Table('mytable', metadata, Column('data', String(32)), - oracle_compress=6) - - The ``oracle_compress`` parameter accepts either an integer compression - level, or ``True`` to use the default compression level. - -.. _oracle_index_options: - -Oracle Specific Index Options ------------------------------ - -Bitmap Indexes -~~~~~~~~~~~~~~ - -You can specify the ``oracle_bitmap`` parameter to create a bitmap index -instead of a B-tree index:: - - Index('my_index', my_table.c.data, oracle_bitmap=True) - -Bitmap indexes cannot be unique and cannot be compressed. SQLAlchemy will not -check for such limitations, only the database will. - -Index compression -~~~~~~~~~~~~~~~~~ - -Oracle has a more efficient storage mode for indexes containing lots of -repeated values. Use the ``oracle_compress`` parameter to turn on key -compression:: - - Index('my_index', my_table.c.data, oracle_compress=True) - - Index('my_index', my_table.c.data1, my_table.c.data2, unique=True, - oracle_compress=1) - -The ``oracle_compress`` parameter accepts either an integer specifying the -number of prefix columns to compress, or ``True`` to use the default (all -columns for non-unique indexes, all but the last column for unique indexes). - -""" # noqa - -from __future__ import annotations - -from collections import defaultdict -from functools import lru_cache -from functools import wraps -import re - -from . import dictionary -from .types import _OracleBoolean -from .types import _OracleDate -from .types import BFILE -from .types import BINARY_DOUBLE -from .types import BINARY_FLOAT -from .types import DATE -from .types import FLOAT -from .types import INTERVAL -from .types import LONG -from .types import NCLOB -from .types import NUMBER -from .types import NVARCHAR2 # noqa -from .types import OracleRaw # noqa -from .types import RAW -from .types import ROWID # noqa -from .types import TIMESTAMP -from .types import VARCHAR2 # noqa -from ... import Computed -from ... import exc -from ... import schema as sa_schema -from ... import sql -from ... import util -from ...engine import default -from ...engine import ObjectKind -from ...engine import ObjectScope -from ...engine import reflection -from ...engine.reflection import ReflectionDefaults -from ...sql import and_ -from ...sql import bindparam -from ...sql import compiler -from ...sql import expression -from ...sql import func -from ...sql import null -from ...sql import or_ -from ...sql import select -from ...sql import sqltypes -from ...sql import util as sql_util -from ...sql import visitors -from ...sql.visitors import InternalTraversal -from ...types import BLOB -from ...types import CHAR -from ...types import CLOB -from ...types import DOUBLE_PRECISION -from ...types import INTEGER -from ...types import NCHAR -from ...types import NVARCHAR -from ...types import REAL -from ...types import VARCHAR - -RESERVED_WORDS = set( - "SHARE RAW DROP BETWEEN FROM DESC OPTION PRIOR LONG THEN " - "DEFAULT ALTER IS INTO MINUS INTEGER NUMBER GRANT IDENTIFIED " - "ALL TO ORDER ON FLOAT DATE HAVING CLUSTER NOWAIT RESOURCE " - "ANY TABLE INDEX FOR UPDATE WHERE CHECK SMALLINT WITH DELETE " - "BY ASC REVOKE LIKE SIZE RENAME NOCOMPRESS NULL GROUP VALUES " - "AS IN VIEW EXCLUSIVE COMPRESS SYNONYM SELECT INSERT EXISTS " - "NOT TRIGGER ELSE CREATE INTERSECT PCTFREE DISTINCT USER " - "CONNECT SET MODE OF UNIQUE VARCHAR2 VARCHAR LOCK OR CHAR " - "DECIMAL UNION PUBLIC AND START UID COMMENT CURRENT LEVEL".split() -) - -NO_ARG_FNS = set( - "UID CURRENT_DATE SYSDATE USER CURRENT_TIME CURRENT_TIMESTAMP".split() -) - - -colspecs = { - sqltypes.Boolean: _OracleBoolean, - sqltypes.Interval: INTERVAL, - sqltypes.DateTime: DATE, - sqltypes.Date: _OracleDate, -} - -ischema_names = { - "VARCHAR2": VARCHAR, - "NVARCHAR2": NVARCHAR, - "CHAR": CHAR, - "NCHAR": NCHAR, - "DATE": DATE, - "NUMBER": NUMBER, - "BLOB": BLOB, - "BFILE": BFILE, - "CLOB": CLOB, - "NCLOB": NCLOB, - "TIMESTAMP": TIMESTAMP, - "TIMESTAMP WITH TIME ZONE": TIMESTAMP, - "TIMESTAMP WITH LOCAL TIME ZONE": TIMESTAMP, - "INTERVAL DAY TO SECOND": INTERVAL, - "RAW": RAW, - "FLOAT": FLOAT, - "DOUBLE PRECISION": DOUBLE_PRECISION, - "REAL": REAL, - "LONG": LONG, - "BINARY_DOUBLE": BINARY_DOUBLE, - "BINARY_FLOAT": BINARY_FLOAT, - "ROWID": ROWID, -} - - -class OracleTypeCompiler(compiler.GenericTypeCompiler): - # Note: - # Oracle DATE == DATETIME - # Oracle does not allow milliseconds in DATE - # Oracle does not support TIME columns - - def visit_datetime(self, type_, **kw): - return self.visit_DATE(type_, **kw) - - def visit_float(self, type_, **kw): - return self.visit_FLOAT(type_, **kw) - - def visit_double(self, type_, **kw): - return self.visit_DOUBLE_PRECISION(type_, **kw) - - def visit_unicode(self, type_, **kw): - if self.dialect._use_nchar_for_unicode: - return self.visit_NVARCHAR2(type_, **kw) - else: - return self.visit_VARCHAR2(type_, **kw) - - def visit_INTERVAL(self, type_, **kw): - return "INTERVAL DAY%s TO SECOND%s" % ( - type_.day_precision is not None - and "(%d)" % type_.day_precision - or "", - type_.second_precision is not None - and "(%d)" % type_.second_precision - or "", - ) - - def visit_LONG(self, type_, **kw): - return "LONG" - - def visit_TIMESTAMP(self, type_, **kw): - if getattr(type_, "local_timezone", False): - return "TIMESTAMP WITH LOCAL TIME ZONE" - elif type_.timezone: - return "TIMESTAMP WITH TIME ZONE" - else: - return "TIMESTAMP" - - def visit_DOUBLE_PRECISION(self, type_, **kw): - return self._generate_numeric(type_, "DOUBLE PRECISION", **kw) - - def visit_BINARY_DOUBLE(self, type_, **kw): - return self._generate_numeric(type_, "BINARY_DOUBLE", **kw) - - def visit_BINARY_FLOAT(self, type_, **kw): - return self._generate_numeric(type_, "BINARY_FLOAT", **kw) - - def visit_FLOAT(self, type_, **kw): - kw["_requires_binary_precision"] = True - return self._generate_numeric(type_, "FLOAT", **kw) - - def visit_NUMBER(self, type_, **kw): - return self._generate_numeric(type_, "NUMBER", **kw) - - def _generate_numeric( - self, - type_, - name, - precision=None, - scale=None, - _requires_binary_precision=False, - **kw, - ): - if precision is None: - precision = getattr(type_, "precision", None) - - if _requires_binary_precision: - binary_precision = getattr(type_, "binary_precision", None) - - if precision and binary_precision is None: - # https://www.oracletutorial.com/oracle-basics/oracle-float/ - estimated_binary_precision = int(precision / 0.30103) - raise exc.ArgumentError( - "Oracle FLOAT types use 'binary precision', which does " - "not convert cleanly from decimal 'precision'. Please " - "specify " - f"this type with a separate Oracle variant, such as " - f"{type_.__class__.__name__}(precision={precision})." - f"with_variant(oracle.FLOAT" - f"(binary_precision=" - f"{estimated_binary_precision}), 'oracle'), so that the " - "Oracle specific 'binary_precision' may be specified " - "accurately." - ) - else: - precision = binary_precision - - if scale is None: - scale = getattr(type_, "scale", None) - - if precision is None: - return name - elif scale is None: - n = "%(name)s(%(precision)s)" - return n % {"name": name, "precision": precision} - else: - n = "%(name)s(%(precision)s, %(scale)s)" - return n % {"name": name, "precision": precision, "scale": scale} - - def visit_string(self, type_, **kw): - return self.visit_VARCHAR2(type_, **kw) - - def visit_VARCHAR2(self, type_, **kw): - return self._visit_varchar(type_, "", "2") - - def visit_NVARCHAR2(self, type_, **kw): - return self._visit_varchar(type_, "N", "2") - - visit_NVARCHAR = visit_NVARCHAR2 - - def visit_VARCHAR(self, type_, **kw): - return self._visit_varchar(type_, "", "") - - def _visit_varchar(self, type_, n, num): - if not type_.length: - return "%(n)sVARCHAR%(two)s" % {"two": num, "n": n} - elif not n and self.dialect._supports_char_length: - varchar = "VARCHAR%(two)s(%(length)s CHAR)" - return varchar % {"length": type_.length, "two": num} - else: - varchar = "%(n)sVARCHAR%(two)s(%(length)s)" - return varchar % {"length": type_.length, "two": num, "n": n} - - def visit_text(self, type_, **kw): - return self.visit_CLOB(type_, **kw) - - def visit_unicode_text(self, type_, **kw): - if self.dialect._use_nchar_for_unicode: - return self.visit_NCLOB(type_, **kw) - else: - return self.visit_CLOB(type_, **kw) - - def visit_large_binary(self, type_, **kw): - return self.visit_BLOB(type_, **kw) - - def visit_big_integer(self, type_, **kw): - return self.visit_NUMBER(type_, precision=19, **kw) - - def visit_boolean(self, type_, **kw): - return self.visit_SMALLINT(type_, **kw) - - def visit_RAW(self, type_, **kw): - if type_.length: - return "RAW(%(length)s)" % {"length": type_.length} - else: - return "RAW" - - def visit_ROWID(self, type_, **kw): - return "ROWID" - - -class OracleCompiler(compiler.SQLCompiler): - """Oracle compiler modifies the lexical structure of Select - statements to work under non-ANSI configured Oracle databases, if - the use_ansi flag is False. - """ - - compound_keywords = util.update_copy( - compiler.SQLCompiler.compound_keywords, - {expression.CompoundSelect.EXCEPT: "MINUS"}, - ) - - def __init__(self, *args, **kwargs): - self.__wheres = {} - super().__init__(*args, **kwargs) - - def visit_mod_binary(self, binary, operator, **kw): - return "mod(%s, %s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_now_func(self, fn, **kw): - return "CURRENT_TIMESTAMP" - - def visit_char_length_func(self, fn, **kw): - return "LENGTH" + self.function_argspec(fn, **kw) - - def visit_match_op_binary(self, binary, operator, **kw): - return "CONTAINS (%s, %s)" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_true(self, expr, **kw): - return "1" - - def visit_false(self, expr, **kw): - return "0" - - def get_cte_preamble(self, recursive): - return "WITH" - - def get_select_hint_text(self, byfroms): - return " ".join("/*+ %s */" % text for table, text in byfroms.items()) - - def function_argspec(self, fn, **kw): - if len(fn.clauses) > 0 or fn.name.upper() not in NO_ARG_FNS: - return compiler.SQLCompiler.function_argspec(self, fn, **kw) - else: - return "" - - def visit_function(self, func, **kw): - text = super().visit_function(func, **kw) - if kw.get("asfrom", False): - text = "TABLE (%s)" % text - return text - - def visit_table_valued_column(self, element, **kw): - text = super().visit_table_valued_column(element, **kw) - text = text + ".COLUMN_VALUE" - return text - - def default_from(self): - """Called when a ``SELECT`` statement has no froms, - and no ``FROM`` clause is to be appended. - - The Oracle compiler tacks a "FROM DUAL" to the statement. - """ - - return " FROM DUAL" - - def visit_join(self, join, from_linter=None, **kwargs): - if self.dialect.use_ansi: - return compiler.SQLCompiler.visit_join( - self, join, from_linter=from_linter, **kwargs - ) - else: - if from_linter: - from_linter.edges.add((join.left, join.right)) - - kwargs["asfrom"] = True - if isinstance(join.right, expression.FromGrouping): - right = join.right.element - else: - right = join.right - return ( - self.process(join.left, from_linter=from_linter, **kwargs) - + ", " - + self.process(right, from_linter=from_linter, **kwargs) - ) - - def _get_nonansi_join_whereclause(self, froms): - clauses = [] - - def visit_join(join): - if join.isouter: - # https://docs.oracle.com/database/121/SQLRF/queries006.htm#SQLRF52354 - # "apply the outer join operator (+) to all columns of B in - # the join condition in the WHERE clause" - that is, - # unconditionally regardless of operator or the other side - def visit_binary(binary): - if isinstance( - binary.left, expression.ColumnClause - ) and join.right.is_derived_from(binary.left.table): - binary.left = _OuterJoinColumn(binary.left) - elif isinstance( - binary.right, expression.ColumnClause - ) and join.right.is_derived_from(binary.right.table): - binary.right = _OuterJoinColumn(binary.right) - - clauses.append( - visitors.cloned_traverse( - join.onclause, {}, {"binary": visit_binary} - ) - ) - else: - clauses.append(join.onclause) - - for j in join.left, join.right: - if isinstance(j, expression.Join): - visit_join(j) - elif isinstance(j, expression.FromGrouping): - visit_join(j.element) - - for f in froms: - if isinstance(f, expression.Join): - visit_join(f) - - if not clauses: - return None - else: - return sql.and_(*clauses) - - def visit_outer_join_column(self, vc, **kw): - return self.process(vc.column, **kw) + "(+)" - - def visit_sequence(self, seq, **kw): - return self.preparer.format_sequence(seq) + ".nextval" - - def get_render_as_alias_suffix(self, alias_name_text): - """Oracle doesn't like ``FROM table AS alias``""" - - return " " + alias_name_text - - def returning_clause( - self, stmt, returning_cols, *, populate_result_map, **kw - ): - columns = [] - binds = [] - - for i, column in enumerate( - expression._select_iterables(returning_cols) - ): - if ( - self.isupdate - and isinstance(column, sa_schema.Column) - and isinstance(column.server_default, Computed) - and not self.dialect._supports_update_returning_computed_cols - ): - util.warn( - "Computed columns don't work with Oracle UPDATE " - "statements that use RETURNING; the value of the column " - "*before* the UPDATE takes place is returned. It is " - "advised to not use RETURNING with an Oracle computed " - "column. Consider setting implicit_returning to False on " - "the Table object in order to avoid implicit RETURNING " - "clauses from being generated for this Table." - ) - if column.type._has_column_expression: - col_expr = column.type.column_expression(column) - else: - col_expr = column - - outparam = sql.outparam("ret_%d" % i, type_=column.type) - self.binds[outparam.key] = outparam - binds.append( - self.bindparam_string(self._truncate_bindparam(outparam)) - ) - - # has_out_parameters would in a normal case be set to True - # as a result of the compiler visiting an outparam() object. - # in this case, the above outparam() objects are not being - # visited. Ensure the statement itself didn't have other - # outparam() objects independently. - # technically, this could be supported, but as it would be - # a very strange use case without a clear rationale, disallow it - if self.has_out_parameters: - raise exc.InvalidRequestError( - "Using explicit outparam() objects with " - "UpdateBase.returning() in the same Core DML statement " - "is not supported in the Oracle dialect." - ) - - self._oracle_returning = True - - columns.append(self.process(col_expr, within_columns_clause=False)) - if populate_result_map: - self._add_to_result_map( - getattr(col_expr, "name", col_expr._anon_name_label), - getattr(col_expr, "name", col_expr._anon_name_label), - ( - column, - getattr(column, "name", None), - getattr(column, "key", None), - ), - column.type, - ) - - return "RETURNING " + ", ".join(columns) + " INTO " + ", ".join(binds) - - def _row_limit_clause(self, select, **kw): - """ORacle 12c supports OFFSET/FETCH operators - Use it instead subquery with row_number - - """ - - if ( - select._fetch_clause is not None - or not self.dialect._supports_offset_fetch - ): - return super()._row_limit_clause( - select, use_literal_execute_for_simple_int=True, **kw - ) - else: - return self.fetch_clause( - select, - fetch_clause=self._get_limit_or_fetch(select), - use_literal_execute_for_simple_int=True, - **kw, - ) - - def _get_limit_or_fetch(self, select): - if select._fetch_clause is None: - return select._limit_clause - else: - return select._fetch_clause - - def translate_select_structure(self, select_stmt, **kwargs): - select = select_stmt - - if not getattr(select, "_oracle_visit", None): - if not self.dialect.use_ansi: - froms = self._display_froms_for_select( - select, kwargs.get("asfrom", False) - ) - whereclause = self._get_nonansi_join_whereclause(froms) - if whereclause is not None: - select = select.where(whereclause) - select._oracle_visit = True - - # if fetch is used this is not needed - if ( - select._has_row_limiting_clause - and not self.dialect._supports_offset_fetch - and select._fetch_clause is None - ): - limit_clause = select._limit_clause - offset_clause = select._offset_clause - - if select._simple_int_clause(limit_clause): - limit_clause = limit_clause.render_literal_execute() - - if select._simple_int_clause(offset_clause): - offset_clause = offset_clause.render_literal_execute() - - # currently using form at: - # https://blogs.oracle.com/oraclemagazine/\ - # on-rownum-and-limiting-results - - orig_select = select - select = select._generate() - select._oracle_visit = True - - # add expressions to accommodate FOR UPDATE OF - for_update = select._for_update_arg - if for_update is not None and for_update.of: - for_update = for_update._clone() - for_update._copy_internals() - - for elem in for_update.of: - if not select.selected_columns.contains_column(elem): - select = select.add_columns(elem) - - # Wrap the middle select and add the hint - inner_subquery = select.alias() - limitselect = sql.select( - *[ - c - for c in inner_subquery.c - if orig_select.selected_columns.corresponding_column(c) - is not None - ] - ) - - if ( - limit_clause is not None - and self.dialect.optimize_limits - and select._simple_int_clause(limit_clause) - ): - limitselect = limitselect.prefix_with( - expression.text( - "/*+ FIRST_ROWS(%s) */" - % self.process(limit_clause, **kwargs) - ) - ) - - limitselect._oracle_visit = True - limitselect._is_wrapper = True - - # add expressions to accommodate FOR UPDATE OF - if for_update is not None and for_update.of: - adapter = sql_util.ClauseAdapter(inner_subquery) - for_update.of = [ - adapter.traverse(elem) for elem in for_update.of - ] - - # If needed, add the limiting clause - if limit_clause is not None: - if select._simple_int_clause(limit_clause) and ( - offset_clause is None - or select._simple_int_clause(offset_clause) - ): - max_row = limit_clause - - if offset_clause is not None: - max_row = max_row + offset_clause - - else: - max_row = limit_clause - - if offset_clause is not None: - max_row = max_row + offset_clause - limitselect = limitselect.where( - sql.literal_column("ROWNUM") <= max_row - ) - - # If needed, add the ora_rn, and wrap again with offset. - if offset_clause is None: - limitselect._for_update_arg = for_update - select = limitselect - else: - limitselect = limitselect.add_columns( - sql.literal_column("ROWNUM").label("ora_rn") - ) - limitselect._oracle_visit = True - limitselect._is_wrapper = True - - if for_update is not None and for_update.of: - limitselect_cols = limitselect.selected_columns - for elem in for_update.of: - if ( - limitselect_cols.corresponding_column(elem) - is None - ): - limitselect = limitselect.add_columns(elem) - - limit_subquery = limitselect.alias() - origselect_cols = orig_select.selected_columns - offsetselect = sql.select( - *[ - c - for c in limit_subquery.c - if origselect_cols.corresponding_column(c) - is not None - ] - ) - - offsetselect._oracle_visit = True - offsetselect._is_wrapper = True - - if for_update is not None and for_update.of: - adapter = sql_util.ClauseAdapter(limit_subquery) - for_update.of = [ - adapter.traverse(elem) for elem in for_update.of - ] - - offsetselect = offsetselect.where( - sql.literal_column("ora_rn") > offset_clause - ) - - offsetselect._for_update_arg = for_update - select = offsetselect - - return select - - def limit_clause(self, select, **kw): - return "" - - def visit_empty_set_expr(self, type_, **kw): - return "SELECT 1 FROM DUAL WHERE 1!=1" - - def for_update_clause(self, select, **kw): - if self.is_subquery(): - return "" - - tmp = " FOR UPDATE" - - if select._for_update_arg.of: - tmp += " OF " + ", ".join( - self.process(elem, **kw) for elem in select._for_update_arg.of - ) - - if select._for_update_arg.nowait: - tmp += " NOWAIT" - if select._for_update_arg.skip_locked: - tmp += " SKIP LOCKED" - - return tmp - - def visit_is_distinct_from_binary(self, binary, operator, **kw): - return "DECODE(%s, %s, 0, 1) = 1" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_is_not_distinct_from_binary(self, binary, operator, **kw): - return "DECODE(%s, %s, 0, 1) = 0" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_regexp_match_op_binary(self, binary, operator, **kw): - string = self.process(binary.left, **kw) - pattern = self.process(binary.right, **kw) - flags = binary.modifiers["flags"] - if flags is None: - return "REGEXP_LIKE(%s, %s)" % (string, pattern) - else: - return "REGEXP_LIKE(%s, %s, %s)" % ( - string, - pattern, - self.render_literal_value(flags, sqltypes.STRINGTYPE), - ) - - def visit_not_regexp_match_op_binary(self, binary, operator, **kw): - return "NOT %s" % self.visit_regexp_match_op_binary( - binary, operator, **kw - ) - - def visit_regexp_replace_op_binary(self, binary, operator, **kw): - string = self.process(binary.left, **kw) - pattern_replace = self.process(binary.right, **kw) - flags = binary.modifiers["flags"] - if flags is None: - return "REGEXP_REPLACE(%s, %s)" % ( - string, - pattern_replace, - ) - else: - return "REGEXP_REPLACE(%s, %s, %s)" % ( - string, - pattern_replace, - self.render_literal_value(flags, sqltypes.STRINGTYPE), - ) - - def visit_aggregate_strings_func(self, fn, **kw): - return "LISTAGG%s" % self.function_argspec(fn, **kw) - - -class OracleDDLCompiler(compiler.DDLCompiler): - def define_constraint_cascades(self, constraint): - text = "" - if constraint.ondelete is not None: - text += " ON DELETE %s" % constraint.ondelete - - # oracle has no ON UPDATE CASCADE - - # its only available via triggers - # https://asktom.oracle.com/tkyte/update_cascade/index.html - if constraint.onupdate is not None: - util.warn( - "Oracle does not contain native UPDATE CASCADE " - "functionality - onupdates will not be rendered for foreign " - "keys. Consider using deferrable=True, initially='deferred' " - "or triggers." - ) - - return text - - def visit_drop_table_comment(self, drop, **kw): - return "COMMENT ON TABLE %s IS ''" % self.preparer.format_table( - drop.element - ) - - def visit_create_index(self, create, **kw): - index = create.element - self._verify_index_table(index) - preparer = self.preparer - text = "CREATE " - if index.unique: - text += "UNIQUE " - if index.dialect_options["oracle"]["bitmap"]: - text += "BITMAP " - text += "INDEX %s ON %s (%s)" % ( - self._prepared_index_name(index, include_schema=True), - preparer.format_table(index.table, use_schema=True), - ", ".join( - self.sql_compiler.process( - expr, include_table=False, literal_binds=True - ) - for expr in index.expressions - ), - ) - if index.dialect_options["oracle"]["compress"] is not False: - if index.dialect_options["oracle"]["compress"] is True: - text += " COMPRESS" - else: - text += " COMPRESS %d" % ( - index.dialect_options["oracle"]["compress"] - ) - return text - - def post_create_table(self, table): - table_opts = [] - opts = table.dialect_options["oracle"] - - if opts["on_commit"]: - on_commit_options = opts["on_commit"].replace("_", " ").upper() - table_opts.append("\n ON COMMIT %s" % on_commit_options) - - if opts["compress"]: - if opts["compress"] is True: - table_opts.append("\n COMPRESS") - else: - table_opts.append("\n COMPRESS FOR %s" % (opts["compress"])) - - return "".join(table_opts) - - def get_identity_options(self, identity_options): - text = super().get_identity_options(identity_options) - text = text.replace("NO MINVALUE", "NOMINVALUE") - text = text.replace("NO MAXVALUE", "NOMAXVALUE") - text = text.replace("NO CYCLE", "NOCYCLE") - if identity_options.order is not None: - text += " ORDER" if identity_options.order else " NOORDER" - return text.strip() - - def visit_computed_column(self, generated, **kw): - text = "GENERATED ALWAYS AS (%s)" % self.sql_compiler.process( - generated.sqltext, include_table=False, literal_binds=True - ) - if generated.persisted is True: - raise exc.CompileError( - "Oracle computed columns do not support 'stored' persistence; " - "set the 'persisted' flag to None or False for Oracle support." - ) - elif generated.persisted is False: - text += " VIRTUAL" - return text - - def visit_identity_column(self, identity, **kw): - if identity.always is None: - kind = "" - else: - kind = "ALWAYS" if identity.always else "BY DEFAULT" - text = "GENERATED %s" % kind - if identity.on_null: - text += " ON NULL" - text += " AS IDENTITY" - options = self.get_identity_options(identity) - if options: - text += " (%s)" % options - return text - - -class OracleIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = {x.lower() for x in RESERVED_WORDS} - illegal_initial_characters = {str(dig) for dig in range(0, 10)}.union( - ["_", "$"] - ) - - def _bindparam_requires_quotes(self, value): - """Return True if the given identifier requires quoting.""" - lc_value = value.lower() - return ( - lc_value in self.reserved_words - or value[0] in self.illegal_initial_characters - or not self.legal_characters.match(str(value)) - ) - - def format_savepoint(self, savepoint): - name = savepoint.ident.lstrip("_") - return super().format_savepoint(savepoint, name) - - -class OracleExecutionContext(default.DefaultExecutionContext): - def fire_sequence(self, seq, type_): - return self._execute_scalar( - "SELECT " - + self.identifier_preparer.format_sequence(seq) - + ".nextval FROM DUAL", - type_, - ) - - def pre_exec(self): - if self.statement and "_oracle_dblink" in self.execution_options: - self.statement = self.statement.replace( - dictionary.DB_LINK_PLACEHOLDER, - self.execution_options["_oracle_dblink"], - ) - - -class OracleDialect(default.DefaultDialect): - name = "oracle" - supports_statement_cache = True - supports_alter = True - max_identifier_length = 128 - - _supports_offset_fetch = True - - insert_returning = True - update_returning = True - delete_returning = True - - div_is_floordiv = False - - supports_simple_order_by_label = False - cte_follows_insert = True - returns_native_bytes = True - - supports_sequences = True - sequences_optional = False - postfetch_lastrowid = False - - default_paramstyle = "named" - colspecs = colspecs - ischema_names = ischema_names - requires_name_normalize = True - - supports_comments = True - - supports_default_values = False - supports_default_metavalue = True - supports_empty_insert = False - supports_identity_columns = True - - statement_compiler = OracleCompiler - ddl_compiler = OracleDDLCompiler - type_compiler_cls = OracleTypeCompiler - preparer = OracleIdentifierPreparer - execution_ctx_cls = OracleExecutionContext - - reflection_options = ("oracle_resolve_synonyms",) - - _use_nchar_for_unicode = False - - construct_arguments = [ - ( - sa_schema.Table, - {"resolve_synonyms": False, "on_commit": None, "compress": False}, - ), - (sa_schema.Index, {"bitmap": False, "compress": False}), - ] - - @util.deprecated_params( - use_binds_for_limits=( - "1.4", - "The ``use_binds_for_limits`` Oracle dialect parameter is " - "deprecated. The dialect now renders LIMIT /OFFSET integers " - "inline in all cases using a post-compilation hook, so that the " - "value is still represented by a 'bound parameter' on the Core " - "Expression side.", - ) - ) - def __init__( - self, - use_ansi=True, - optimize_limits=False, - use_binds_for_limits=None, - use_nchar_for_unicode=False, - exclude_tablespaces=("SYSTEM", "SYSAUX"), - enable_offset_fetch=True, - **kwargs, - ): - default.DefaultDialect.__init__(self, **kwargs) - self._use_nchar_for_unicode = use_nchar_for_unicode - self.use_ansi = use_ansi - self.optimize_limits = optimize_limits - self.exclude_tablespaces = exclude_tablespaces - self.enable_offset_fetch = self._supports_offset_fetch = ( - enable_offset_fetch - ) - - def initialize(self, connection): - super().initialize(connection) - - # Oracle 8i has RETURNING: - # https://docs.oracle.com/cd/A87860_01/doc/index.htm - - # so does Oracle8: - # https://docs.oracle.com/cd/A64702_01/doc/index.htm - - if self._is_oracle_8: - self.colspecs = self.colspecs.copy() - self.colspecs.pop(sqltypes.Interval) - self.use_ansi = False - - self.supports_identity_columns = self.server_version_info >= (12,) - self._supports_offset_fetch = ( - self.enable_offset_fetch and self.server_version_info >= (12,) - ) - - def _get_effective_compat_server_version_info(self, connection): - # dialect does not need compat levels below 12.2, so don't query - # in those cases - - if self.server_version_info < (12, 2): - return self.server_version_info - try: - compat = connection.exec_driver_sql( - "SELECT value FROM v$parameter WHERE name = 'compatible'" - ).scalar() - except exc.DBAPIError: - compat = None - - if compat: - try: - return tuple(int(x) for x in compat.split(".")) - except: - return self.server_version_info - else: - return self.server_version_info - - @property - def _is_oracle_8(self): - return self.server_version_info and self.server_version_info < (9,) - - @property - def _supports_table_compression(self): - return self.server_version_info and self.server_version_info >= (10, 1) - - @property - def _supports_table_compress_for(self): - return self.server_version_info and self.server_version_info >= (11,) - - @property - def _supports_char_length(self): - return not self._is_oracle_8 - - @property - def _supports_update_returning_computed_cols(self): - # on version 18 this error is no longet present while it happens on 11 - # it may work also on versions before the 18 - return self.server_version_info and self.server_version_info >= (18,) - - @property - def _supports_except_all(self): - return self.server_version_info and self.server_version_info >= (21,) - - def do_release_savepoint(self, connection, name): - # Oracle does not support RELEASE SAVEPOINT - pass - - def _check_max_identifier_length(self, connection): - if self._get_effective_compat_server_version_info(connection) < ( - 12, - 2, - ): - return 30 - else: - # use the default - return None - - def get_isolation_level_values(self, dbapi_connection): - return ["READ COMMITTED", "SERIALIZABLE"] - - def get_default_isolation_level(self, dbapi_conn): - try: - return self.get_isolation_level(dbapi_conn) - except NotImplementedError: - raise - except: - return "READ COMMITTED" - - def _execute_reflection( - self, connection, query, dblink, returns_long, params=None - ): - if dblink and not dblink.startswith("@"): - dblink = f"@{dblink}" - execution_options = { - # handle db links - "_oracle_dblink": dblink or "", - # override any schema translate map - "schema_translate_map": None, - } - - if dblink and returns_long: - # Oracle seems to error with - # "ORA-00997: illegal use of LONG datatype" when returning - # LONG columns via a dblink in a query with bind params - # This type seems to be very hard to cast into something else - # so it seems easier to just use bind param in this case - def visit_bindparam(bindparam): - bindparam.literal_execute = True - - query = visitors.cloned_traverse( - query, {}, {"bindparam": visit_bindparam} - ) - return connection.execute( - query, params, execution_options=execution_options - ) - - @util.memoized_property - def _has_table_query(self): - # materialized views are returned by all_tables - tables = ( - select( - dictionary.all_tables.c.table_name, - dictionary.all_tables.c.owner, - ) - .union_all( - select( - dictionary.all_views.c.view_name.label("table_name"), - dictionary.all_views.c.owner, - ) - ) - .subquery("tables_and_views") - ) - - query = select(tables.c.table_name).where( - tables.c.table_name == bindparam("table_name"), - tables.c.owner == bindparam("owner"), - ) - return query - - @reflection.cache - def has_table( - self, connection, table_name, schema=None, dblink=None, **kw - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - self._ensure_has_table_connection(connection) - - if not schema: - schema = self.default_schema_name - - params = { - "table_name": self.denormalize_name(table_name), - "owner": self.denormalize_schema_name(schema), - } - cursor = self._execute_reflection( - connection, - self._has_table_query, - dblink, - returns_long=False, - params=params, - ) - return bool(cursor.scalar()) - - @reflection.cache - def has_sequence( - self, connection, sequence_name, schema=None, dblink=None, **kw - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - if not schema: - schema = self.default_schema_name - - query = select(dictionary.all_sequences.c.sequence_name).where( - dictionary.all_sequences.c.sequence_name - == self.denormalize_schema_name(sequence_name), - dictionary.all_sequences.c.sequence_owner - == self.denormalize_schema_name(schema), - ) - - cursor = self._execute_reflection( - connection, query, dblink, returns_long=False - ) - return bool(cursor.scalar()) - - def _get_default_schema_name(self, connection): - return self.normalize_name( - connection.exec_driver_sql( - "select sys_context( 'userenv', 'current_schema' ) from dual" - ).scalar() - ) - - def denormalize_schema_name(self, name): - # look for quoted_name - force = getattr(name, "quote", None) - if force is None and name == "public": - # look for case insensitive, no quoting specified, "public" - return "PUBLIC" - return super().denormalize_name(name) - - @reflection.flexi_cache( - ("schema", InternalTraversal.dp_string), - ("filter_names", InternalTraversal.dp_string_list), - ("dblink", InternalTraversal.dp_string), - ) - def _get_synonyms(self, connection, schema, filter_names, dblink, **kw): - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - - has_filter_names, params = self._prepare_filter_names(filter_names) - query = select( - dictionary.all_synonyms.c.synonym_name, - dictionary.all_synonyms.c.table_name, - dictionary.all_synonyms.c.table_owner, - dictionary.all_synonyms.c.db_link, - ).where(dictionary.all_synonyms.c.owner == owner) - if has_filter_names: - query = query.where( - dictionary.all_synonyms.c.synonym_name.in_( - params["filter_names"] - ) - ) - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).mappings() - return result.all() - - @lru_cache() - def _all_objects_query( - self, owner, scope, kind, has_filter_names, has_mat_views - ): - query = ( - select(dictionary.all_objects.c.object_name) - .select_from(dictionary.all_objects) - .where(dictionary.all_objects.c.owner == owner) - ) - - # NOTE: materialized views are listed in all_objects twice; - # once as MATERIALIZE VIEW and once as TABLE - if kind is ObjectKind.ANY: - # materilaized view are listed also as tables so there is no - # need to add them to the in_. - query = query.where( - dictionary.all_objects.c.object_type.in_(("TABLE", "VIEW")) - ) - else: - object_type = [] - if ObjectKind.VIEW in kind: - object_type.append("VIEW") - if ( - ObjectKind.MATERIALIZED_VIEW in kind - and ObjectKind.TABLE not in kind - ): - # materilaized view are listed also as tables so there is no - # need to add them to the in_ if also selecting tables. - object_type.append("MATERIALIZED VIEW") - if ObjectKind.TABLE in kind: - object_type.append("TABLE") - if has_mat_views and ObjectKind.MATERIALIZED_VIEW not in kind: - # materialized view are listed also as tables, - # so they need to be filtered out - # EXCEPT ALL / MINUS profiles as faster than using - # NOT EXISTS or NOT IN with a subquery, but it's in - # general faster to get the mat view names and exclude - # them only when needed - query = query.where( - dictionary.all_objects.c.object_name.not_in( - bindparam("mat_views") - ) - ) - query = query.where( - dictionary.all_objects.c.object_type.in_(object_type) - ) - - # handles scope - if scope is ObjectScope.DEFAULT: - query = query.where(dictionary.all_objects.c.temporary == "N") - elif scope is ObjectScope.TEMPORARY: - query = query.where(dictionary.all_objects.c.temporary == "Y") - - if has_filter_names: - query = query.where( - dictionary.all_objects.c.object_name.in_( - bindparam("filter_names") - ) - ) - return query - - @reflection.flexi_cache( - ("schema", InternalTraversal.dp_string), - ("scope", InternalTraversal.dp_plain_obj), - ("kind", InternalTraversal.dp_plain_obj), - ("filter_names", InternalTraversal.dp_string_list), - ("dblink", InternalTraversal.dp_string), - ) - def _get_all_objects( - self, connection, schema, scope, kind, filter_names, dblink, **kw - ): - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - - has_filter_names, params = self._prepare_filter_names(filter_names) - has_mat_views = False - if ( - ObjectKind.TABLE in kind - and ObjectKind.MATERIALIZED_VIEW not in kind - ): - # see note in _all_objects_query - mat_views = self.get_materialized_view_names( - connection, schema, dblink, _normalize=False, **kw - ) - if mat_views: - params["mat_views"] = mat_views - has_mat_views = True - - query = self._all_objects_query( - owner, scope, kind, has_filter_names, has_mat_views - ) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False, params=params - ).scalars() - - return result.all() - - def _handle_synonyms_decorator(fn): - @wraps(fn) - def wrapper(self, *args, **kwargs): - return self._handle_synonyms(fn, *args, **kwargs) - - return wrapper - - def _handle_synonyms(self, fn, connection, *args, **kwargs): - if not kwargs.get("oracle_resolve_synonyms", False): - return fn(self, connection, *args, **kwargs) - - original_kw = kwargs.copy() - schema = kwargs.pop("schema", None) - result = self._get_synonyms( - connection, - schema=schema, - filter_names=kwargs.pop("filter_names", None), - dblink=kwargs.pop("dblink", None), - info_cache=kwargs.get("info_cache", None), - ) - - dblinks_owners = defaultdict(dict) - for row in result: - key = row["db_link"], row["table_owner"] - tn = self.normalize_name(row["table_name"]) - dblinks_owners[key][tn] = row["synonym_name"] - - if not dblinks_owners: - # No synonym, do the plain thing - return fn(self, connection, *args, **original_kw) - - data = {} - for (dblink, table_owner), mapping in dblinks_owners.items(): - call_kw = { - **original_kw, - "schema": table_owner, - "dblink": self.normalize_name(dblink), - "filter_names": mapping.keys(), - } - call_result = fn(self, connection, *args, **call_kw) - for (_, tn), value in call_result: - synonym_name = self.normalize_name(mapping[tn]) - data[(schema, synonym_name)] = value - return data.items() - - @reflection.cache - def get_schema_names(self, connection, dblink=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - query = select(dictionary.all_users.c.username).order_by( - dictionary.all_users.c.username - ) - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(row) for row in result] - - @reflection.cache - def get_table_names(self, connection, schema=None, dblink=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - # note that table_names() isn't loading DBLINKed or synonym'ed tables - if schema is None: - schema = self.default_schema_name - - den_schema = self.denormalize_schema_name(schema) - if kw.get("oracle_resolve_synonyms", False): - tables = ( - select( - dictionary.all_tables.c.table_name, - dictionary.all_tables.c.owner, - dictionary.all_tables.c.iot_name, - dictionary.all_tables.c.duration, - dictionary.all_tables.c.tablespace_name, - ) - .union_all( - select( - dictionary.all_synonyms.c.synonym_name.label( - "table_name" - ), - dictionary.all_synonyms.c.owner, - dictionary.all_tables.c.iot_name, - dictionary.all_tables.c.duration, - dictionary.all_tables.c.tablespace_name, - ) - .select_from(dictionary.all_tables) - .join( - dictionary.all_synonyms, - and_( - dictionary.all_tables.c.table_name - == dictionary.all_synonyms.c.table_name, - dictionary.all_tables.c.owner - == func.coalesce( - dictionary.all_synonyms.c.table_owner, - dictionary.all_synonyms.c.owner, - ), - ), - ) - ) - .subquery("available_tables") - ) - else: - tables = dictionary.all_tables - - query = select(tables.c.table_name) - if self.exclude_tablespaces: - query = query.where( - func.coalesce( - tables.c.tablespace_name, "no tablespace" - ).not_in(self.exclude_tablespaces) - ) - query = query.where( - tables.c.owner == den_schema, - tables.c.iot_name.is_(null()), - tables.c.duration.is_(null()), - ) - - # remove materialized views - mat_query = select( - dictionary.all_mviews.c.mview_name.label("table_name") - ).where(dictionary.all_mviews.c.owner == den_schema) - - query = ( - query.except_all(mat_query) - if self._supports_except_all - else query.except_(mat_query) - ) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(row) for row in result] - - @reflection.cache - def get_temp_table_names(self, connection, dblink=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - schema = self.denormalize_schema_name(self.default_schema_name) - - query = select(dictionary.all_tables.c.table_name) - if self.exclude_tablespaces: - query = query.where( - func.coalesce( - dictionary.all_tables.c.tablespace_name, "no tablespace" - ).not_in(self.exclude_tablespaces) - ) - query = query.where( - dictionary.all_tables.c.owner == schema, - dictionary.all_tables.c.iot_name.is_(null()), - dictionary.all_tables.c.duration.is_not(null()), - ) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(row) for row in result] - - @reflection.cache - def get_materialized_view_names( - self, connection, schema=None, dblink=None, _normalize=True, **kw - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - if not schema: - schema = self.default_schema_name - - query = select(dictionary.all_mviews.c.mview_name).where( - dictionary.all_mviews.c.owner - == self.denormalize_schema_name(schema) - ) - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - if _normalize: - return [self.normalize_name(row) for row in result] - else: - return result.all() - - @reflection.cache - def get_view_names(self, connection, schema=None, dblink=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - if not schema: - schema = self.default_schema_name - - query = select(dictionary.all_views.c.view_name).where( - dictionary.all_views.c.owner - == self.denormalize_schema_name(schema) - ) - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(row) for row in result] - - @reflection.cache - def get_sequence_names(self, connection, schema=None, dblink=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link.""" - if not schema: - schema = self.default_schema_name - query = select(dictionary.all_sequences.c.sequence_name).where( - dictionary.all_sequences.c.sequence_owner - == self.denormalize_schema_name(schema) - ) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(row) for row in result] - - def _value_or_raise(self, data, table, schema): - table = self.normalize_name(str(table)) - try: - return dict(data)[(schema, table)] - except KeyError: - raise exc.NoSuchTableError( - f"{schema}.{table}" if schema else table - ) from None - - def _prepare_filter_names(self, filter_names): - if filter_names: - fn = [self.denormalize_name(name) for name in filter_names] - return True, {"filter_names": fn} - else: - return False, {} - - @reflection.cache - def get_table_options(self, connection, table_name, schema=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_table_options( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _table_options_query( - self, owner, scope, kind, has_filter_names, has_mat_views - ): - query = select( - dictionary.all_tables.c.table_name, - dictionary.all_tables.c.compression, - dictionary.all_tables.c.compress_for, - ).where(dictionary.all_tables.c.owner == owner) - if has_filter_names: - query = query.where( - dictionary.all_tables.c.table_name.in_( - bindparam("filter_names") - ) - ) - if scope is ObjectScope.DEFAULT: - query = query.where(dictionary.all_tables.c.duration.is_(null())) - elif scope is ObjectScope.TEMPORARY: - query = query.where( - dictionary.all_tables.c.duration.is_not(null()) - ) - - if ( - has_mat_views - and ObjectKind.TABLE in kind - and ObjectKind.MATERIALIZED_VIEW not in kind - ): - # cant use EXCEPT ALL / MINUS here because we don't have an - # excludable row vs. the query above - # outerjoin + where null works better on oracle 21 but 11 does - # not like it at all. this is the next best thing - - query = query.where( - dictionary.all_tables.c.table_name.not_in( - bindparam("mat_views") - ) - ) - elif ( - ObjectKind.TABLE not in kind - and ObjectKind.MATERIALIZED_VIEW in kind - ): - query = query.where( - dictionary.all_tables.c.table_name.in_(bindparam("mat_views")) - ) - return query - - @_handle_synonyms_decorator - def get_multi_table_options( - self, - connection, - *, - schema, - filter_names, - scope, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - - has_filter_names, params = self._prepare_filter_names(filter_names) - has_mat_views = False - - if ( - ObjectKind.TABLE in kind - and ObjectKind.MATERIALIZED_VIEW not in kind - ): - # see note in _table_options_query - mat_views = self.get_materialized_view_names( - connection, schema, dblink, _normalize=False, **kw - ) - if mat_views: - params["mat_views"] = mat_views - has_mat_views = True - elif ( - ObjectKind.TABLE not in kind - and ObjectKind.MATERIALIZED_VIEW in kind - ): - mat_views = self.get_materialized_view_names( - connection, schema, dblink, _normalize=False, **kw - ) - params["mat_views"] = mat_views - - options = {} - default = ReflectionDefaults.table_options - - if ObjectKind.TABLE in kind or ObjectKind.MATERIALIZED_VIEW in kind: - query = self._table_options_query( - owner, scope, kind, has_filter_names, has_mat_views - ) - result = self._execute_reflection( - connection, query, dblink, returns_long=False, params=params - ) - - for table, compression, compress_for in result: - if compression == "ENABLED": - data = {"oracle_compress": compress_for} - else: - data = default() - options[(schema, self.normalize_name(table))] = data - if ObjectKind.VIEW in kind and ObjectScope.DEFAULT in scope: - # add the views (no temporary views) - for view in self.get_view_names(connection, schema, dblink, **kw): - if not filter_names or view in filter_names: - options[(schema, view)] = default() - - return options.items() - - @reflection.cache - def get_columns(self, connection, table_name, schema=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - - data = self.get_multi_columns( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - def _run_batches( - self, connection, query, dblink, returns_long, mappings, all_objects - ): - each_batch = 500 - batches = list(all_objects) - while batches: - batch = batches[0:each_batch] - batches[0:each_batch] = [] - - result = self._execute_reflection( - connection, - query, - dblink, - returns_long=returns_long, - params={"all_objects": batch}, - ) - if mappings: - yield from result.mappings() - else: - yield from result - - @lru_cache() - def _column_query(self, owner): - all_cols = dictionary.all_tab_cols - all_comments = dictionary.all_col_comments - all_ids = dictionary.all_tab_identity_cols - - if self.server_version_info >= (12,): - add_cols = ( - all_cols.c.default_on_null, - sql.case( - (all_ids.c.table_name.is_(None), sql.null()), - else_=all_ids.c.generation_type - + "," - + all_ids.c.identity_options, - ).label("identity_options"), - ) - join_identity_cols = True - else: - add_cols = ( - sql.null().label("default_on_null"), - sql.null().label("identity_options"), - ) - join_identity_cols = False - - # NOTE: on oracle cannot create tables/views without columns and - # a table cannot have all column hidden: - # ORA-54039: table must have at least one column that is not invisible - # all_tab_cols returns data for tables/views/mat-views. - # all_tab_cols does not return recycled tables - - query = ( - select( - all_cols.c.table_name, - all_cols.c.column_name, - all_cols.c.data_type, - all_cols.c.char_length, - all_cols.c.data_precision, - all_cols.c.data_scale, - all_cols.c.nullable, - all_cols.c.data_default, - all_comments.c.comments, - all_cols.c.virtual_column, - *add_cols, - ).select_from(all_cols) - # NOTE: all_col_comments has a row for each column even if no - # comment is present, so a join could be performed, but there - # seems to be no difference compared to an outer join - .outerjoin( - all_comments, - and_( - all_cols.c.table_name == all_comments.c.table_name, - all_cols.c.column_name == all_comments.c.column_name, - all_cols.c.owner == all_comments.c.owner, - ), - ) - ) - if join_identity_cols: - query = query.outerjoin( - all_ids, - and_( - all_cols.c.table_name == all_ids.c.table_name, - all_cols.c.column_name == all_ids.c.column_name, - all_cols.c.owner == all_ids.c.owner, - ), - ) - - query = query.where( - all_cols.c.table_name.in_(bindparam("all_objects")), - all_cols.c.hidden_column == "NO", - all_cols.c.owner == owner, - ).order_by(all_cols.c.table_name, all_cols.c.column_id) - return query - - @_handle_synonyms_decorator - def get_multi_columns( - self, - connection, - *, - schema, - filter_names, - scope, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - query = self._column_query(owner) - - if ( - filter_names - and kind is ObjectKind.ANY - and scope is ObjectScope.ANY - ): - all_objects = [self.denormalize_name(n) for n in filter_names] - else: - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - columns = defaultdict(list) - - # all_tab_cols.data_default is LONG - result = self._run_batches( - connection, - query, - dblink, - returns_long=True, - mappings=True, - all_objects=all_objects, - ) - - def maybe_int(value): - if isinstance(value, float) and value.is_integer(): - return int(value) - else: - return value - - remove_size = re.compile(r"\(\d+\)") - - for row_dict in result: - table_name = self.normalize_name(row_dict["table_name"]) - orig_colname = row_dict["column_name"] - colname = self.normalize_name(orig_colname) - coltype = row_dict["data_type"] - precision = maybe_int(row_dict["data_precision"]) - - if coltype == "NUMBER": - scale = maybe_int(row_dict["data_scale"]) - if precision is None and scale == 0: - coltype = INTEGER() - else: - coltype = NUMBER(precision, scale) - elif coltype == "FLOAT": - # https://docs.oracle.com/cd/B14117_01/server.101/b10758/sqlqr06.htm - if precision == 126: - # The DOUBLE PRECISION datatype is a floating-point - # number with binary precision 126. - coltype = DOUBLE_PRECISION() - elif precision == 63: - # The REAL datatype is a floating-point number with a - # binary precision of 63, or 18 decimal. - coltype = REAL() - else: - # non standard precision - coltype = FLOAT(binary_precision=precision) - - elif coltype in ("VARCHAR2", "NVARCHAR2", "CHAR", "NCHAR"): - char_length = maybe_int(row_dict["char_length"]) - coltype = self.ischema_names.get(coltype)(char_length) - elif "WITH TIME ZONE" in coltype: - coltype = TIMESTAMP(timezone=True) - elif "WITH LOCAL TIME ZONE" in coltype: - coltype = TIMESTAMP(local_timezone=True) - else: - coltype = re.sub(remove_size, "", coltype) - try: - coltype = self.ischema_names[coltype] - except KeyError: - util.warn( - "Did not recognize type '%s' of column '%s'" - % (coltype, colname) - ) - coltype = sqltypes.NULLTYPE - - default = row_dict["data_default"] - if row_dict["virtual_column"] == "YES": - computed = dict(sqltext=default) - default = None - else: - computed = None - - identity_options = row_dict["identity_options"] - if identity_options is not None: - identity = self._parse_identity_options( - identity_options, row_dict["default_on_null"] - ) - default = None - else: - identity = None - - cdict = { - "name": colname, - "type": coltype, - "nullable": row_dict["nullable"] == "Y", - "default": default, - "comment": row_dict["comments"], - } - if orig_colname.lower() == orig_colname: - cdict["quote"] = True - if computed is not None: - cdict["computed"] = computed - if identity is not None: - cdict["identity"] = identity - - columns[(schema, table_name)].append(cdict) - - # NOTE: default not needed since all tables have columns - # default = ReflectionDefaults.columns - # return ( - # (key, value if value else default()) - # for key, value in columns.items() - # ) - return columns.items() - - def _parse_identity_options(self, identity_options, default_on_null): - # identity_options is a string that starts with 'ALWAYS,' or - # 'BY DEFAULT,' and continues with - # START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 123, MIN_VALUE: 1, - # CYCLE_FLAG: N, CACHE_SIZE: 1, ORDER_FLAG: N, SCALE_FLAG: N, - # EXTEND_FLAG: N, SESSION_FLAG: N, KEEP_VALUE: N - parts = [p.strip() for p in identity_options.split(",")] - identity = { - "always": parts[0] == "ALWAYS", - "on_null": default_on_null == "YES", - } - - for part in parts[1:]: - option, value = part.split(":") - value = value.strip() - - if "START WITH" in option: - identity["start"] = int(value) - elif "INCREMENT BY" in option: - identity["increment"] = int(value) - elif "MAX_VALUE" in option: - identity["maxvalue"] = int(value) - elif "MIN_VALUE" in option: - identity["minvalue"] = int(value) - elif "CYCLE_FLAG" in option: - identity["cycle"] = value == "Y" - elif "CACHE_SIZE" in option: - identity["cache"] = int(value) - elif "ORDER_FLAG" in option: - identity["order"] = value == "Y" - return identity - - @reflection.cache - def get_table_comment(self, connection, table_name, schema=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_table_comment( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _comment_query(self, owner, scope, kind, has_filter_names): - # NOTE: all_tab_comments / all_mview_comments have a row for all - # object even if they don't have comments - queries = [] - if ObjectKind.TABLE in kind or ObjectKind.VIEW in kind: - # all_tab_comments returns also plain views - tbl_view = select( - dictionary.all_tab_comments.c.table_name, - dictionary.all_tab_comments.c.comments, - ).where( - dictionary.all_tab_comments.c.owner == owner, - dictionary.all_tab_comments.c.table_name.not_like("BIN$%"), - ) - if ObjectKind.VIEW not in kind: - tbl_view = tbl_view.where( - dictionary.all_tab_comments.c.table_type == "TABLE" - ) - elif ObjectKind.TABLE not in kind: - tbl_view = tbl_view.where( - dictionary.all_tab_comments.c.table_type == "VIEW" - ) - queries.append(tbl_view) - if ObjectKind.MATERIALIZED_VIEW in kind: - mat_view = select( - dictionary.all_mview_comments.c.mview_name.label("table_name"), - dictionary.all_mview_comments.c.comments, - ).where( - dictionary.all_mview_comments.c.owner == owner, - dictionary.all_mview_comments.c.mview_name.not_like("BIN$%"), - ) - queries.append(mat_view) - if len(queries) == 1: - query = queries[0] - else: - union = sql.union_all(*queries).subquery("tables_and_views") - query = select(union.c.table_name, union.c.comments) - - name_col = query.selected_columns.table_name - - if scope in (ObjectScope.DEFAULT, ObjectScope.TEMPORARY): - temp = "Y" if scope is ObjectScope.TEMPORARY else "N" - # need distinct since materialized view are listed also - # as tables in all_objects - query = query.distinct().join( - dictionary.all_objects, - and_( - dictionary.all_objects.c.owner == owner, - dictionary.all_objects.c.object_name == name_col, - dictionary.all_objects.c.temporary == temp, - ), - ) - if has_filter_names: - query = query.where(name_col.in_(bindparam("filter_names"))) - return query - - @_handle_synonyms_decorator - def get_multi_table_comment( - self, - connection, - *, - schema, - filter_names, - scope, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - has_filter_names, params = self._prepare_filter_names(filter_names) - query = self._comment_query(owner, scope, kind, has_filter_names) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False, params=params - ) - default = ReflectionDefaults.table_comment - # materialized views by default seem to have a comment like - # "snapshot table for snapshot owner.mat_view_name" - ignore_mat_view = "snapshot table for snapshot " - return ( - ( - (schema, self.normalize_name(table)), - ( - {"text": comment} - if comment is not None - and not comment.startswith(ignore_mat_view) - else default() - ), - ) - for table, comment in result - ) - - @reflection.cache - def get_indexes(self, connection, table_name, schema=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_indexes( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _index_query(self, owner): - return ( - select( - dictionary.all_ind_columns.c.table_name, - dictionary.all_ind_columns.c.index_name, - dictionary.all_ind_columns.c.column_name, - dictionary.all_indexes.c.index_type, - dictionary.all_indexes.c.uniqueness, - dictionary.all_indexes.c.compression, - dictionary.all_indexes.c.prefix_length, - dictionary.all_ind_columns.c.descend, - dictionary.all_ind_expressions.c.column_expression, - ) - .select_from(dictionary.all_ind_columns) - .join( - dictionary.all_indexes, - sql.and_( - dictionary.all_ind_columns.c.index_name - == dictionary.all_indexes.c.index_name, - dictionary.all_ind_columns.c.index_owner - == dictionary.all_indexes.c.owner, - ), - ) - .outerjoin( - # NOTE: this adds about 20% to the query time. Using a - # case expression with a scalar subquery only when needed - # with the assumption that most indexes are not expression - # would be faster but oracle does not like that with - # LONG datatype. It errors with: - # ORA-00997: illegal use of LONG datatype - dictionary.all_ind_expressions, - sql.and_( - dictionary.all_ind_expressions.c.index_name - == dictionary.all_ind_columns.c.index_name, - dictionary.all_ind_expressions.c.index_owner - == dictionary.all_ind_columns.c.index_owner, - dictionary.all_ind_expressions.c.column_position - == dictionary.all_ind_columns.c.column_position, - ), - ) - .where( - dictionary.all_indexes.c.table_owner == owner, - dictionary.all_indexes.c.table_name.in_( - bindparam("all_objects") - ), - ) - .order_by( - dictionary.all_ind_columns.c.index_name, - dictionary.all_ind_columns.c.column_position, - ) - ) - - @reflection.flexi_cache( - ("schema", InternalTraversal.dp_string), - ("dblink", InternalTraversal.dp_string), - ("all_objects", InternalTraversal.dp_string_list), - ) - def _get_indexes_rows(self, connection, schema, dblink, all_objects, **kw): - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - - query = self._index_query(owner) - - pks = { - row_dict["constraint_name"] - for row_dict in self._get_all_constraint_rows( - connection, schema, dblink, all_objects, **kw - ) - if row_dict["constraint_type"] == "P" - } - - # all_ind_expressions.column_expression is LONG - result = self._run_batches( - connection, - query, - dblink, - returns_long=True, - mappings=True, - all_objects=all_objects, - ) - - return [ - row_dict - for row_dict in result - if row_dict["index_name"] not in pks - ] - - @_handle_synonyms_decorator - def get_multi_indexes( - self, - connection, - *, - schema, - filter_names, - scope, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - uniqueness = {"NONUNIQUE": False, "UNIQUE": True} - enabled = {"DISABLED": False, "ENABLED": True} - is_bitmap = {"BITMAP", "FUNCTION-BASED BITMAP"} - - indexes = defaultdict(dict) - - for row_dict in self._get_indexes_rows( - connection, schema, dblink, all_objects, **kw - ): - index_name = self.normalize_name(row_dict["index_name"]) - table_name = self.normalize_name(row_dict["table_name"]) - table_indexes = indexes[(schema, table_name)] - - if index_name not in table_indexes: - table_indexes[index_name] = index_dict = { - "name": index_name, - "column_names": [], - "dialect_options": {}, - "unique": uniqueness.get(row_dict["uniqueness"], False), - } - do = index_dict["dialect_options"] - if row_dict["index_type"] in is_bitmap: - do["oracle_bitmap"] = True - if enabled.get(row_dict["compression"], False): - do["oracle_compress"] = row_dict["prefix_length"] - - else: - index_dict = table_indexes[index_name] - - expr = row_dict["column_expression"] - if expr is not None: - index_dict["column_names"].append(None) - if "expressions" in index_dict: - index_dict["expressions"].append(expr) - else: - index_dict["expressions"] = index_dict["column_names"][:-1] - index_dict["expressions"].append(expr) - - if row_dict["descend"].lower() != "asc": - assert row_dict["descend"].lower() == "desc" - cs = index_dict.setdefault("column_sorting", {}) - cs[expr] = ("desc",) - else: - assert row_dict["descend"].lower() == "asc" - cn = self.normalize_name(row_dict["column_name"]) - index_dict["column_names"].append(cn) - if "expressions" in index_dict: - index_dict["expressions"].append(cn) - - default = ReflectionDefaults.indexes - - return ( - (key, list(indexes[key].values()) if key in indexes else default()) - for key in ( - (schema, self.normalize_name(obj_name)) - for obj_name in all_objects - ) - ) - - @reflection.cache - def get_pk_constraint(self, connection, table_name, schema=None, **kw): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_pk_constraint( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _constraint_query(self, owner): - local = dictionary.all_cons_columns.alias("local") - remote = dictionary.all_cons_columns.alias("remote") - return ( - select( - dictionary.all_constraints.c.table_name, - dictionary.all_constraints.c.constraint_type, - dictionary.all_constraints.c.constraint_name, - local.c.column_name.label("local_column"), - remote.c.table_name.label("remote_table"), - remote.c.column_name.label("remote_column"), - remote.c.owner.label("remote_owner"), - dictionary.all_constraints.c.search_condition, - dictionary.all_constraints.c.delete_rule, - ) - .select_from(dictionary.all_constraints) - .join( - local, - and_( - local.c.owner == dictionary.all_constraints.c.owner, - dictionary.all_constraints.c.constraint_name - == local.c.constraint_name, - ), - ) - .outerjoin( - remote, - and_( - dictionary.all_constraints.c.r_owner == remote.c.owner, - dictionary.all_constraints.c.r_constraint_name - == remote.c.constraint_name, - or_( - remote.c.position.is_(sql.null()), - local.c.position == remote.c.position, - ), - ), - ) - .where( - dictionary.all_constraints.c.owner == owner, - dictionary.all_constraints.c.table_name.in_( - bindparam("all_objects") - ), - dictionary.all_constraints.c.constraint_type.in_( - ("R", "P", "U", "C") - ), - ) - .order_by( - dictionary.all_constraints.c.constraint_name, local.c.position - ) - ) - - @reflection.flexi_cache( - ("schema", InternalTraversal.dp_string), - ("dblink", InternalTraversal.dp_string), - ("all_objects", InternalTraversal.dp_string_list), - ) - def _get_all_constraint_rows( - self, connection, schema, dblink, all_objects, **kw - ): - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - query = self._constraint_query(owner) - - # since the result is cached a list must be created - values = list( - self._run_batches( - connection, - query, - dblink, - returns_long=False, - mappings=True, - all_objects=all_objects, - ) - ) - return values - - @_handle_synonyms_decorator - def get_multi_pk_constraint( - self, - connection, - *, - scope, - schema, - filter_names, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - primary_keys = defaultdict(dict) - default = ReflectionDefaults.pk_constraint - - for row_dict in self._get_all_constraint_rows( - connection, schema, dblink, all_objects, **kw - ): - if row_dict["constraint_type"] != "P": - continue - table_name = self.normalize_name(row_dict["table_name"]) - constraint_name = self.normalize_name(row_dict["constraint_name"]) - column_name = self.normalize_name(row_dict["local_column"]) - - table_pk = primary_keys[(schema, table_name)] - if not table_pk: - table_pk["name"] = constraint_name - table_pk["constrained_columns"] = [column_name] - else: - table_pk["constrained_columns"].append(column_name) - - return ( - (key, primary_keys[key] if key in primary_keys else default()) - for key in ( - (schema, self.normalize_name(obj_name)) - for obj_name in all_objects - ) - ) - - @reflection.cache - def get_foreign_keys( - self, - connection, - table_name, - schema=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_foreign_keys( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @_handle_synonyms_decorator - def get_multi_foreign_keys( - self, - connection, - *, - scope, - schema, - filter_names, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - resolve_synonyms = kw.get("oracle_resolve_synonyms", False) - - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - - all_remote_owners = set() - fkeys = defaultdict(dict) - - for row_dict in self._get_all_constraint_rows( - connection, schema, dblink, all_objects, **kw - ): - if row_dict["constraint_type"] != "R": - continue - - table_name = self.normalize_name(row_dict["table_name"]) - constraint_name = self.normalize_name(row_dict["constraint_name"]) - table_fkey = fkeys[(schema, table_name)] - - assert constraint_name is not None - - local_column = self.normalize_name(row_dict["local_column"]) - remote_table = self.normalize_name(row_dict["remote_table"]) - remote_column = self.normalize_name(row_dict["remote_column"]) - remote_owner_orig = row_dict["remote_owner"] - remote_owner = self.normalize_name(remote_owner_orig) - if remote_owner_orig is not None: - all_remote_owners.add(remote_owner_orig) - - if remote_table is None: - # ticket 363 - if dblink and not dblink.startswith("@"): - dblink = f"@{dblink}" - util.warn( - "Got 'None' querying 'table_name' from " - f"all_cons_columns{dblink or ''} - does the user have " - "proper rights to the table?" - ) - continue - - if constraint_name not in table_fkey: - table_fkey[constraint_name] = fkey = { - "name": constraint_name, - "constrained_columns": [], - "referred_schema": None, - "referred_table": remote_table, - "referred_columns": [], - "options": {}, - } - - if resolve_synonyms: - # will be removed below - fkey["_ref_schema"] = remote_owner - - if schema is not None or remote_owner_orig != owner: - fkey["referred_schema"] = remote_owner - - delete_rule = row_dict["delete_rule"] - if delete_rule != "NO ACTION": - fkey["options"]["ondelete"] = delete_rule - - else: - fkey = table_fkey[constraint_name] - - fkey["constrained_columns"].append(local_column) - fkey["referred_columns"].append(remote_column) - - if resolve_synonyms and all_remote_owners: - query = select( - dictionary.all_synonyms.c.owner, - dictionary.all_synonyms.c.table_name, - dictionary.all_synonyms.c.table_owner, - dictionary.all_synonyms.c.synonym_name, - ).where(dictionary.all_synonyms.c.owner.in_(all_remote_owners)) - - result = self._execute_reflection( - connection, query, dblink, returns_long=False - ).mappings() - - remote_owners_lut = {} - for row in result: - synonym_owner = self.normalize_name(row["owner"]) - table_name = self.normalize_name(row["table_name"]) - - remote_owners_lut[(synonym_owner, table_name)] = ( - row["table_owner"], - row["synonym_name"], - ) - - empty = (None, None) - for table_fkeys in fkeys.values(): - for table_fkey in table_fkeys.values(): - key = ( - table_fkey.pop("_ref_schema"), - table_fkey["referred_table"], - ) - remote_owner, syn_name = remote_owners_lut.get(key, empty) - if syn_name: - sn = self.normalize_name(syn_name) - table_fkey["referred_table"] = sn - if schema is not None or remote_owner != owner: - ro = self.normalize_name(remote_owner) - table_fkey["referred_schema"] = ro - else: - table_fkey["referred_schema"] = None - default = ReflectionDefaults.foreign_keys - - return ( - (key, list(fkeys[key].values()) if key in fkeys else default()) - for key in ( - (schema, self.normalize_name(obj_name)) - for obj_name in all_objects - ) - ) - - @reflection.cache - def get_unique_constraints( - self, connection, table_name, schema=None, **kw - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_unique_constraints( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @_handle_synonyms_decorator - def get_multi_unique_constraints( - self, - connection, - *, - scope, - schema, - filter_names, - kind, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - unique_cons = defaultdict(dict) - - index_names = { - row_dict["index_name"] - for row_dict in self._get_indexes_rows( - connection, schema, dblink, all_objects, **kw - ) - } - - for row_dict in self._get_all_constraint_rows( - connection, schema, dblink, all_objects, **kw - ): - if row_dict["constraint_type"] != "U": - continue - table_name = self.normalize_name(row_dict["table_name"]) - constraint_name_orig = row_dict["constraint_name"] - constraint_name = self.normalize_name(constraint_name_orig) - column_name = self.normalize_name(row_dict["local_column"]) - table_uc = unique_cons[(schema, table_name)] - - assert constraint_name is not None - - if constraint_name not in table_uc: - table_uc[constraint_name] = uc = { - "name": constraint_name, - "column_names": [], - "duplicates_index": ( - constraint_name - if constraint_name_orig in index_names - else None - ), - } - else: - uc = table_uc[constraint_name] - - uc["column_names"].append(column_name) - - default = ReflectionDefaults.unique_constraints - - return ( - ( - key, - ( - list(unique_cons[key].values()) - if key in unique_cons - else default() - ), - ) - for key in ( - (schema, self.normalize_name(obj_name)) - for obj_name in all_objects - ) - ) - - @reflection.cache - def get_view_definition( - self, - connection, - view_name, - schema=None, - dblink=None, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - if kw.get("oracle_resolve_synonyms", False): - synonyms = self._get_synonyms( - connection, schema, filter_names=[view_name], dblink=dblink - ) - if synonyms: - assert len(synonyms) == 1 - row_dict = synonyms[0] - dblink = self.normalize_name(row_dict["db_link"]) - schema = row_dict["table_owner"] - view_name = row_dict["table_name"] - - name = self.denormalize_name(view_name) - owner = self.denormalize_schema_name( - schema or self.default_schema_name - ) - query = ( - select(dictionary.all_views.c.text) - .where( - dictionary.all_views.c.view_name == name, - dictionary.all_views.c.owner == owner, - ) - .union_all( - select(dictionary.all_mviews.c.query).where( - dictionary.all_mviews.c.mview_name == name, - dictionary.all_mviews.c.owner == owner, - ) - ) - ) - - rp = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalar() - if rp is None: - raise exc.NoSuchTableError( - f"{schema}.{view_name}" if schema else view_name - ) - else: - return rp - - @reflection.cache - def get_check_constraints( - self, connection, table_name, schema=None, include_all=False, **kw - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - data = self.get_multi_check_constraints( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - include_all=include_all, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @_handle_synonyms_decorator - def get_multi_check_constraints( - self, - connection, - *, - schema, - filter_names, - dblink=None, - scope, - kind, - include_all=False, - **kw, - ): - """Supported kw arguments are: ``dblink`` to reflect via a db link; - ``oracle_resolve_synonyms`` to resolve names to synonyms - """ - all_objects = self._get_all_objects( - connection, schema, scope, kind, filter_names, dblink, **kw - ) - - not_null = re.compile(r"..+?. IS NOT NULL$") - - check_constraints = defaultdict(list) - - for row_dict in self._get_all_constraint_rows( - connection, schema, dblink, all_objects, **kw - ): - if row_dict["constraint_type"] != "C": - continue - table_name = self.normalize_name(row_dict["table_name"]) - constraint_name = self.normalize_name(row_dict["constraint_name"]) - search_condition = row_dict["search_condition"] - - table_checks = check_constraints[(schema, table_name)] - if constraint_name is not None and ( - include_all or not not_null.match(search_condition) - ): - table_checks.append( - {"name": constraint_name, "sqltext": search_condition} - ) - - default = ReflectionDefaults.check_constraints - - return ( - ( - key, - ( - check_constraints[key] - if key in check_constraints - else default() - ), - ) - for key in ( - (schema, self.normalize_name(obj_name)) - for obj_name in all_objects - ) - ) - - def _list_dblinks(self, connection, dblink=None): - query = select(dictionary.all_db_links.c.db_link) - links = self._execute_reflection( - connection, query, dblink, returns_long=False - ).scalars() - return [self.normalize_name(link) for link in links] - - -class _OuterJoinColumn(sql.ClauseElement): - __visit_name__ = "outer_join_column" - - def __init__(self, column): - self.column = column diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py deleted file mode 100644 index 9346224..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py +++ /dev/null @@ -1,1492 +0,0 @@ -# dialects/oracle/cx_oracle.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" -.. dialect:: oracle+cx_oracle - :name: cx-Oracle - :dbapi: cx_oracle - :connectstring: oracle+cx_oracle://user:pass@hostname:port[/dbname][?service_name=[&key=value&key=value...]] - :url: https://oracle.github.io/python-cx_Oracle/ - -DSN vs. Hostname connections ------------------------------ - -cx_Oracle provides several methods of indicating the target database. The -dialect translates from a series of different URL forms. - -Hostname Connections with Easy Connect Syntax -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Given a hostname, port and service name of the target Oracle Database, for -example from Oracle's `Easy Connect syntax -`_, -then connect in SQLAlchemy using the ``service_name`` query string parameter:: - - engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:port/?service_name=myservice&encoding=UTF-8&nencoding=UTF-8") - -The `full Easy Connect syntax -`_ -is not supported. Instead, use a ``tnsnames.ora`` file and connect using a -DSN. - -Connections with tnsnames.ora or Oracle Cloud -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Alternatively, if no port, database name, or ``service_name`` is provided, the -dialect will use an Oracle DSN "connection string". This takes the "hostname" -portion of the URL as the data source name. For example, if the -``tnsnames.ora`` file contains a `Net Service Name -`_ -of ``myalias`` as below:: - - myalias = - (DESCRIPTION = - (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521)) - (CONNECT_DATA = - (SERVER = DEDICATED) - (SERVICE_NAME = orclpdb1) - ) - ) - -The cx_Oracle dialect connects to this database service when ``myalias`` is the -hostname portion of the URL, without specifying a port, database name or -``service_name``:: - - engine = create_engine("oracle+cx_oracle://scott:tiger@myalias/?encoding=UTF-8&nencoding=UTF-8") - -Users of Oracle Cloud should use this syntax and also configure the cloud -wallet as shown in cx_Oracle documentation `Connecting to Autononmous Databases -`_. - -SID Connections -^^^^^^^^^^^^^^^ - -To use Oracle's obsolete SID connection syntax, the SID can be passed in a -"database name" portion of the URL as below:: - - engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:1521/dbname?encoding=UTF-8&nencoding=UTF-8") - -Above, the DSN passed to cx_Oracle is created by ``cx_Oracle.makedsn()`` as -follows:: - - >>> import cx_Oracle - >>> cx_Oracle.makedsn("hostname", 1521, sid="dbname") - '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))' - -Passing cx_Oracle connect arguments ------------------------------------ - -Additional connection arguments can usually be passed via the URL -query string; particular symbols like ``cx_Oracle.SYSDBA`` are intercepted -and converted to the correct symbol:: - - e = create_engine( - "oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true") - -.. versionchanged:: 1.3 the cx_oracle dialect now accepts all argument names - within the URL string itself, to be passed to the cx_Oracle DBAPI. As - was the case earlier but not correctly documented, the - :paramref:`_sa.create_engine.connect_args` parameter also accepts all - cx_Oracle DBAPI connect arguments. - -To pass arguments directly to ``.connect()`` without using the query -string, use the :paramref:`_sa.create_engine.connect_args` dictionary. -Any cx_Oracle parameter value and/or constant may be passed, such as:: - - import cx_Oracle - e = create_engine( - "oracle+cx_oracle://user:pass@dsn", - connect_args={ - "encoding": "UTF-8", - "nencoding": "UTF-8", - "mode": cx_Oracle.SYSDBA, - "events": True - } - ) - -Note that the default value for ``encoding`` and ``nencoding`` was changed to -"UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when using that -version, or later. - -Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver --------------------------------------------------------------------------- - -There are also options that are consumed by the SQLAlchemy cx_oracle dialect -itself. These options are always passed directly to :func:`_sa.create_engine` -, such as:: - - e = create_engine( - "oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False) - -The parameters accepted by the cx_oracle dialect are as follows: - -* ``arraysize`` - set the cx_oracle.arraysize value on cursors; defaults - to ``None``, indicating that the driver default should be used (typically - the value is 100). This setting controls how many rows are buffered when - fetching rows, and can have a significant effect on performance when - modified. The setting is used for both ``cx_Oracle`` as well as - ``oracledb``. - - .. versionchanged:: 2.0.26 - changed the default value from 50 to None, - to use the default value of the driver itself. - -* ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`. - -* ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail. - -* ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail. - -.. _cx_oracle_sessionpool: - -Using cx_Oracle SessionPool ---------------------------- - -The cx_Oracle library provides its own connection pool implementation that may -be used in place of SQLAlchemy's pooling functionality. This can be achieved -by using the :paramref:`_sa.create_engine.creator` parameter to provide a -function that returns a new connection, along with setting -:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable -SQLAlchemy's pooling:: - - import cx_Oracle - from sqlalchemy import create_engine - from sqlalchemy.pool import NullPool - - pool = cx_Oracle.SessionPool( - user="scott", password="tiger", dsn="orclpdb", - min=2, max=5, increment=1, threaded=True, - encoding="UTF-8", nencoding="UTF-8" - ) - - engine = create_engine("oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool) - -The above engine may then be used normally where cx_Oracle's pool handles -connection pooling:: - - with engine.connect() as conn: - print(conn.scalar("select 1 FROM dual")) - - -As well as providing a scalable solution for multi-user applications, the -cx_Oracle session pool supports some Oracle features such as DRCP and -`Application Continuity -`_. - -Using Oracle Database Resident Connection Pooling (DRCP) --------------------------------------------------------- - -When using Oracle's `DRCP -`_, -the best practice is to pass a connection class and "purity" when acquiring a -connection from the SessionPool. Refer to the `cx_Oracle DRCP documentation -`_. - -This can be achieved by wrapping ``pool.acquire()``:: - - import cx_Oracle - from sqlalchemy import create_engine - from sqlalchemy.pool import NullPool - - pool = cx_Oracle.SessionPool( - user="scott", password="tiger", dsn="orclpdb", - min=2, max=5, increment=1, threaded=True, - encoding="UTF-8", nencoding="UTF-8" - ) - - def creator(): - return pool.acquire(cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF) - - engine = create_engine("oracle+cx_oracle://", creator=creator, poolclass=NullPool) - -The above engine may then be used normally where cx_Oracle handles session -pooling and Oracle Database additionally uses DRCP:: - - with engine.connect() as conn: - print(conn.scalar("select 1 FROM dual")) - -.. _cx_oracle_unicode: - -Unicode -------- - -As is the case for all DBAPIs under Python 3, all strings are inherently -Unicode strings. In all cases however, the driver requires an explicit -encoding configuration. - -Ensuring the Correct Client Encoding -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The long accepted standard for establishing client encoding for nearly all -Oracle related software is via the `NLS_LANG `_ -environment variable. cx_Oracle like most other Oracle drivers will use -this environment variable as the source of its encoding configuration. The -format of this variable is idiosyncratic; a typical value would be -``AMERICAN_AMERICA.AL32UTF8``. - -The cx_Oracle driver also supports a programmatic alternative which is to -pass the ``encoding`` and ``nencoding`` parameters directly to its -``.connect()`` function. These can be present in the URL as follows:: - - engine = create_engine("oracle+cx_oracle://scott:tiger@orclpdb/?encoding=UTF-8&nencoding=UTF-8") - -For the meaning of the ``encoding`` and ``nencoding`` parameters, please -consult -`Characters Sets and National Language Support (NLS) `_. - -.. seealso:: - - `Characters Sets and National Language Support (NLS) `_ - - in the cx_Oracle documentation. - - -Unicode-specific Column datatypes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The Core expression language handles unicode data by use of the :class:`.Unicode` -and :class:`.UnicodeText` -datatypes. These types correspond to the VARCHAR2 and CLOB Oracle datatypes by -default. When using these datatypes with Unicode data, it is expected that -the Oracle database is configured with a Unicode-aware character set, as well -as that the ``NLS_LANG`` environment variable is set appropriately, so that -the VARCHAR2 and CLOB datatypes can accommodate the data. - -In the case that the Oracle database is not configured with a Unicode character -set, the two options are to use the :class:`_types.NCHAR` and -:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag -``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, -which will cause the -SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` / -:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB. - -.. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText` - datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle datatypes - unless the ``use_nchar_for_unicode=True`` is passed to the dialect - when :func:`_sa.create_engine` is called. - - -.. _cx_oracle_unicode_encoding_errors: - -Encoding Errors -^^^^^^^^^^^^^^^ - -For the unusual case that data in the Oracle database is present with a broken -encoding, the dialect accepts a parameter ``encoding_errors`` which will be -passed to Unicode decoding functions in order to affect how decoding errors are -handled. The value is ultimately consumed by the Python `decode -`_ function, and -is passed both via cx_Oracle's ``encodingErrors`` parameter consumed by -``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the -cx_Oracle dialect makes use of both under different circumstances. - -.. versionadded:: 1.3.11 - - -.. _cx_oracle_setinputsizes: - -Fine grained control over cx_Oracle data binding performance with setinputsizes -------------------------------------------------------------------------------- - -The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the -DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the -datatypes that are bound to a SQL statement for Python values being passed as -parameters. While virtually no other DBAPI assigns any use to the -``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its -interactions with the Oracle client interface, and in some scenarios it is not -possible for SQLAlchemy to know exactly how data should be bound, as some -settings can cause profoundly different performance characteristics, while -altering the type coercion behavior at the same time. - -Users of the cx_Oracle dialect are **strongly encouraged** to read through -cx_Oracle's list of built-in datatype symbols at -https://cx-oracle.readthedocs.io/en/latest/api_manual/module.html#database-types. -Note that in some cases, significant performance degradation can occur when -using these types vs. not, in particular when specifying ``cx_Oracle.CLOB``. - -On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can -be used both for runtime visibility (e.g. logging) of the setinputsizes step as -well as to fully control how ``setinputsizes()`` is used on a per-statement -basis. - -.. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes` - - -Example 1 - logging all setinputsizes calls -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following example illustrates how to log the intermediary values from a -SQLAlchemy perspective before they are converted to the raw ``setinputsizes()`` -parameter dictionary. The keys of the dictionary are :class:`.BindParameter` -objects which have a ``.key`` and a ``.type`` attribute:: - - from sqlalchemy import create_engine, event - - engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe") - - @event.listens_for(engine, "do_setinputsizes") - def _log_setinputsizes(inputsizes, cursor, statement, parameters, context): - for bindparam, dbapitype in inputsizes.items(): - log.info( - "Bound parameter name: %s SQLAlchemy type: %r " - "DBAPI object: %s", - bindparam.key, bindparam.type, dbapitype) - -Example 2 - remove all bindings to CLOB -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``CLOB`` datatype in cx_Oracle incurs a significant performance overhead, -however is set by default for the ``Text`` type within the SQLAlchemy 1.2 -series. This setting can be modified as follows:: - - from sqlalchemy import create_engine, event - from cx_Oracle import CLOB - - engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe") - - @event.listens_for(engine, "do_setinputsizes") - def _remove_clob(inputsizes, cursor, statement, parameters, context): - for bindparam, dbapitype in list(inputsizes.items()): - if dbapitype is CLOB: - del inputsizes[bindparam] - -.. _cx_oracle_returning: - -RETURNING Support ------------------ - -The cx_Oracle dialect implements RETURNING using OUT parameters. -The dialect supports RETURNING fully. - -.. _cx_oracle_lob: - -LOB Datatypes --------------- - -LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and -BLOB. Modern versions of cx_Oracle and oracledb are optimized for these -datatypes to be delivered as a single buffer. As such, SQLAlchemy makes use of -these newer type handlers by default. - -To disable the use of newer type handlers and deliver LOB objects as classic -buffered objects with a ``read()`` method, the parameter -``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`, -which takes place only engine-wide. - -Two Phase Transactions Not Supported -------------------------------------- - -Two phase transactions are **not supported** under cx_Oracle due to poor -driver support. As of cx_Oracle 6.0b1, the interface for -two phase transactions has been changed to be more of a direct pass-through -to the underlying OCI layer with less automation. The additional logic -to support this system is not implemented in SQLAlchemy. - -.. _cx_oracle_numeric: - -Precision Numerics ------------------- - -SQLAlchemy's numeric types can handle receiving and returning values as Python -``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a -subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in -use, the :paramref:`.Numeric.asdecimal` flag determines if values should be -coerced to ``Decimal`` upon return, or returned as float objects. To make -matters more complicated under Oracle, Oracle's ``NUMBER`` type can also -represent integer values if the "scale" is zero, so the Oracle-specific -:class:`_oracle.NUMBER` type takes this into account as well. - -The cx_Oracle dialect makes extensive use of connection- and cursor-level -"outputtypehandler" callables in order to coerce numeric values as requested. -These callables are specific to the specific flavor of :class:`.Numeric` in -use, as well as if no SQLAlchemy typing objects are present. There are -observed scenarios where Oracle may sends incomplete or ambiguous information -about the numeric types being returned, such as a query where the numeric types -are buried under multiple levels of subquery. The type handlers do their best -to make the right decision in all cases, deferring to the underlying cx_Oracle -DBAPI for all those cases where the driver can make the best decision. - -When no typing objects are present, as when executing plain SQL strings, a -default "outputtypehandler" is present which will generally return numeric -values which specify precision and scale as Python ``Decimal`` objects. To -disable this coercion to decimal for performance reasons, pass the flag -``coerce_to_decimal=False`` to :func:`_sa.create_engine`:: - - engine = create_engine("oracle+cx_oracle://dsn", coerce_to_decimal=False) - -The ``coerce_to_decimal`` flag only impacts the results of plain string -SQL statements that are not otherwise associated with a :class:`.Numeric` -SQLAlchemy type (or a subclass of such). - -.. versionchanged:: 1.2 The numeric handling system for cx_Oracle has been - reworked to take advantage of newer cx_Oracle features as well - as better integration of outputtypehandlers. - -""" # noqa -from __future__ import annotations - -import decimal -import random -import re - -from . import base as oracle -from .base import OracleCompiler -from .base import OracleDialect -from .base import OracleExecutionContext -from .types import _OracleDateLiteralRender -from ... import exc -from ... import util -from ...engine import cursor as _cursor -from ...engine import interfaces -from ...engine import processors -from ...sql import sqltypes -from ...sql._typing import is_sql_compiler - -# source: -# https://github.com/oracle/python-cx_Oracle/issues/596#issuecomment-999243649 -_CX_ORACLE_MAGIC_LOB_SIZE = 131072 - - -class _OracleInteger(sqltypes.Integer): - def get_dbapi_type(self, dbapi): - # see https://github.com/oracle/python-cx_Oracle/issues/ - # 208#issuecomment-409715955 - return int - - def _cx_oracle_var(self, dialect, cursor, arraysize=None): - cx_Oracle = dialect.dbapi - return cursor.var( - cx_Oracle.STRING, - 255, - arraysize=arraysize if arraysize is not None else cursor.arraysize, - outconverter=int, - ) - - def _cx_oracle_outputtypehandler(self, dialect): - def handler(cursor, name, default_type, size, precision, scale): - return self._cx_oracle_var(dialect, cursor) - - return handler - - -class _OracleNumeric(sqltypes.Numeric): - is_number = False - - def bind_processor(self, dialect): - if self.scale == 0: - return None - elif self.asdecimal: - processor = processors.to_decimal_processor_factory( - decimal.Decimal, self._effective_decimal_return_scale - ) - - def process(value): - if isinstance(value, (int, float)): - return processor(value) - elif value is not None and value.is_infinite(): - return float(value) - else: - return value - - return process - else: - return processors.to_float - - def result_processor(self, dialect, coltype): - return None - - def _cx_oracle_outputtypehandler(self, dialect): - cx_Oracle = dialect.dbapi - - def handler(cursor, name, default_type, size, precision, scale): - outconverter = None - - if precision: - if self.asdecimal: - if default_type == cx_Oracle.NATIVE_FLOAT: - # receiving float and doing Decimal after the fact - # allows for float("inf") to be handled - type_ = default_type - outconverter = decimal.Decimal - else: - type_ = decimal.Decimal - else: - if self.is_number and scale == 0: - # integer. cx_Oracle is observed to handle the widest - # variety of ints when no directives are passed, - # from 5.2 to 7.0. See [ticket:4457] - return None - else: - type_ = cx_Oracle.NATIVE_FLOAT - - else: - if self.asdecimal: - if default_type == cx_Oracle.NATIVE_FLOAT: - type_ = default_type - outconverter = decimal.Decimal - else: - type_ = decimal.Decimal - else: - if self.is_number and scale == 0: - # integer. cx_Oracle is observed to handle the widest - # variety of ints when no directives are passed, - # from 5.2 to 7.0. See [ticket:4457] - return None - else: - type_ = cx_Oracle.NATIVE_FLOAT - - return cursor.var( - type_, - 255, - arraysize=cursor.arraysize, - outconverter=outconverter, - ) - - return handler - - -class _OracleUUID(sqltypes.Uuid): - def get_dbapi_type(self, dbapi): - return dbapi.STRING - - -class _OracleBinaryFloat(_OracleNumeric): - def get_dbapi_type(self, dbapi): - return dbapi.NATIVE_FLOAT - - -class _OracleBINARY_FLOAT(_OracleBinaryFloat, oracle.BINARY_FLOAT): - pass - - -class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE): - pass - - -class _OracleNUMBER(_OracleNumeric): - is_number = True - - -class _CXOracleDate(oracle._OracleDate): - def bind_processor(self, dialect): - return None - - def result_processor(self, dialect, coltype): - def process(value): - if value is not None: - return value.date() - else: - return value - - return process - - -class _CXOracleTIMESTAMP(_OracleDateLiteralRender, sqltypes.TIMESTAMP): - def literal_processor(self, dialect): - return self._literal_processor_datetime(dialect) - - -class _LOBDataType: - pass - - -# TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR -# here are inconsistent and not very good -class _OracleChar(sqltypes.CHAR): - def get_dbapi_type(self, dbapi): - return dbapi.FIXED_CHAR - - -class _OracleNChar(sqltypes.NCHAR): - def get_dbapi_type(self, dbapi): - return dbapi.FIXED_NCHAR - - -class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2): - def get_dbapi_type(self, dbapi): - return dbapi.NCHAR - - -class _OracleUnicodeStringCHAR(sqltypes.Unicode): - def get_dbapi_type(self, dbapi): - return dbapi.LONG_STRING - - -class _OracleUnicodeTextNCLOB(_LOBDataType, oracle.NCLOB): - def get_dbapi_type(self, dbapi): - # previously, this was dbapi.NCLOB. - # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() - # when this datatype is used. - return dbapi.DB_TYPE_NVARCHAR - - -class _OracleUnicodeTextCLOB(_LOBDataType, sqltypes.UnicodeText): - def get_dbapi_type(self, dbapi): - # previously, this was dbapi.CLOB. - # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() - # when this datatype is used. - return dbapi.DB_TYPE_NVARCHAR - - -class _OracleText(_LOBDataType, sqltypes.Text): - def get_dbapi_type(self, dbapi): - # previously, this was dbapi.CLOB. - # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() - # when this datatype is used. - return dbapi.DB_TYPE_NVARCHAR - - -class _OracleLong(_LOBDataType, oracle.LONG): - def get_dbapi_type(self, dbapi): - return dbapi.LONG_STRING - - -class _OracleString(sqltypes.String): - pass - - -class _OracleEnum(sqltypes.Enum): - def bind_processor(self, dialect): - enum_proc = sqltypes.Enum.bind_processor(self, dialect) - - def process(value): - raw_str = enum_proc(value) - return raw_str - - return process - - -class _OracleBinary(_LOBDataType, sqltypes.LargeBinary): - def get_dbapi_type(self, dbapi): - # previously, this was dbapi.BLOB. - # DB_TYPE_RAW will instead be passed to setinputsizes() - # when this datatype is used. - return dbapi.DB_TYPE_RAW - - def bind_processor(self, dialect): - return None - - def result_processor(self, dialect, coltype): - if not dialect.auto_convert_lobs: - return None - else: - return super().result_processor(dialect, coltype) - - -class _OracleInterval(oracle.INTERVAL): - def get_dbapi_type(self, dbapi): - return dbapi.INTERVAL - - -class _OracleRaw(oracle.RAW): - pass - - -class _OracleRowid(oracle.ROWID): - def get_dbapi_type(self, dbapi): - return dbapi.ROWID - - -class OracleCompiler_cx_oracle(OracleCompiler): - _oracle_cx_sql_compiler = True - - _oracle_returning = False - - # Oracle bind names can't start with digits or underscores. - # currently we rely upon Oracle-specific quoting of bind names in most - # cases. however for expanding params, the escape chars are used. - # see #8708 - bindname_escape_characters = util.immutabledict( - { - "%": "P", - "(": "A", - ")": "Z", - ":": "C", - ".": "C", - "[": "C", - "]": "C", - " ": "C", - "\\": "C", - "/": "C", - "?": "C", - } - ) - - def bindparam_string(self, name, **kw): - quote = getattr(name, "quote", None) - if ( - quote is True - or quote is not False - and self.preparer._bindparam_requires_quotes(name) - # bind param quoting for Oracle doesn't work with post_compile - # params. For those, the default bindparam_string will escape - # special chars, and the appending of a number "_1" etc. will - # take care of reserved words - and not kw.get("post_compile", False) - ): - # interesting to note about expanding parameters - since the - # new parameters take the form _, at least if - # they are originally formed from reserved words, they no longer - # need quoting :). names that include illegal characters - # won't work however. - quoted_name = '"%s"' % name - kw["escaped_from"] = name - name = quoted_name - return OracleCompiler.bindparam_string(self, name, **kw) - - # TODO: we could likely do away with quoting altogether for - # Oracle parameters and use the custom escaping here - escaped_from = kw.get("escaped_from", None) - if not escaped_from: - if self._bind_translate_re.search(name): - # not quite the translate use case as we want to - # also get a quick boolean if we even found - # unusual characters in the name - new_name = self._bind_translate_re.sub( - lambda m: self._bind_translate_chars[m.group(0)], - name, - ) - if new_name[0].isdigit() or new_name[0] == "_": - new_name = "D" + new_name - kw["escaped_from"] = name - name = new_name - elif name[0].isdigit() or name[0] == "_": - new_name = "D" + name - kw["escaped_from"] = name - name = new_name - - return OracleCompiler.bindparam_string(self, name, **kw) - - -class OracleExecutionContext_cx_oracle(OracleExecutionContext): - out_parameters = None - - def _generate_out_parameter_vars(self): - # check for has_out_parameters or RETURNING, create cx_Oracle.var - # objects if so - if self.compiled.has_out_parameters or self.compiled._oracle_returning: - out_parameters = self.out_parameters - assert out_parameters is not None - - len_params = len(self.parameters) - - quoted_bind_names = self.compiled.escaped_bind_names - for bindparam in self.compiled.binds.values(): - if bindparam.isoutparam: - name = self.compiled.bind_names[bindparam] - type_impl = bindparam.type.dialect_impl(self.dialect) - - if hasattr(type_impl, "_cx_oracle_var"): - out_parameters[name] = type_impl._cx_oracle_var( - self.dialect, self.cursor, arraysize=len_params - ) - else: - dbtype = type_impl.get_dbapi_type(self.dialect.dbapi) - - cx_Oracle = self.dialect.dbapi - - assert cx_Oracle is not None - - if dbtype is None: - raise exc.InvalidRequestError( - "Cannot create out parameter for " - "parameter " - "%r - its type %r is not supported by" - " cx_oracle" % (bindparam.key, bindparam.type) - ) - - # note this is an OUT parameter. Using - # non-LOB datavalues with large unicode-holding - # values causes the failure (both cx_Oracle and - # oracledb): - # ORA-22835: Buffer too small for CLOB to CHAR or - # BLOB to RAW conversion (actual: 16507, - # maximum: 4000) - # [SQL: INSERT INTO long_text (x, y, z) VALUES - # (:x, :y, :z) RETURNING long_text.x, long_text.y, - # long_text.z INTO :ret_0, :ret_1, :ret_2] - # so even for DB_TYPE_NVARCHAR we convert to a LOB - - if isinstance(type_impl, _LOBDataType): - if dbtype == cx_Oracle.DB_TYPE_NVARCHAR: - dbtype = cx_Oracle.NCLOB - elif dbtype == cx_Oracle.DB_TYPE_RAW: - dbtype = cx_Oracle.BLOB - # other LOB types go in directly - - out_parameters[name] = self.cursor.var( - dbtype, - # this is fine also in oracledb_async since - # the driver will await the read coroutine - outconverter=lambda value: value.read(), - arraysize=len_params, - ) - elif ( - isinstance(type_impl, _OracleNumeric) - and type_impl.asdecimal - ): - out_parameters[name] = self.cursor.var( - decimal.Decimal, - arraysize=len_params, - ) - - else: - out_parameters[name] = self.cursor.var( - dbtype, arraysize=len_params - ) - - for param in self.parameters: - param[quoted_bind_names.get(name, name)] = ( - out_parameters[name] - ) - - def _generate_cursor_outputtype_handler(self): - output_handlers = {} - - for keyname, name, objects, type_ in self.compiled._result_columns: - handler = type_._cached_custom_processor( - self.dialect, - "cx_oracle_outputtypehandler", - self._get_cx_oracle_type_handler, - ) - - if handler: - denormalized_name = self.dialect.denormalize_name(keyname) - output_handlers[denormalized_name] = handler - - if output_handlers: - default_handler = self._dbapi_connection.outputtypehandler - - def output_type_handler( - cursor, name, default_type, size, precision, scale - ): - if name in output_handlers: - return output_handlers[name]( - cursor, name, default_type, size, precision, scale - ) - else: - return default_handler( - cursor, name, default_type, size, precision, scale - ) - - self.cursor.outputtypehandler = output_type_handler - - def _get_cx_oracle_type_handler(self, impl): - if hasattr(impl, "_cx_oracle_outputtypehandler"): - return impl._cx_oracle_outputtypehandler(self.dialect) - else: - return None - - def pre_exec(self): - super().pre_exec() - if not getattr(self.compiled, "_oracle_cx_sql_compiler", False): - return - - self.out_parameters = {} - - self._generate_out_parameter_vars() - - self._generate_cursor_outputtype_handler() - - def post_exec(self): - if ( - self.compiled - and is_sql_compiler(self.compiled) - and self.compiled._oracle_returning - ): - initial_buffer = self.fetchall_for_returning( - self.cursor, _internal=True - ) - - fetch_strategy = _cursor.FullyBufferedCursorFetchStrategy( - self.cursor, - [ - (entry.keyname, None) - for entry in self.compiled._result_columns - ], - initial_buffer=initial_buffer, - ) - - self.cursor_fetch_strategy = fetch_strategy - - def create_cursor(self): - c = self._dbapi_connection.cursor() - if self.dialect.arraysize: - c.arraysize = self.dialect.arraysize - - return c - - def fetchall_for_returning(self, cursor, *, _internal=False): - compiled = self.compiled - if ( - not _internal - and compiled is None - or not is_sql_compiler(compiled) - or not compiled._oracle_returning - ): - raise NotImplementedError( - "execution context was not prepared for Oracle RETURNING" - ) - - # create a fake cursor result from the out parameters. unlike - # get_out_parameter_values(), the result-row handlers here will be - # applied at the Result level - - numcols = len(self.out_parameters) - - # [stmt_result for stmt_result in outparam.values] == each - # statement in executemany - # [val for val in stmt_result] == each row for a particular - # statement - return list( - zip( - *[ - [ - val - for stmt_result in self.out_parameters[ - f"ret_{j}" - ].values - for val in (stmt_result or ()) - ] - for j in range(numcols) - ] - ) - ) - - def get_out_parameter_values(self, out_param_names): - # this method should not be called when the compiler has - # RETURNING as we've turned the has_out_parameters flag set to - # False. - assert not self.compiled.returning - - return [ - self.dialect._paramval(self.out_parameters[name]) - for name in out_param_names - ] - - -class OracleDialect_cx_oracle(OracleDialect): - supports_statement_cache = True - execution_ctx_cls = OracleExecutionContext_cx_oracle - statement_compiler = OracleCompiler_cx_oracle - - supports_sane_rowcount = True - supports_sane_multi_rowcount = True - - insert_executemany_returning = True - insert_executemany_returning_sort_by_parameter_order = True - update_executemany_returning = True - delete_executemany_returning = True - - bind_typing = interfaces.BindTyping.SETINPUTSIZES - - driver = "cx_oracle" - - colspecs = util.update_copy( - OracleDialect.colspecs, - { - sqltypes.TIMESTAMP: _CXOracleTIMESTAMP, - sqltypes.Numeric: _OracleNumeric, - sqltypes.Float: _OracleNumeric, - oracle.BINARY_FLOAT: _OracleBINARY_FLOAT, - oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE, - sqltypes.Integer: _OracleInteger, - oracle.NUMBER: _OracleNUMBER, - sqltypes.Date: _CXOracleDate, - sqltypes.LargeBinary: _OracleBinary, - sqltypes.Boolean: oracle._OracleBoolean, - sqltypes.Interval: _OracleInterval, - oracle.INTERVAL: _OracleInterval, - sqltypes.Text: _OracleText, - sqltypes.String: _OracleString, - sqltypes.UnicodeText: _OracleUnicodeTextCLOB, - sqltypes.CHAR: _OracleChar, - sqltypes.NCHAR: _OracleNChar, - sqltypes.Enum: _OracleEnum, - oracle.LONG: _OracleLong, - oracle.RAW: _OracleRaw, - sqltypes.Unicode: _OracleUnicodeStringCHAR, - sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR, - sqltypes.Uuid: _OracleUUID, - oracle.NCLOB: _OracleUnicodeTextNCLOB, - oracle.ROWID: _OracleRowid, - }, - ) - - execute_sequence_format = list - - _cx_oracle_threaded = None - - _cursor_var_unicode_kwargs = util.immutabledict() - - @util.deprecated_params( - threaded=( - "1.3", - "The 'threaded' parameter to the cx_oracle/oracledb dialect " - "is deprecated as a dialect-level argument, and will be removed " - "in a future release. As of version 1.3, it defaults to False " - "rather than True. The 'threaded' option can be passed to " - "cx_Oracle directly in the URL query string passed to " - ":func:`_sa.create_engine`.", - ) - ) - def __init__( - self, - auto_convert_lobs=True, - coerce_to_decimal=True, - arraysize=None, - encoding_errors=None, - threaded=None, - **kwargs, - ): - OracleDialect.__init__(self, **kwargs) - self.arraysize = arraysize - self.encoding_errors = encoding_errors - if encoding_errors: - self._cursor_var_unicode_kwargs = { - "encodingErrors": encoding_errors - } - if threaded is not None: - self._cx_oracle_threaded = threaded - self.auto_convert_lobs = auto_convert_lobs - self.coerce_to_decimal = coerce_to_decimal - if self._use_nchar_for_unicode: - self.colspecs = self.colspecs.copy() - self.colspecs[sqltypes.Unicode] = _OracleUnicodeStringNCHAR - self.colspecs[sqltypes.UnicodeText] = _OracleUnicodeTextNCLOB - - dbapi_module = self.dbapi - self._load_version(dbapi_module) - - if dbapi_module is not None: - # these constants will first be seen in SQLAlchemy datatypes - # coming from the get_dbapi_type() method. We then - # will place the following types into setinputsizes() calls - # on each statement. Oracle constants that are not in this - # list will not be put into setinputsizes(). - self.include_set_input_sizes = { - dbapi_module.DATETIME, - dbapi_module.DB_TYPE_NVARCHAR, # used for CLOB, NCLOB - dbapi_module.DB_TYPE_RAW, # used for BLOB - dbapi_module.NCLOB, # not currently used except for OUT param - dbapi_module.CLOB, # not currently used except for OUT param - dbapi_module.LOB, # not currently used - dbapi_module.BLOB, # not currently used except for OUT param - dbapi_module.NCHAR, - dbapi_module.FIXED_NCHAR, - dbapi_module.FIXED_CHAR, - dbapi_module.TIMESTAMP, - int, # _OracleInteger, - # _OracleBINARY_FLOAT, _OracleBINARY_DOUBLE, - dbapi_module.NATIVE_FLOAT, - } - - self._paramval = lambda value: value.getvalue() - - def _load_version(self, dbapi_module): - version = (0, 0, 0) - if dbapi_module is not None: - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version) - if m: - version = tuple( - int(x) for x in m.group(1, 2, 3) if x is not None - ) - self.cx_oracle_ver = version - if self.cx_oracle_ver < (8,) and self.cx_oracle_ver > (0, 0, 0): - raise exc.InvalidRequestError( - "cx_Oracle version 8 and above are supported" - ) - - @classmethod - def import_dbapi(cls): - import cx_Oracle - - return cx_Oracle - - def initialize(self, connection): - super().initialize(connection) - self._detect_decimal_char(connection) - - def get_isolation_level(self, dbapi_connection): - # sources: - - # general idea of transaction id, have to start one, etc. - # https://stackoverflow.com/questions/10711204/how-to-check-isoloation-level - - # how to decode xid cols from v$transaction to match - # https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532779900346079444 - - # Oracle tuple comparison without using IN: - # https://www.sql-workbench.eu/comparison/tuple_comparison.html - - with dbapi_connection.cursor() as cursor: - # this is the only way to ensure a transaction is started without - # actually running DML. There's no way to see the configured - # isolation level without getting it from v$transaction which - # means transaction has to be started. - outval = cursor.var(str) - cursor.execute( - """ - begin - :trans_id := dbms_transaction.local_transaction_id( TRUE ); - end; - """, - {"trans_id": outval}, - ) - trans_id = outval.getvalue() - xidusn, xidslot, xidsqn = trans_id.split(".", 2) - - cursor.execute( - "SELECT CASE BITAND(t.flag, POWER(2, 28)) " - "WHEN 0 THEN 'READ COMMITTED' " - "ELSE 'SERIALIZABLE' END AS isolation_level " - "FROM v$transaction t WHERE " - "(t.xidusn, t.xidslot, t.xidsqn) = " - "((:xidusn, :xidslot, :xidsqn))", - {"xidusn": xidusn, "xidslot": xidslot, "xidsqn": xidsqn}, - ) - row = cursor.fetchone() - if row is None: - raise exc.InvalidRequestError( - "could not retrieve isolation level" - ) - result = row[0] - - return result - - def get_isolation_level_values(self, dbapi_connection): - return super().get_isolation_level_values(dbapi_connection) + [ - "AUTOCOMMIT" - ] - - def set_isolation_level(self, dbapi_connection, level): - if level == "AUTOCOMMIT": - dbapi_connection.autocommit = True - else: - dbapi_connection.autocommit = False - dbapi_connection.rollback() - with dbapi_connection.cursor() as cursor: - cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}") - - def _detect_decimal_char(self, connection): - # we have the option to change this setting upon connect, - # or just look at what it is upon connect and convert. - # to minimize the chance of interference with changes to - # NLS_TERRITORY or formatting behavior of the DB, we opt - # to just look at it - - dbapi_connection = connection.connection - - with dbapi_connection.cursor() as cursor: - # issue #8744 - # nls_session_parameters is not available in some Oracle - # modes like "mount mode". But then, v$nls_parameters is not - # available if the connection doesn't have SYSDBA priv. - # - # simplify the whole thing and just use the method that we were - # doing in the test suite already, selecting a number - - def output_type_handler( - cursor, name, defaultType, size, precision, scale - ): - return cursor.var( - self.dbapi.STRING, 255, arraysize=cursor.arraysize - ) - - cursor.outputtypehandler = output_type_handler - cursor.execute("SELECT 1.1 FROM DUAL") - value = cursor.fetchone()[0] - - decimal_char = value.lstrip("0")[1] - assert not decimal_char[0].isdigit() - - self._decimal_char = decimal_char - - if self._decimal_char != ".": - _detect_decimal = self._detect_decimal - _to_decimal = self._to_decimal - - self._detect_decimal = lambda value: _detect_decimal( - value.replace(self._decimal_char, ".") - ) - self._to_decimal = lambda value: _to_decimal( - value.replace(self._decimal_char, ".") - ) - - def _detect_decimal(self, value): - if "." in value: - return self._to_decimal(value) - else: - return int(value) - - _to_decimal = decimal.Decimal - - def _generate_connection_outputtype_handler(self): - """establish the default outputtypehandler established at the - connection level. - - """ - - dialect = self - cx_Oracle = dialect.dbapi - - number_handler = _OracleNUMBER( - asdecimal=True - )._cx_oracle_outputtypehandler(dialect) - float_handler = _OracleNUMBER( - asdecimal=False - )._cx_oracle_outputtypehandler(dialect) - - def output_type_handler( - cursor, name, default_type, size, precision, scale - ): - if ( - default_type == cx_Oracle.NUMBER - and default_type is not cx_Oracle.NATIVE_FLOAT - ): - if not dialect.coerce_to_decimal: - return None - elif precision == 0 and scale in (0, -127): - # ambiguous type, this occurs when selecting - # numbers from deep subqueries - return cursor.var( - cx_Oracle.STRING, - 255, - outconverter=dialect._detect_decimal, - arraysize=cursor.arraysize, - ) - elif precision and scale > 0: - return number_handler( - cursor, name, default_type, size, precision, scale - ) - else: - return float_handler( - cursor, name, default_type, size, precision, scale - ) - - # if unicode options were specified, add a decoder, otherwise - # cx_Oracle should return Unicode - elif ( - dialect._cursor_var_unicode_kwargs - and default_type - in ( - cx_Oracle.STRING, - cx_Oracle.FIXED_CHAR, - ) - and default_type is not cx_Oracle.CLOB - and default_type is not cx_Oracle.NCLOB - ): - return cursor.var( - str, - size, - cursor.arraysize, - **dialect._cursor_var_unicode_kwargs, - ) - - elif dialect.auto_convert_lobs and default_type in ( - cx_Oracle.CLOB, - cx_Oracle.NCLOB, - ): - return cursor.var( - cx_Oracle.DB_TYPE_NVARCHAR, - _CX_ORACLE_MAGIC_LOB_SIZE, - cursor.arraysize, - **dialect._cursor_var_unicode_kwargs, - ) - - elif dialect.auto_convert_lobs and default_type in ( - cx_Oracle.BLOB, - ): - return cursor.var( - cx_Oracle.DB_TYPE_RAW, - _CX_ORACLE_MAGIC_LOB_SIZE, - cursor.arraysize, - ) - - return output_type_handler - - def on_connect(self): - output_type_handler = self._generate_connection_outputtype_handler() - - def on_connect(conn): - conn.outputtypehandler = output_type_handler - - return on_connect - - def create_connect_args(self, url): - opts = dict(url.query) - - for opt in ("use_ansi", "auto_convert_lobs"): - if opt in opts: - util.warn_deprecated( - f"{self.driver} dialect option {opt!r} should only be " - "passed to create_engine directly, not within the URL " - "string", - version="1.3", - ) - util.coerce_kw_type(opts, opt, bool) - setattr(self, opt, opts.pop(opt)) - - database = url.database - service_name = opts.pop("service_name", None) - if database or service_name: - # if we have a database, then we have a remote host - port = url.port - if port: - port = int(port) - else: - port = 1521 - - if database and service_name: - raise exc.InvalidRequestError( - '"service_name" option shouldn\'t ' - 'be used with a "database" part of the url' - ) - if database: - makedsn_kwargs = {"sid": database} - if service_name: - makedsn_kwargs = {"service_name": service_name} - - dsn = self.dbapi.makedsn(url.host, port, **makedsn_kwargs) - else: - # we have a local tnsname - dsn = url.host - - if dsn is not None: - opts["dsn"] = dsn - if url.password is not None: - opts["password"] = url.password - if url.username is not None: - opts["user"] = url.username - - if self._cx_oracle_threaded is not None: - opts.setdefault("threaded", self._cx_oracle_threaded) - - def convert_cx_oracle_constant(value): - if isinstance(value, str): - try: - int_val = int(value) - except ValueError: - value = value.upper() - return getattr(self.dbapi, value) - else: - return int_val - else: - return value - - util.coerce_kw_type(opts, "mode", convert_cx_oracle_constant) - util.coerce_kw_type(opts, "threaded", bool) - util.coerce_kw_type(opts, "events", bool) - util.coerce_kw_type(opts, "purity", convert_cx_oracle_constant) - return ([], opts) - - def _get_server_version_info(self, connection): - return tuple(int(x) for x in connection.connection.version.split(".")) - - def is_disconnect(self, e, connection, cursor): - (error,) = e.args - if isinstance( - e, (self.dbapi.InterfaceError, self.dbapi.DatabaseError) - ) and "not connected" in str(e): - return True - - if hasattr(error, "code") and error.code in { - 28, - 3114, - 3113, - 3135, - 1033, - 2396, - }: - # ORA-00028: your session has been killed - # ORA-03114: not connected to ORACLE - # ORA-03113: end-of-file on communication channel - # ORA-03135: connection lost contact - # ORA-01033: ORACLE initialization or shutdown in progress - # ORA-02396: exceeded maximum idle time, please connect again - # TODO: Others ? - return True - - if re.match(r"^(?:DPI-1010|DPI-1080|DPY-1001|DPY-4011)", str(e)): - # DPI-1010: not connected - # DPI-1080: connection was closed by ORA-3113 - # python-oracledb's DPY-1001: not connected to database - # python-oracledb's DPY-4011: the database or network closed the - # connection - # TODO: others? - return True - - return False - - def create_xid(self): - """create a two-phase transaction ID. - - this id will be passed to do_begin_twophase(), do_rollback_twophase(), - do_commit_twophase(). its format is unspecified. - - """ - - id_ = random.randint(0, 2**128) - return (0x1234, "%032x" % id_, "%032x" % 9) - - def do_executemany(self, cursor, statement, parameters, context=None): - if isinstance(parameters, tuple): - parameters = list(parameters) - cursor.executemany(statement, parameters) - - def do_begin_twophase(self, connection, xid): - connection.connection.begin(*xid) - connection.connection.info["cx_oracle_xid"] = xid - - def do_prepare_twophase(self, connection, xid): - result = connection.connection.prepare() - connection.info["cx_oracle_prepared"] = result - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - self.do_rollback(connection.connection) - # TODO: need to end XA state here - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if not is_prepared: - self.do_commit(connection.connection) - else: - if recover: - raise NotImplementedError( - "2pc recovery not implemented for cx_Oracle" - ) - oci_prepared = connection.info["cx_oracle_prepared"] - if oci_prepared: - self.do_commit(connection.connection) - # TODO: need to end XA state here - - def do_set_input_sizes(self, cursor, list_of_tuples, context): - if self.positional: - # not usually used, here to support if someone is modifying - # the dialect to use positional style - cursor.setinputsizes( - *[dbtype for key, dbtype, sqltype in list_of_tuples] - ) - else: - collection = ( - (key, dbtype) - for key, dbtype, sqltype in list_of_tuples - if dbtype - ) - - cursor.setinputsizes(**{key: dbtype for key, dbtype in collection}) - - def do_recover_twophase(self, connection): - raise NotImplementedError( - "recover two phase query for cx_Oracle not implemented" - ) - - -dialect = OracleDialect_cx_oracle diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/dictionary.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/dictionary.py deleted file mode 100644 index 63479b9..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/dictionary.py +++ /dev/null @@ -1,507 +0,0 @@ -# dialects/oracle/dictionary.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from .types import DATE -from .types import LONG -from .types import NUMBER -from .types import RAW -from .types import VARCHAR2 -from ... import Column -from ... import MetaData -from ... import Table -from ... import table -from ...sql.sqltypes import CHAR - -# constants -DB_LINK_PLACEHOLDER = "__$sa_dblink$__" -# tables -dual = table("dual") -dictionary_meta = MetaData() - -# NOTE: all the dictionary_meta are aliases because oracle does not like -# using the full table@dblink for every column in query, and complains with -# ORA-00960: ambiguous column naming in select list -all_tables = Table( - "all_tables" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("tablespace_name", VARCHAR2(30)), - Column("cluster_name", VARCHAR2(128)), - Column("iot_name", VARCHAR2(128)), - Column("status", VARCHAR2(8)), - Column("pct_free", NUMBER), - Column("pct_used", NUMBER), - Column("ini_trans", NUMBER), - Column("max_trans", NUMBER), - Column("initial_extent", NUMBER), - Column("next_extent", NUMBER), - Column("min_extents", NUMBER), - Column("max_extents", NUMBER), - Column("pct_increase", NUMBER), - Column("freelists", NUMBER), - Column("freelist_groups", NUMBER), - Column("logging", VARCHAR2(3)), - Column("backed_up", VARCHAR2(1)), - Column("num_rows", NUMBER), - Column("blocks", NUMBER), - Column("empty_blocks", NUMBER), - Column("avg_space", NUMBER), - Column("chain_cnt", NUMBER), - Column("avg_row_len", NUMBER), - Column("avg_space_freelist_blocks", NUMBER), - Column("num_freelist_blocks", NUMBER), - Column("degree", VARCHAR2(10)), - Column("instances", VARCHAR2(10)), - Column("cache", VARCHAR2(5)), - Column("table_lock", VARCHAR2(8)), - Column("sample_size", NUMBER), - Column("last_analyzed", DATE), - Column("partitioned", VARCHAR2(3)), - Column("iot_type", VARCHAR2(12)), - Column("temporary", VARCHAR2(1)), - Column("secondary", VARCHAR2(1)), - Column("nested", VARCHAR2(3)), - Column("buffer_pool", VARCHAR2(7)), - Column("flash_cache", VARCHAR2(7)), - Column("cell_flash_cache", VARCHAR2(7)), - Column("row_movement", VARCHAR2(8)), - Column("global_stats", VARCHAR2(3)), - Column("user_stats", VARCHAR2(3)), - Column("duration", VARCHAR2(15)), - Column("skip_corrupt", VARCHAR2(8)), - Column("monitoring", VARCHAR2(3)), - Column("cluster_owner", VARCHAR2(128)), - Column("dependencies", VARCHAR2(8)), - Column("compression", VARCHAR2(8)), - Column("compress_for", VARCHAR2(30)), - Column("dropped", VARCHAR2(3)), - Column("read_only", VARCHAR2(3)), - Column("segment_created", VARCHAR2(3)), - Column("result_cache", VARCHAR2(7)), - Column("clustering", VARCHAR2(3)), - Column("activity_tracking", VARCHAR2(23)), - Column("dml_timestamp", VARCHAR2(25)), - Column("has_identity", VARCHAR2(3)), - Column("container_data", VARCHAR2(3)), - Column("inmemory", VARCHAR2(8)), - Column("inmemory_priority", VARCHAR2(8)), - Column("inmemory_distribute", VARCHAR2(15)), - Column("inmemory_compression", VARCHAR2(17)), - Column("inmemory_duplicate", VARCHAR2(13)), - Column("default_collation", VARCHAR2(100)), - Column("duplicated", VARCHAR2(1)), - Column("sharded", VARCHAR2(1)), - Column("externally_sharded", VARCHAR2(1)), - Column("externally_duplicated", VARCHAR2(1)), - Column("external", VARCHAR2(3)), - Column("hybrid", VARCHAR2(3)), - Column("cellmemory", VARCHAR2(24)), - Column("containers_default", VARCHAR2(3)), - Column("container_map", VARCHAR2(3)), - Column("extended_data_link", VARCHAR2(3)), - Column("extended_data_link_map", VARCHAR2(3)), - Column("inmemory_service", VARCHAR2(12)), - Column("inmemory_service_name", VARCHAR2(1000)), - Column("container_map_object", VARCHAR2(3)), - Column("memoptimize_read", VARCHAR2(8)), - Column("memoptimize_write", VARCHAR2(8)), - Column("has_sensitive_column", VARCHAR2(3)), - Column("admit_null", VARCHAR2(3)), - Column("data_link_dml_enabled", VARCHAR2(3)), - Column("logical_replication", VARCHAR2(8)), -).alias("a_tables") - -all_views = Table( - "all_views" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("view_name", VARCHAR2(128), nullable=False), - Column("text_length", NUMBER), - Column("text", LONG), - Column("text_vc", VARCHAR2(4000)), - Column("type_text_length", NUMBER), - Column("type_text", VARCHAR2(4000)), - Column("oid_text_length", NUMBER), - Column("oid_text", VARCHAR2(4000)), - Column("view_type_owner", VARCHAR2(128)), - Column("view_type", VARCHAR2(128)), - Column("superview_name", VARCHAR2(128)), - Column("editioning_view", VARCHAR2(1)), - Column("read_only", VARCHAR2(1)), - Column("container_data", VARCHAR2(1)), - Column("bequeath", VARCHAR2(12)), - Column("origin_con_id", VARCHAR2(256)), - Column("default_collation", VARCHAR2(100)), - Column("containers_default", VARCHAR2(3)), - Column("container_map", VARCHAR2(3)), - Column("extended_data_link", VARCHAR2(3)), - Column("extended_data_link_map", VARCHAR2(3)), - Column("has_sensitive_column", VARCHAR2(3)), - Column("admit_null", VARCHAR2(3)), - Column("pdb_local_only", VARCHAR2(3)), -).alias("a_views") - -all_sequences = Table( - "all_sequences" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("sequence_owner", VARCHAR2(128), nullable=False), - Column("sequence_name", VARCHAR2(128), nullable=False), - Column("min_value", NUMBER), - Column("max_value", NUMBER), - Column("increment_by", NUMBER, nullable=False), - Column("cycle_flag", VARCHAR2(1)), - Column("order_flag", VARCHAR2(1)), - Column("cache_size", NUMBER, nullable=False), - Column("last_number", NUMBER, nullable=False), - Column("scale_flag", VARCHAR2(1)), - Column("extend_flag", VARCHAR2(1)), - Column("sharded_flag", VARCHAR2(1)), - Column("session_flag", VARCHAR2(1)), - Column("keep_value", VARCHAR2(1)), -).alias("a_sequences") - -all_users = Table( - "all_users" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("username", VARCHAR2(128), nullable=False), - Column("user_id", NUMBER, nullable=False), - Column("created", DATE, nullable=False), - Column("common", VARCHAR2(3)), - Column("oracle_maintained", VARCHAR2(1)), - Column("inherited", VARCHAR2(3)), - Column("default_collation", VARCHAR2(100)), - Column("implicit", VARCHAR2(3)), - Column("all_shard", VARCHAR2(3)), - Column("external_shard", VARCHAR2(3)), -).alias("a_users") - -all_mviews = Table( - "all_mviews" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("mview_name", VARCHAR2(128), nullable=False), - Column("container_name", VARCHAR2(128), nullable=False), - Column("query", LONG), - Column("query_len", NUMBER(38)), - Column("updatable", VARCHAR2(1)), - Column("update_log", VARCHAR2(128)), - Column("master_rollback_seg", VARCHAR2(128)), - Column("master_link", VARCHAR2(128)), - Column("rewrite_enabled", VARCHAR2(1)), - Column("rewrite_capability", VARCHAR2(9)), - Column("refresh_mode", VARCHAR2(6)), - Column("refresh_method", VARCHAR2(8)), - Column("build_mode", VARCHAR2(9)), - Column("fast_refreshable", VARCHAR2(18)), - Column("last_refresh_type", VARCHAR2(8)), - Column("last_refresh_date", DATE), - Column("last_refresh_end_time", DATE), - Column("staleness", VARCHAR2(19)), - Column("after_fast_refresh", VARCHAR2(19)), - Column("unknown_prebuilt", VARCHAR2(1)), - Column("unknown_plsql_func", VARCHAR2(1)), - Column("unknown_external_table", VARCHAR2(1)), - Column("unknown_consider_fresh", VARCHAR2(1)), - Column("unknown_import", VARCHAR2(1)), - Column("unknown_trusted_fd", VARCHAR2(1)), - Column("compile_state", VARCHAR2(19)), - Column("use_no_index", VARCHAR2(1)), - Column("stale_since", DATE), - Column("num_pct_tables", NUMBER), - Column("num_fresh_pct_regions", NUMBER), - Column("num_stale_pct_regions", NUMBER), - Column("segment_created", VARCHAR2(3)), - Column("evaluation_edition", VARCHAR2(128)), - Column("unusable_before", VARCHAR2(128)), - Column("unusable_beginning", VARCHAR2(128)), - Column("default_collation", VARCHAR2(100)), - Column("on_query_computation", VARCHAR2(1)), - Column("auto", VARCHAR2(3)), -).alias("a_mviews") - -all_tab_identity_cols = Table( - "all_tab_identity_cols" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_name", VARCHAR2(128), nullable=False), - Column("generation_type", VARCHAR2(10)), - Column("sequence_name", VARCHAR2(128), nullable=False), - Column("identity_options", VARCHAR2(298)), -).alias("a_tab_identity_cols") - -all_tab_cols = Table( - "all_tab_cols" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_name", VARCHAR2(128), nullable=False), - Column("data_type", VARCHAR2(128)), - Column("data_type_mod", VARCHAR2(3)), - Column("data_type_owner", VARCHAR2(128)), - Column("data_length", NUMBER, nullable=False), - Column("data_precision", NUMBER), - Column("data_scale", NUMBER), - Column("nullable", VARCHAR2(1)), - Column("column_id", NUMBER), - Column("default_length", NUMBER), - Column("data_default", LONG), - Column("num_distinct", NUMBER), - Column("low_value", RAW(1000)), - Column("high_value", RAW(1000)), - Column("density", NUMBER), - Column("num_nulls", NUMBER), - Column("num_buckets", NUMBER), - Column("last_analyzed", DATE), - Column("sample_size", NUMBER), - Column("character_set_name", VARCHAR2(44)), - Column("char_col_decl_length", NUMBER), - Column("global_stats", VARCHAR2(3)), - Column("user_stats", VARCHAR2(3)), - Column("avg_col_len", NUMBER), - Column("char_length", NUMBER), - Column("char_used", VARCHAR2(1)), - Column("v80_fmt_image", VARCHAR2(3)), - Column("data_upgraded", VARCHAR2(3)), - Column("hidden_column", VARCHAR2(3)), - Column("virtual_column", VARCHAR2(3)), - Column("segment_column_id", NUMBER), - Column("internal_column_id", NUMBER, nullable=False), - Column("histogram", VARCHAR2(15)), - Column("qualified_col_name", VARCHAR2(4000)), - Column("user_generated", VARCHAR2(3)), - Column("default_on_null", VARCHAR2(3)), - Column("identity_column", VARCHAR2(3)), - Column("evaluation_edition", VARCHAR2(128)), - Column("unusable_before", VARCHAR2(128)), - Column("unusable_beginning", VARCHAR2(128)), - Column("collation", VARCHAR2(100)), - Column("collated_column_id", NUMBER), -).alias("a_tab_cols") - -all_tab_comments = Table( - "all_tab_comments" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("table_type", VARCHAR2(11)), - Column("comments", VARCHAR2(4000)), - Column("origin_con_id", NUMBER), -).alias("a_tab_comments") - -all_col_comments = Table( - "all_col_comments" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_name", VARCHAR2(128), nullable=False), - Column("comments", VARCHAR2(4000)), - Column("origin_con_id", NUMBER), -).alias("a_col_comments") - -all_mview_comments = Table( - "all_mview_comments" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("mview_name", VARCHAR2(128), nullable=False), - Column("comments", VARCHAR2(4000)), -).alias("a_mview_comments") - -all_ind_columns = Table( - "all_ind_columns" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("index_owner", VARCHAR2(128), nullable=False), - Column("index_name", VARCHAR2(128), nullable=False), - Column("table_owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_name", VARCHAR2(4000)), - Column("column_position", NUMBER, nullable=False), - Column("column_length", NUMBER, nullable=False), - Column("char_length", NUMBER), - Column("descend", VARCHAR2(4)), - Column("collated_column_id", NUMBER), -).alias("a_ind_columns") - -all_indexes = Table( - "all_indexes" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("index_name", VARCHAR2(128), nullable=False), - Column("index_type", VARCHAR2(27)), - Column("table_owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("table_type", CHAR(11)), - Column("uniqueness", VARCHAR2(9)), - Column("compression", VARCHAR2(13)), - Column("prefix_length", NUMBER), - Column("tablespace_name", VARCHAR2(30)), - Column("ini_trans", NUMBER), - Column("max_trans", NUMBER), - Column("initial_extent", NUMBER), - Column("next_extent", NUMBER), - Column("min_extents", NUMBER), - Column("max_extents", NUMBER), - Column("pct_increase", NUMBER), - Column("pct_threshold", NUMBER), - Column("include_column", NUMBER), - Column("freelists", NUMBER), - Column("freelist_groups", NUMBER), - Column("pct_free", NUMBER), - Column("logging", VARCHAR2(3)), - Column("blevel", NUMBER), - Column("leaf_blocks", NUMBER), - Column("distinct_keys", NUMBER), - Column("avg_leaf_blocks_per_key", NUMBER), - Column("avg_data_blocks_per_key", NUMBER), - Column("clustering_factor", NUMBER), - Column("status", VARCHAR2(8)), - Column("num_rows", NUMBER), - Column("sample_size", NUMBER), - Column("last_analyzed", DATE), - Column("degree", VARCHAR2(40)), - Column("instances", VARCHAR2(40)), - Column("partitioned", VARCHAR2(3)), - Column("temporary", VARCHAR2(1)), - Column("generated", VARCHAR2(1)), - Column("secondary", VARCHAR2(1)), - Column("buffer_pool", VARCHAR2(7)), - Column("flash_cache", VARCHAR2(7)), - Column("cell_flash_cache", VARCHAR2(7)), - Column("user_stats", VARCHAR2(3)), - Column("duration", VARCHAR2(15)), - Column("pct_direct_access", NUMBER), - Column("ityp_owner", VARCHAR2(128)), - Column("ityp_name", VARCHAR2(128)), - Column("parameters", VARCHAR2(1000)), - Column("global_stats", VARCHAR2(3)), - Column("domidx_status", VARCHAR2(12)), - Column("domidx_opstatus", VARCHAR2(6)), - Column("funcidx_status", VARCHAR2(8)), - Column("join_index", VARCHAR2(3)), - Column("iot_redundant_pkey_elim", VARCHAR2(3)), - Column("dropped", VARCHAR2(3)), - Column("visibility", VARCHAR2(9)), - Column("domidx_management", VARCHAR2(14)), - Column("segment_created", VARCHAR2(3)), - Column("orphaned_entries", VARCHAR2(3)), - Column("indexing", VARCHAR2(7)), - Column("auto", VARCHAR2(3)), -).alias("a_indexes") - -all_ind_expressions = Table( - "all_ind_expressions" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("index_owner", VARCHAR2(128), nullable=False), - Column("index_name", VARCHAR2(128), nullable=False), - Column("table_owner", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_expression", LONG), - Column("column_position", NUMBER, nullable=False), -).alias("a_ind_expressions") - -all_constraints = Table( - "all_constraints" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128)), - Column("constraint_name", VARCHAR2(128)), - Column("constraint_type", VARCHAR2(1)), - Column("table_name", VARCHAR2(128)), - Column("search_condition", LONG), - Column("search_condition_vc", VARCHAR2(4000)), - Column("r_owner", VARCHAR2(128)), - Column("r_constraint_name", VARCHAR2(128)), - Column("delete_rule", VARCHAR2(9)), - Column("status", VARCHAR2(8)), - Column("deferrable", VARCHAR2(14)), - Column("deferred", VARCHAR2(9)), - Column("validated", VARCHAR2(13)), - Column("generated", VARCHAR2(14)), - Column("bad", VARCHAR2(3)), - Column("rely", VARCHAR2(4)), - Column("last_change", DATE), - Column("index_owner", VARCHAR2(128)), - Column("index_name", VARCHAR2(128)), - Column("invalid", VARCHAR2(7)), - Column("view_related", VARCHAR2(14)), - Column("origin_con_id", VARCHAR2(256)), -).alias("a_constraints") - -all_cons_columns = Table( - "all_cons_columns" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("constraint_name", VARCHAR2(128), nullable=False), - Column("table_name", VARCHAR2(128), nullable=False), - Column("column_name", VARCHAR2(4000)), - Column("position", NUMBER), -).alias("a_cons_columns") - -# TODO figure out if it's still relevant, since there is no mention from here -# https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/ALL_DB_LINKS.html -# original note: -# using user_db_links here since all_db_links appears -# to have more restricted permissions. -# https://docs.oracle.com/cd/B28359_01/server.111/b28310/ds_admin005.htm -# will need to hear from more users if we are doing -# the right thing here. See [ticket:2619] -all_db_links = Table( - "all_db_links" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("db_link", VARCHAR2(128), nullable=False), - Column("username", VARCHAR2(128)), - Column("host", VARCHAR2(2000)), - Column("created", DATE, nullable=False), - Column("hidden", VARCHAR2(3)), - Column("shard_internal", VARCHAR2(3)), - Column("valid", VARCHAR2(3)), - Column("intra_cdb", VARCHAR2(3)), -).alias("a_db_links") - -all_synonyms = Table( - "all_synonyms" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128)), - Column("synonym_name", VARCHAR2(128)), - Column("table_owner", VARCHAR2(128)), - Column("table_name", VARCHAR2(128)), - Column("db_link", VARCHAR2(128)), - Column("origin_con_id", VARCHAR2(256)), -).alias("a_synonyms") - -all_objects = Table( - "all_objects" + DB_LINK_PLACEHOLDER, - dictionary_meta, - Column("owner", VARCHAR2(128), nullable=False), - Column("object_name", VARCHAR2(128), nullable=False), - Column("subobject_name", VARCHAR2(128)), - Column("object_id", NUMBER, nullable=False), - Column("data_object_id", NUMBER), - Column("object_type", VARCHAR2(23)), - Column("created", DATE, nullable=False), - Column("last_ddl_time", DATE, nullable=False), - Column("timestamp", VARCHAR2(19)), - Column("status", VARCHAR2(7)), - Column("temporary", VARCHAR2(1)), - Column("generated", VARCHAR2(1)), - Column("secondary", VARCHAR2(1)), - Column("namespace", NUMBER, nullable=False), - Column("edition_name", VARCHAR2(128)), - Column("sharing", VARCHAR2(13)), - Column("editionable", VARCHAR2(1)), - Column("oracle_maintained", VARCHAR2(1)), - Column("application", VARCHAR2(1)), - Column("default_collation", VARCHAR2(100)), - Column("duplicated", VARCHAR2(1)), - Column("sharded", VARCHAR2(1)), - Column("created_appid", NUMBER), - Column("created_vsnid", NUMBER), - Column("modified_appid", NUMBER), - Column("modified_vsnid", NUMBER), -).alias("a_objects") diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/oracledb.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/oracledb.py deleted file mode 100644 index 9cdec3b..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/oracledb.py +++ /dev/null @@ -1,311 +0,0 @@ -# dialects/oracle/oracledb.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: oracle+oracledb - :name: python-oracledb - :dbapi: oracledb - :connectstring: oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=[&key=value&key=value...]] - :url: https://oracle.github.io/python-oracledb/ - -python-oracledb is released by Oracle to supersede the cx_Oracle driver. -It is fully compatible with cx_Oracle and features both a "thin" client -mode that requires no dependencies, as well as a "thick" mode that uses -the Oracle Client Interface in the same way as cx_Oracle. - -.. seealso:: - - :ref:`cx_oracle` - all of cx_Oracle's notes apply to the oracledb driver - as well. - -The SQLAlchemy ``oracledb`` dialect provides both a sync and an async -implementation under the same dialect name. The proper version is -selected depending on how the engine is created: - -* calling :func:`_sa.create_engine` with ``oracle+oracledb://...`` will - automatically select the sync version, e.g.:: - - from sqlalchemy import create_engine - sync_engine = create_engine("oracle+oracledb://scott:tiger@localhost/?service_name=XEPDB1") - -* calling :func:`_asyncio.create_async_engine` with - ``oracle+oracledb://...`` will automatically select the async version, - e.g.:: - - from sqlalchemy.ext.asyncio import create_async_engine - asyncio_engine = create_async_engine("oracle+oracledb://scott:tiger@localhost/?service_name=XEPDB1") - -The asyncio version of the dialect may also be specified explicitly using the -``oracledb_async`` suffix, as:: - - from sqlalchemy.ext.asyncio import create_async_engine - asyncio_engine = create_async_engine("oracle+oracledb_async://scott:tiger@localhost/?service_name=XEPDB1") - -.. versionadded:: 2.0.25 added support for the async version of oracledb. - -Thick mode support ------------------- - -By default the ``python-oracledb`` is started in thin mode, that does not -require oracle client libraries to be installed in the system. The -``python-oracledb`` driver also support a "thick" mode, that behaves -similarly to ``cx_oracle`` and requires that Oracle Client Interface (OCI) -is installed. - -To enable this mode, the user may call ``oracledb.init_oracle_client`` -manually, or by passing the parameter ``thick_mode=True`` to -:func:`_sa.create_engine`. To pass custom arguments to ``init_oracle_client``, -like the ``lib_dir`` path, a dict may be passed to this parameter, as in:: - - engine = sa.create_engine("oracle+oracledb://...", thick_mode={ - "lib_dir": "/path/to/oracle/client/lib", "driver_name": "my-app" - }) - -.. seealso:: - - https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client - - -.. versionadded:: 2.0.0 added support for oracledb driver. - -""" # noqa -from __future__ import annotations - -import collections -import re -from typing import Any -from typing import TYPE_CHECKING - -from .cx_oracle import OracleDialect_cx_oracle as _OracleDialect_cx_oracle -from ... import exc -from ... import pool -from ...connectors.asyncio import AsyncAdapt_dbapi_connection -from ...connectors.asyncio import AsyncAdapt_dbapi_cursor -from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection -from ...util import asbool -from ...util import await_fallback -from ...util import await_only - -if TYPE_CHECKING: - from oracledb import AsyncConnection - from oracledb import AsyncCursor - - -class OracleDialect_oracledb(_OracleDialect_cx_oracle): - supports_statement_cache = True - driver = "oracledb" - _min_version = (1,) - - def __init__( - self, - auto_convert_lobs=True, - coerce_to_decimal=True, - arraysize=None, - encoding_errors=None, - thick_mode=None, - **kwargs, - ): - super().__init__( - auto_convert_lobs, - coerce_to_decimal, - arraysize, - encoding_errors, - **kwargs, - ) - - if self.dbapi is not None and ( - thick_mode or isinstance(thick_mode, dict) - ): - kw = thick_mode if isinstance(thick_mode, dict) else {} - self.dbapi.init_oracle_client(**kw) - - @classmethod - def import_dbapi(cls): - import oracledb - - return oracledb - - @classmethod - def is_thin_mode(cls, connection): - return connection.connection.dbapi_connection.thin - - @classmethod - def get_async_dialect_cls(cls, url): - return OracleDialectAsync_oracledb - - def _load_version(self, dbapi_module): - version = (0, 0, 0) - if dbapi_module is not None: - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version) - if m: - version = tuple( - int(x) for x in m.group(1, 2, 3) if x is not None - ) - self.oracledb_ver = version - if ( - self.oracledb_ver > (0, 0, 0) - and self.oracledb_ver < self._min_version - ): - raise exc.InvalidRequestError( - f"oracledb version {self._min_version} and above are supported" - ) - - -class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor): - _cursor: AsyncCursor - __slots__ = () - - @property - def outputtypehandler(self): - return self._cursor.outputtypehandler - - @outputtypehandler.setter - def outputtypehandler(self, value): - self._cursor.outputtypehandler = value - - def var(self, *args, **kwargs): - return self._cursor.var(*args, **kwargs) - - def close(self): - self._rows.clear() - self._cursor.close() - - def setinputsizes(self, *args: Any, **kwargs: Any) -> Any: - return self._cursor.setinputsizes(*args, **kwargs) - - def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor: - try: - return cursor.__enter__() - except Exception as error: - self._adapt_connection._handle_exception(error) - - async def _execute_async(self, operation, parameters): - # override to not use mutex, oracledb already has mutex - - if parameters is None: - result = await self._cursor.execute(operation) - else: - result = await self._cursor.execute(operation, parameters) - - if self._cursor.description and not self.server_side: - self._rows = collections.deque(await self._cursor.fetchall()) - return result - - async def _executemany_async( - self, - operation, - seq_of_parameters, - ): - # override to not use mutex, oracledb already has mutex - return await self._cursor.executemany(operation, seq_of_parameters) - - def __enter__(self): - return self - - def __exit__(self, type_: Any, value: Any, traceback: Any) -> None: - self.close() - - -class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection): - _connection: AsyncConnection - __slots__ = () - - thin = True - - _cursor_cls = AsyncAdapt_oracledb_cursor - _ss_cursor_cls = None - - @property - def autocommit(self): - return self._connection.autocommit - - @autocommit.setter - def autocommit(self, value): - self._connection.autocommit = value - - @property - def outputtypehandler(self): - return self._connection.outputtypehandler - - @outputtypehandler.setter - def outputtypehandler(self, value): - self._connection.outputtypehandler = value - - @property - def version(self): - return self._connection.version - - @property - def stmtcachesize(self): - return self._connection.stmtcachesize - - @stmtcachesize.setter - def stmtcachesize(self, value): - self._connection.stmtcachesize = value - - def cursor(self): - return AsyncAdapt_oracledb_cursor(self) - - -class AsyncAdaptFallback_oracledb_connection( - AsyncAdaptFallback_dbapi_connection, AsyncAdapt_oracledb_connection -): - __slots__ = () - - -class OracledbAdaptDBAPI: - def __init__(self, oracledb) -> None: - self.oracledb = oracledb - - for k, v in self.oracledb.__dict__.items(): - if k != "connect": - self.__dict__[k] = v - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - creator_fn = kw.pop("async_creator_fn", self.oracledb.connect_async) - - if asbool(async_fallback): - return AsyncAdaptFallback_oracledb_connection( - self, await_fallback(creator_fn(*arg, **kw)) - ) - - else: - return AsyncAdapt_oracledb_connection( - self, await_only(creator_fn(*arg, **kw)) - ) - - -class OracleDialectAsync_oracledb(OracleDialect_oracledb): - is_async = True - supports_statement_cache = True - - _min_version = (2,) - - # thick_mode mode is not supported by asyncio, oracledb will raise - @classmethod - def import_dbapi(cls): - import oracledb - - return OracledbAdaptDBAPI(oracledb) - - @classmethod - def get_pool_class(cls, url): - async_fallback = url.query.get("async_fallback", False) - - if asbool(async_fallback): - return pool.FallbackAsyncAdaptedQueuePool - else: - return pool.AsyncAdaptedQueuePool - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = OracleDialect_oracledb -dialect_async = OracleDialectAsync_oracledb diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/provision.py deleted file mode 100644 index b33c152..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/provision.py +++ /dev/null @@ -1,220 +0,0 @@ -# dialects/oracle/provision.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import create_engine -from ... import exc -from ... import inspect -from ...engine import url as sa_url -from ...testing.provision import configure_follower -from ...testing.provision import create_db -from ...testing.provision import drop_all_schema_objects_post_tables -from ...testing.provision import drop_all_schema_objects_pre_tables -from ...testing.provision import drop_db -from ...testing.provision import follower_url_from_main -from ...testing.provision import log -from ...testing.provision import post_configure_engine -from ...testing.provision import run_reap_dbs -from ...testing.provision import set_default_schema_on_connection -from ...testing.provision import stop_test_class_outside_fixtures -from ...testing.provision import temp_table_keyword_args -from ...testing.provision import update_db_opts - - -@create_db.for_db("oracle") -def _oracle_create_db(cfg, eng, ident): - # NOTE: make sure you've run "ALTER DATABASE default tablespace users" or - # similar, so that the default tablespace is not "system"; reflection will - # fail otherwise - with eng.begin() as conn: - conn.exec_driver_sql("create user %s identified by xe" % ident) - conn.exec_driver_sql("create user %s_ts1 identified by xe" % ident) - conn.exec_driver_sql("create user %s_ts2 identified by xe" % ident) - conn.exec_driver_sql("grant dba to %s" % (ident,)) - conn.exec_driver_sql("grant unlimited tablespace to %s" % ident) - conn.exec_driver_sql("grant unlimited tablespace to %s_ts1" % ident) - conn.exec_driver_sql("grant unlimited tablespace to %s_ts2" % ident) - # these are needed to create materialized views - conn.exec_driver_sql("grant create table to %s" % ident) - conn.exec_driver_sql("grant create table to %s_ts1" % ident) - conn.exec_driver_sql("grant create table to %s_ts2" % ident) - - -@configure_follower.for_db("oracle") -def _oracle_configure_follower(config, ident): - config.test_schema = "%s_ts1" % ident - config.test_schema_2 = "%s_ts2" % ident - - -def _ora_drop_ignore(conn, dbname): - try: - conn.exec_driver_sql("drop user %s cascade" % dbname) - log.info("Reaped db: %s", dbname) - return True - except exc.DatabaseError as err: - log.warning("couldn't drop db: %s", err) - return False - - -@drop_all_schema_objects_pre_tables.for_db("oracle") -def _ora_drop_all_schema_objects_pre_tables(cfg, eng): - _purge_recyclebin(eng) - _purge_recyclebin(eng, cfg.test_schema) - - -@drop_all_schema_objects_post_tables.for_db("oracle") -def _ora_drop_all_schema_objects_post_tables(cfg, eng): - with eng.begin() as conn: - for syn in conn.dialect._get_synonyms(conn, None, None, None): - conn.exec_driver_sql(f"drop synonym {syn['synonym_name']}") - - for syn in conn.dialect._get_synonyms( - conn, cfg.test_schema, None, None - ): - conn.exec_driver_sql( - f"drop synonym {cfg.test_schema}.{syn['synonym_name']}" - ) - - for tmp_table in inspect(conn).get_temp_table_names(): - conn.exec_driver_sql(f"drop table {tmp_table}") - - -@drop_db.for_db("oracle") -def _oracle_drop_db(cfg, eng, ident): - with eng.begin() as conn: - # cx_Oracle seems to occasionally leak open connections when a large - # suite it run, even if we confirm we have zero references to - # connection objects. - # while there is a "kill session" command in Oracle, - # it unfortunately does not release the connection sufficiently. - _ora_drop_ignore(conn, ident) - _ora_drop_ignore(conn, "%s_ts1" % ident) - _ora_drop_ignore(conn, "%s_ts2" % ident) - - -@stop_test_class_outside_fixtures.for_db("oracle") -def _ora_stop_test_class_outside_fixtures(config, db, cls): - try: - _purge_recyclebin(db) - except exc.DatabaseError as err: - log.warning("purge recyclebin command failed: %s", err) - - # clear statement cache on all connections that were used - # https://github.com/oracle/python-cx_Oracle/issues/519 - - for cx_oracle_conn in _all_conns: - try: - sc = cx_oracle_conn.stmtcachesize - except db.dialect.dbapi.InterfaceError: - # connection closed - pass - else: - cx_oracle_conn.stmtcachesize = 0 - cx_oracle_conn.stmtcachesize = sc - _all_conns.clear() - - -def _purge_recyclebin(eng, schema=None): - with eng.begin() as conn: - if schema is None: - # run magic command to get rid of identity sequences - # https://floo.bar/2019/11/29/drop-the-underlying-sequence-of-an-identity-column/ # noqa: E501 - conn.exec_driver_sql("purge recyclebin") - else: - # per user: https://community.oracle.com/tech/developers/discussion/2255402/how-to-clear-dba-recyclebin-for-a-particular-user # noqa: E501 - for owner, object_name, type_ in conn.exec_driver_sql( - "select owner, object_name,type from " - "dba_recyclebin where owner=:schema and type='TABLE'", - {"schema": conn.dialect.denormalize_name(schema)}, - ).all(): - conn.exec_driver_sql(f'purge {type_} {owner}."{object_name}"') - - -_all_conns = set() - - -@post_configure_engine.for_db("oracle") -def _oracle_post_configure_engine(url, engine, follower_ident): - from sqlalchemy import event - - @event.listens_for(engine, "checkout") - def checkout(dbapi_con, con_record, con_proxy): - _all_conns.add(dbapi_con) - - @event.listens_for(engine, "checkin") - def checkin(dbapi_connection, connection_record): - # work around cx_Oracle issue: - # https://github.com/oracle/python-cx_Oracle/issues/530 - # invalidate oracle connections that had 2pc set up - if "cx_oracle_xid" in connection_record.info: - connection_record.invalidate() - - -@run_reap_dbs.for_db("oracle") -def _reap_oracle_dbs(url, idents): - log.info("db reaper connecting to %r", url) - eng = create_engine(url) - with eng.begin() as conn: - log.info("identifiers in file: %s", ", ".join(idents)) - - to_reap = conn.exec_driver_sql( - "select u.username from all_users u where username " - "like 'TEST_%' and not exists (select username " - "from v$session where username=u.username)" - ) - all_names = {username.lower() for (username,) in to_reap} - to_drop = set() - for name in all_names: - if name.endswith("_ts1") or name.endswith("_ts2"): - continue - elif name in idents: - to_drop.add(name) - if "%s_ts1" % name in all_names: - to_drop.add("%s_ts1" % name) - if "%s_ts2" % name in all_names: - to_drop.add("%s_ts2" % name) - - dropped = total = 0 - for total, username in enumerate(to_drop, 1): - if _ora_drop_ignore(conn, username): - dropped += 1 - log.info( - "Dropped %d out of %d stale databases detected", dropped, total - ) - - -@follower_url_from_main.for_db("oracle") -def _oracle_follower_url_from_main(url, ident): - url = sa_url.make_url(url) - return url.set(username=ident, password="xe") - - -@temp_table_keyword_args.for_db("oracle") -def _oracle_temp_table_keyword_args(cfg, eng): - return { - "prefixes": ["GLOBAL TEMPORARY"], - "oracle_on_commit": "PRESERVE ROWS", - } - - -@set_default_schema_on_connection.for_db("oracle") -def _oracle_set_default_schema_on_connection( - cfg, dbapi_connection, schema_name -): - cursor = dbapi_connection.cursor() - cursor.execute("ALTER SESSION SET CURRENT_SCHEMA=%s" % schema_name) - cursor.close() - - -@update_db_opts.for_db("oracle") -def _update_db_opts(db_url, db_opts, options): - """Set database options (db_opts) for a test database that we created.""" - if ( - options.oracledb_thick_mode - and sa_url.make_url(db_url).get_driver_name() == "oracledb" - ): - db_opts["thick_mode"] = True diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/types.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/types.py deleted file mode 100644 index 36caaa0..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/types.py +++ /dev/null @@ -1,287 +0,0 @@ -# dialects/oracle/types.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from __future__ import annotations - -import datetime as dt -from typing import Optional -from typing import Type -from typing import TYPE_CHECKING - -from ... import exc -from ...sql import sqltypes -from ...types import NVARCHAR -from ...types import VARCHAR - -if TYPE_CHECKING: - from ...engine.interfaces import Dialect - from ...sql.type_api import _LiteralProcessorType - - -class RAW(sqltypes._Binary): - __visit_name__ = "RAW" - - -OracleRaw = RAW - - -class NCLOB(sqltypes.Text): - __visit_name__ = "NCLOB" - - -class VARCHAR2(VARCHAR): - __visit_name__ = "VARCHAR2" - - -NVARCHAR2 = NVARCHAR - - -class NUMBER(sqltypes.Numeric, sqltypes.Integer): - __visit_name__ = "NUMBER" - - def __init__(self, precision=None, scale=None, asdecimal=None): - if asdecimal is None: - asdecimal = bool(scale and scale > 0) - - super().__init__(precision=precision, scale=scale, asdecimal=asdecimal) - - def adapt(self, impltype): - ret = super().adapt(impltype) - # leave a hint for the DBAPI handler - ret._is_oracle_number = True - return ret - - @property - def _type_affinity(self): - if bool(self.scale and self.scale > 0): - return sqltypes.Numeric - else: - return sqltypes.Integer - - -class FLOAT(sqltypes.FLOAT): - """Oracle FLOAT. - - This is the same as :class:`_sqltypes.FLOAT` except that - an Oracle-specific :paramref:`_oracle.FLOAT.binary_precision` - parameter is accepted, and - the :paramref:`_sqltypes.Float.precision` parameter is not accepted. - - Oracle FLOAT types indicate precision in terms of "binary precision", which - defaults to 126. For a REAL type, the value is 63. This parameter does not - cleanly map to a specific number of decimal places but is roughly - equivalent to the desired number of decimal places divided by 0.3103. - - .. versionadded:: 2.0 - - """ - - __visit_name__ = "FLOAT" - - def __init__( - self, - binary_precision=None, - asdecimal=False, - decimal_return_scale=None, - ): - r""" - Construct a FLOAT - - :param binary_precision: Oracle binary precision value to be rendered - in DDL. This may be approximated to the number of decimal characters - using the formula "decimal precision = 0.30103 * binary precision". - The default value used by Oracle for FLOAT / DOUBLE PRECISION is 126. - - :param asdecimal: See :paramref:`_sqltypes.Float.asdecimal` - - :param decimal_return_scale: See - :paramref:`_sqltypes.Float.decimal_return_scale` - - """ - super().__init__( - asdecimal=asdecimal, decimal_return_scale=decimal_return_scale - ) - self.binary_precision = binary_precision - - -class BINARY_DOUBLE(sqltypes.Double): - __visit_name__ = "BINARY_DOUBLE" - - -class BINARY_FLOAT(sqltypes.Float): - __visit_name__ = "BINARY_FLOAT" - - -class BFILE(sqltypes.LargeBinary): - __visit_name__ = "BFILE" - - -class LONG(sqltypes.Text): - __visit_name__ = "LONG" - - -class _OracleDateLiteralRender: - def _literal_processor_datetime(self, dialect): - def process(value): - if getattr(value, "microsecond", None): - value = ( - f"""TO_TIMESTAMP""" - f"""('{value.isoformat().replace("T", " ")}', """ - """'YYYY-MM-DD HH24:MI:SS.FF')""" - ) - else: - value = ( - f"""TO_DATE""" - f"""('{value.isoformat().replace("T", " ")}', """ - """'YYYY-MM-DD HH24:MI:SS')""" - ) - return value - - return process - - def _literal_processor_date(self, dialect): - def process(value): - if getattr(value, "microsecond", None): - value = ( - f"""TO_TIMESTAMP""" - f"""('{value.isoformat().split("T")[0]}', """ - """'YYYY-MM-DD')""" - ) - else: - value = ( - f"""TO_DATE""" - f"""('{value.isoformat().split("T")[0]}', """ - """'YYYY-MM-DD')""" - ) - return value - - return process - - -class DATE(_OracleDateLiteralRender, sqltypes.DateTime): - """Provide the oracle DATE type. - - This type has no special Python behavior, except that it subclasses - :class:`_types.DateTime`; this is to suit the fact that the Oracle - ``DATE`` type supports a time value. - - """ - - __visit_name__ = "DATE" - - def literal_processor(self, dialect): - return self._literal_processor_datetime(dialect) - - def _compare_type_affinity(self, other): - return other._type_affinity in (sqltypes.DateTime, sqltypes.Date) - - -class _OracleDate(_OracleDateLiteralRender, sqltypes.Date): - def literal_processor(self, dialect): - return self._literal_processor_date(dialect) - - -class INTERVAL(sqltypes.NativeForEmulated, sqltypes._AbstractInterval): - __visit_name__ = "INTERVAL" - - def __init__(self, day_precision=None, second_precision=None): - """Construct an INTERVAL. - - Note that only DAY TO SECOND intervals are currently supported. - This is due to a lack of support for YEAR TO MONTH intervals - within available DBAPIs. - - :param day_precision: the day precision value. this is the number of - digits to store for the day field. Defaults to "2" - :param second_precision: the second precision value. this is the - number of digits to store for the fractional seconds field. - Defaults to "6". - - """ - self.day_precision = day_precision - self.second_precision = second_precision - - @classmethod - def _adapt_from_generic_interval(cls, interval): - return INTERVAL( - day_precision=interval.day_precision, - second_precision=interval.second_precision, - ) - - @classmethod - def adapt_emulated_to_native( - cls, interval: sqltypes.Interval, **kw # type: ignore[override] - ): - return INTERVAL( - day_precision=interval.day_precision, - second_precision=interval.second_precision, - ) - - @property - def _type_affinity(self): - return sqltypes.Interval - - def as_generic(self, allow_nulltype=False): - return sqltypes.Interval( - native=True, - second_precision=self.second_precision, - day_precision=self.day_precision, - ) - - @property - def python_type(self) -> Type[dt.timedelta]: - return dt.timedelta - - def literal_processor( - self, dialect: Dialect - ) -> Optional[_LiteralProcessorType[dt.timedelta]]: - def process(value: dt.timedelta) -> str: - return f"NUMTODSINTERVAL({value.total_seconds()}, 'SECOND')" - - return process - - -class TIMESTAMP(sqltypes.TIMESTAMP): - """Oracle implementation of ``TIMESTAMP``, which supports additional - Oracle-specific modes - - .. versionadded:: 2.0 - - """ - - def __init__(self, timezone: bool = False, local_timezone: bool = False): - """Construct a new :class:`_oracle.TIMESTAMP`. - - :param timezone: boolean. Indicates that the TIMESTAMP type should - use Oracle's ``TIMESTAMP WITH TIME ZONE`` datatype. - - :param local_timezone: boolean. Indicates that the TIMESTAMP type - should use Oracle's ``TIMESTAMP WITH LOCAL TIME ZONE`` datatype. - - - """ - if timezone and local_timezone: - raise exc.ArgumentError( - "timezone and local_timezone are mutually exclusive" - ) - super().__init__(timezone=timezone) - self.local_timezone = local_timezone - - -class ROWID(sqltypes.TypeEngine): - """Oracle ROWID type. - - When used in a cast() or similar, generates ROWID. - - """ - - __visit_name__ = "ROWID" - - -class _OracleBoolean(sqltypes.Boolean): - def get_dbapi_type(self, dbapi): - return dbapi.NUMBER diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__init__.py deleted file mode 100644 index 325ea88..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__init__.py +++ /dev/null @@ -1,167 +0,0 @@ -# dialects/postgresql/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from types import ModuleType - -from . import array as arraylib # noqa # keep above base and other dialects -from . import asyncpg # noqa -from . import base -from . import pg8000 # noqa -from . import psycopg # noqa -from . import psycopg2 # noqa -from . import psycopg2cffi # noqa -from .array import All -from .array import Any -from .array import ARRAY -from .array import array -from .base import BIGINT -from .base import BOOLEAN -from .base import CHAR -from .base import DATE -from .base import DOMAIN -from .base import DOUBLE_PRECISION -from .base import FLOAT -from .base import INTEGER -from .base import NUMERIC -from .base import REAL -from .base import SMALLINT -from .base import TEXT -from .base import UUID -from .base import VARCHAR -from .dml import Insert -from .dml import insert -from .ext import aggregate_order_by -from .ext import array_agg -from .ext import ExcludeConstraint -from .ext import phraseto_tsquery -from .ext import plainto_tsquery -from .ext import to_tsquery -from .ext import to_tsvector -from .ext import ts_headline -from .ext import websearch_to_tsquery -from .hstore import HSTORE -from .hstore import hstore -from .json import JSON -from .json import JSONB -from .json import JSONPATH -from .named_types import CreateDomainType -from .named_types import CreateEnumType -from .named_types import DropDomainType -from .named_types import DropEnumType -from .named_types import ENUM -from .named_types import NamedType -from .ranges import AbstractMultiRange -from .ranges import AbstractRange -from .ranges import AbstractSingleRange -from .ranges import DATEMULTIRANGE -from .ranges import DATERANGE -from .ranges import INT4MULTIRANGE -from .ranges import INT4RANGE -from .ranges import INT8MULTIRANGE -from .ranges import INT8RANGE -from .ranges import MultiRange -from .ranges import NUMMULTIRANGE -from .ranges import NUMRANGE -from .ranges import Range -from .ranges import TSMULTIRANGE -from .ranges import TSRANGE -from .ranges import TSTZMULTIRANGE -from .ranges import TSTZRANGE -from .types import BIT -from .types import BYTEA -from .types import CIDR -from .types import CITEXT -from .types import INET -from .types import INTERVAL -from .types import MACADDR -from .types import MACADDR8 -from .types import MONEY -from .types import OID -from .types import REGCLASS -from .types import REGCONFIG -from .types import TIME -from .types import TIMESTAMP -from .types import TSQUERY -from .types import TSVECTOR - - -# Alias psycopg also as psycopg_async -psycopg_async = type( - "psycopg_async", (ModuleType,), {"dialect": psycopg.dialect_async} -) - -base.dialect = dialect = psycopg2.dialect - - -__all__ = ( - "INTEGER", - "BIGINT", - "SMALLINT", - "VARCHAR", - "CHAR", - "TEXT", - "NUMERIC", - "FLOAT", - "REAL", - "INET", - "CIDR", - "CITEXT", - "UUID", - "BIT", - "MACADDR", - "MACADDR8", - "MONEY", - "OID", - "REGCLASS", - "REGCONFIG", - "TSQUERY", - "TSVECTOR", - "DOUBLE_PRECISION", - "TIMESTAMP", - "TIME", - "DATE", - "BYTEA", - "BOOLEAN", - "INTERVAL", - "ARRAY", - "ENUM", - "DOMAIN", - "dialect", - "array", - "HSTORE", - "hstore", - "INT4RANGE", - "INT8RANGE", - "NUMRANGE", - "DATERANGE", - "INT4MULTIRANGE", - "INT8MULTIRANGE", - "NUMMULTIRANGE", - "DATEMULTIRANGE", - "TSVECTOR", - "TSRANGE", - "TSTZRANGE", - "TSMULTIRANGE", - "TSTZMULTIRANGE", - "JSON", - "JSONB", - "JSONPATH", - "Any", - "All", - "DropEnumType", - "DropDomainType", - "CreateDomainType", - "NamedType", - "CreateEnumType", - "ExcludeConstraint", - "Range", - "aggregate_order_by", - "array_agg", - "insert", - "Insert", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 620cf0d..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-311.pyc deleted file mode 100644 index fc1553f..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc deleted file mode 100644 index a257440..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc deleted file mode 100644 index 50a38f0..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc deleted file mode 100644 index b5aaeaa..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc deleted file mode 100644 index 27eae2b..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc deleted file mode 100644 index 7ae58de..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc deleted file mode 100644 index c004810..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc deleted file mode 100644 index c429892..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-311.pyc deleted file mode 100644 index a075cd8..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-311.pyc deleted file mode 100644 index 93ccdfb..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc deleted file mode 100644 index 1bbbf9c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-311.pyc deleted file mode 100644 index 23fc72e..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index 39fb4c6..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-311.pyc deleted file mode 100644 index aa442c1..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc deleted file mode 100644 index 1308229..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc deleted file mode 100644 index bd4f6c4..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc deleted file mode 100644 index d0a785d..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-311.pyc deleted file mode 100644 index ed561da..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py deleted file mode 100644 index 46858c9..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py +++ /dev/null @@ -1,187 +0,0 @@ -# dialects/postgresql/_psycopg_common.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from __future__ import annotations - -import decimal - -from .array import ARRAY as PGARRAY -from .base import _DECIMAL_TYPES -from .base import _FLOAT_TYPES -from .base import _INT_TYPES -from .base import PGDialect -from .base import PGExecutionContext -from .hstore import HSTORE -from .pg_catalog import _SpaceVector -from .pg_catalog import INT2VECTOR -from .pg_catalog import OIDVECTOR -from ... import exc -from ... import types as sqltypes -from ... import util -from ...engine import processors - -_server_side_id = util.counter() - - -class _PsycopgNumeric(sqltypes.Numeric): - def bind_processor(self, dialect): - return None - - def result_processor(self, dialect, coltype): - if self.asdecimal: - if coltype in _FLOAT_TYPES: - return processors.to_decimal_processor_factory( - decimal.Decimal, self._effective_decimal_return_scale - ) - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - # psycopg returns Decimal natively for 1700 - return None - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - else: - if coltype in _FLOAT_TYPES: - # psycopg returns float natively for 701 - return None - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - return processors.to_float - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - - -class _PsycopgFloat(_PsycopgNumeric): - __visit_name__ = "float" - - -class _PsycopgHStore(HSTORE): - def bind_processor(self, dialect): - if dialect._has_native_hstore: - return None - else: - return super().bind_processor(dialect) - - def result_processor(self, dialect, coltype): - if dialect._has_native_hstore: - return None - else: - return super().result_processor(dialect, coltype) - - -class _PsycopgARRAY(PGARRAY): - render_bind_cast = True - - -class _PsycopgINT2VECTOR(_SpaceVector, INT2VECTOR): - pass - - -class _PsycopgOIDVECTOR(_SpaceVector, OIDVECTOR): - pass - - -class _PGExecutionContext_common_psycopg(PGExecutionContext): - def create_server_side_cursor(self): - # use server-side cursors: - # psycopg - # https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#server-side-cursors - # psycopg2 - # https://www.psycopg.org/docs/usage.html#server-side-cursors - ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:]) - return self._dbapi_connection.cursor(ident) - - -class _PGDialect_common_psycopg(PGDialect): - supports_statement_cache = True - supports_server_side_cursors = True - - default_paramstyle = "pyformat" - - _has_native_hstore = True - - colspecs = util.update_copy( - PGDialect.colspecs, - { - sqltypes.Numeric: _PsycopgNumeric, - sqltypes.Float: _PsycopgFloat, - HSTORE: _PsycopgHStore, - sqltypes.ARRAY: _PsycopgARRAY, - INT2VECTOR: _PsycopgINT2VECTOR, - OIDVECTOR: _PsycopgOIDVECTOR, - }, - ) - - def __init__( - self, - client_encoding=None, - use_native_hstore=True, - **kwargs, - ): - PGDialect.__init__(self, **kwargs) - if not use_native_hstore: - self._has_native_hstore = False - self.use_native_hstore = use_native_hstore - self.client_encoding = client_encoding - - def create_connect_args(self, url): - opts = url.translate_connect_args(username="user", database="dbname") - - multihosts, multiports = self._split_multihost_from_url(url) - - if opts or url.query: - if not opts: - opts = {} - if "port" in opts: - opts["port"] = int(opts["port"]) - opts.update(url.query) - - if multihosts: - opts["host"] = ",".join(multihosts) - comma_ports = ",".join(str(p) if p else "" for p in multiports) - if comma_ports: - opts["port"] = comma_ports - return ([], opts) - else: - # no connection arguments whatsoever; psycopg2.connect() - # requires that "dsn" be present as a blank string. - return ([""], opts) - - def get_isolation_level_values(self, dbapi_connection): - return ( - "AUTOCOMMIT", - "READ COMMITTED", - "READ UNCOMMITTED", - "REPEATABLE READ", - "SERIALIZABLE", - ) - - def set_deferrable(self, connection, value): - connection.deferrable = value - - def get_deferrable(self, connection): - return connection.deferrable - - def _do_autocommit(self, connection, value): - connection.autocommit = value - - def do_ping(self, dbapi_connection): - cursor = None - before_autocommit = dbapi_connection.autocommit - - if not before_autocommit: - dbapi_connection.autocommit = True - cursor = dbapi_connection.cursor() - try: - cursor.execute(self._dialect_specific_select_one) - finally: - cursor.close() - if not before_autocommit and not dbapi_connection.closed: - dbapi_connection.autocommit = before_autocommit - - return True diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/array.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/array.py deleted file mode 100644 index 1d63655..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/array.py +++ /dev/null @@ -1,425 +0,0 @@ -# dialects/postgresql/array.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -from __future__ import annotations - -import re -from typing import Any -from typing import Optional -from typing import TypeVar - -from .operators import CONTAINED_BY -from .operators import CONTAINS -from .operators import OVERLAP -from ... import types as sqltypes -from ... import util -from ...sql import expression -from ...sql import operators -from ...sql._typing import _TypeEngineArgument - - -_T = TypeVar("_T", bound=Any) - - -def Any(other, arrexpr, operator=operators.eq): - """A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.any` method. - See that method for details. - - """ - - return arrexpr.any(other, operator) - - -def All(other, arrexpr, operator=operators.eq): - """A synonym for the ARRAY-level :meth:`.ARRAY.Comparator.all` method. - See that method for details. - - """ - - return arrexpr.all(other, operator) - - -class array(expression.ExpressionClauseList[_T]): - """A PostgreSQL ARRAY literal. - - This is used to produce ARRAY literals in SQL expressions, e.g.:: - - from sqlalchemy.dialects.postgresql import array - from sqlalchemy.dialects import postgresql - from sqlalchemy import select, func - - stmt = select(array([1,2]) + array([3,4,5])) - - print(stmt.compile(dialect=postgresql.dialect())) - - Produces the SQL:: - - SELECT ARRAY[%(param_1)s, %(param_2)s] || - ARRAY[%(param_3)s, %(param_4)s, %(param_5)s]) AS anon_1 - - An instance of :class:`.array` will always have the datatype - :class:`_types.ARRAY`. The "inner" type of the array is inferred from - the values present, unless the ``type_`` keyword argument is passed:: - - array(['foo', 'bar'], type_=CHAR) - - Multidimensional arrays are produced by nesting :class:`.array` constructs. - The dimensionality of the final :class:`_types.ARRAY` - type is calculated by - recursively adding the dimensions of the inner :class:`_types.ARRAY` - type:: - - stmt = select( - array([ - array([1, 2]), array([3, 4]), array([column('q'), column('x')]) - ]) - ) - print(stmt.compile(dialect=postgresql.dialect())) - - Produces:: - - SELECT ARRAY[ARRAY[%(param_1)s, %(param_2)s], - ARRAY[%(param_3)s, %(param_4)s], ARRAY[q, x]] AS anon_1 - - .. versionadded:: 1.3.6 added support for multidimensional array literals - - .. seealso:: - - :class:`_postgresql.ARRAY` - - """ - - __visit_name__ = "array" - - stringify_dialect = "postgresql" - inherit_cache = True - - def __init__(self, clauses, **kw): - type_arg = kw.pop("type_", None) - super().__init__(operators.comma_op, *clauses, **kw) - - self._type_tuple = [arg.type for arg in self.clauses] - - main_type = ( - type_arg - if type_arg is not None - else self._type_tuple[0] if self._type_tuple else sqltypes.NULLTYPE - ) - - if isinstance(main_type, ARRAY): - self.type = ARRAY( - main_type.item_type, - dimensions=( - main_type.dimensions + 1 - if main_type.dimensions is not None - else 2 - ), - ) - else: - self.type = ARRAY(main_type) - - @property - def _select_iterable(self): - return (self,) - - def _bind_param(self, operator, obj, _assume_scalar=False, type_=None): - if _assume_scalar or operator is operators.getitem: - return expression.BindParameter( - None, - obj, - _compared_to_operator=operator, - type_=type_, - _compared_to_type=self.type, - unique=True, - ) - - else: - return array( - [ - self._bind_param( - operator, o, _assume_scalar=True, type_=type_ - ) - for o in obj - ] - ) - - def self_group(self, against=None): - if against in (operators.any_op, operators.all_op, operators.getitem): - return expression.Grouping(self) - else: - return self - - -class ARRAY(sqltypes.ARRAY): - """PostgreSQL ARRAY type. - - The :class:`_postgresql.ARRAY` type is constructed in the same way - as the core :class:`_types.ARRAY` type; a member type is required, and a - number of dimensions is recommended if the type is to be used for more - than one dimension:: - - from sqlalchemy.dialects import postgresql - - mytable = Table("mytable", metadata, - Column("data", postgresql.ARRAY(Integer, dimensions=2)) - ) - - The :class:`_postgresql.ARRAY` type provides all operations defined on the - core :class:`_types.ARRAY` type, including support for "dimensions", - indexed access, and simple matching such as - :meth:`.types.ARRAY.Comparator.any` and - :meth:`.types.ARRAY.Comparator.all`. :class:`_postgresql.ARRAY` - class also - provides PostgreSQL-specific methods for containment operations, including - :meth:`.postgresql.ARRAY.Comparator.contains` - :meth:`.postgresql.ARRAY.Comparator.contained_by`, and - :meth:`.postgresql.ARRAY.Comparator.overlap`, e.g.:: - - mytable.c.data.contains([1, 2]) - - Indexed access is one-based by default, to match that of PostgreSQL; - for zero-based indexed access, set - :paramref:`_postgresql.ARRAY.zero_indexes`. - - Additionally, the :class:`_postgresql.ARRAY` - type does not work directly in - conjunction with the :class:`.ENUM` type. For a workaround, see the - special type at :ref:`postgresql_array_of_enum`. - - .. container:: topic - - **Detecting Changes in ARRAY columns when using the ORM** - - The :class:`_postgresql.ARRAY` type, when used with the SQLAlchemy ORM, - does not detect in-place mutations to the array. In order to detect - these, the :mod:`sqlalchemy.ext.mutable` extension must be used, using - the :class:`.MutableList` class:: - - from sqlalchemy.dialects.postgresql import ARRAY - from sqlalchemy.ext.mutable import MutableList - - class SomeOrmClass(Base): - # ... - - data = Column(MutableList.as_mutable(ARRAY(Integer))) - - This extension will allow "in-place" changes such to the array - such as ``.append()`` to produce events which will be detected by the - unit of work. Note that changes to elements **inside** the array, - including subarrays that are mutated in place, are **not** detected. - - Alternatively, assigning a new array value to an ORM element that - replaces the old one will always trigger a change event. - - .. seealso:: - - :class:`_types.ARRAY` - base array type - - :class:`_postgresql.array` - produces a literal array value. - - """ - - def __init__( - self, - item_type: _TypeEngineArgument[Any], - as_tuple: bool = False, - dimensions: Optional[int] = None, - zero_indexes: bool = False, - ): - """Construct an ARRAY. - - E.g.:: - - Column('myarray', ARRAY(Integer)) - - Arguments are: - - :param item_type: The data type of items of this array. Note that - dimensionality is irrelevant here, so multi-dimensional arrays like - ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as - ``ARRAY(ARRAY(Integer))`` or such. - - :param as_tuple=False: Specify whether return results - should be converted to tuples from lists. DBAPIs such - as psycopg2 return lists by default. When tuples are - returned, the results are hashable. - - :param dimensions: if non-None, the ARRAY will assume a fixed - number of dimensions. This will cause the DDL emitted for this - ARRAY to include the exact number of bracket clauses ``[]``, - and will also optimize the performance of the type overall. - Note that PG arrays are always implicitly "non-dimensioned", - meaning they can store any number of dimensions no matter how - they were declared. - - :param zero_indexes=False: when True, index values will be converted - between Python zero-based and PostgreSQL one-based indexes, e.g. - a value of one will be added to all index values before passing - to the database. - - """ - if isinstance(item_type, ARRAY): - raise ValueError( - "Do not nest ARRAY types; ARRAY(basetype) " - "handles multi-dimensional arrays of basetype" - ) - if isinstance(item_type, type): - item_type = item_type() - self.item_type = item_type - self.as_tuple = as_tuple - self.dimensions = dimensions - self.zero_indexes = zero_indexes - - class Comparator(sqltypes.ARRAY.Comparator): - """Define comparison operations for :class:`_types.ARRAY`. - - Note that these operations are in addition to those provided - by the base :class:`.types.ARRAY.Comparator` class, including - :meth:`.types.ARRAY.Comparator.any` and - :meth:`.types.ARRAY.Comparator.all`. - - """ - - def contains(self, other, **kwargs): - """Boolean expression. Test if elements are a superset of the - elements of the argument array expression. - - kwargs may be ignored by this operator but are required for API - conformance. - """ - return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) - - def contained_by(self, other): - """Boolean expression. Test if elements are a proper subset of the - elements of the argument array expression. - """ - return self.operate( - CONTAINED_BY, other, result_type=sqltypes.Boolean - ) - - def overlap(self, other): - """Boolean expression. Test if array has elements in common with - an argument array expression. - """ - return self.operate(OVERLAP, other, result_type=sqltypes.Boolean) - - comparator_factory = Comparator - - @property - def hashable(self): - return self.as_tuple - - @property - def python_type(self): - return list - - def compare_values(self, x, y): - return x == y - - @util.memoized_property - def _against_native_enum(self): - return ( - isinstance(self.item_type, sqltypes.Enum) - and self.item_type.native_enum - ) - - def literal_processor(self, dialect): - item_proc = self.item_type.dialect_impl(dialect).literal_processor( - dialect - ) - if item_proc is None: - return None - - def to_str(elements): - return f"ARRAY[{', '.join(elements)}]" - - def process(value): - inner = self._apply_item_processor( - value, item_proc, self.dimensions, to_str - ) - return inner - - return process - - def bind_processor(self, dialect): - item_proc = self.item_type.dialect_impl(dialect).bind_processor( - dialect - ) - - def process(value): - if value is None: - return value - else: - return self._apply_item_processor( - value, item_proc, self.dimensions, list - ) - - return process - - def result_processor(self, dialect, coltype): - item_proc = self.item_type.dialect_impl(dialect).result_processor( - dialect, coltype - ) - - def process(value): - if value is None: - return value - else: - return self._apply_item_processor( - value, - item_proc, - self.dimensions, - tuple if self.as_tuple else list, - ) - - if self._against_native_enum: - super_rp = process - pattern = re.compile(r"^{(.*)}$") - - def handle_raw_string(value): - inner = pattern.match(value).group(1) - return _split_enum_values(inner) - - def process(value): - if value is None: - return value - # isinstance(value, str) is required to handle - # the case where a TypeDecorator for and Array of Enum is - # used like was required in sa < 1.3.17 - return super_rp( - handle_raw_string(value) - if isinstance(value, str) - else value - ) - - return process - - -def _split_enum_values(array_string): - if '"' not in array_string: - # no escape char is present so it can just split on the comma - return array_string.split(",") if array_string else [] - - # handles quoted strings from: - # r'abc,"quoted","also\\\\quoted", "quoted, comma", "esc \" quot", qpr' - # returns - # ['abc', 'quoted', 'also\\quoted', 'quoted, comma', 'esc " quot', 'qpr'] - text = array_string.replace(r"\"", "_$ESC_QUOTE$_") - text = text.replace(r"\\", "\\") - result = [] - on_quotes = re.split(r'(")', text) - in_quotes = False - for tok in on_quotes: - if tok == '"': - in_quotes = not in_quotes - elif in_quotes: - result.append(tok.replace("_$ESC_QUOTE$_", '"')) - else: - result.extend(re.findall(r"([^\s,]+),?", tok)) - return result diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py deleted file mode 100644 index df2656d..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py +++ /dev/null @@ -1,1262 +0,0 @@ -# dialects/postgresql/asyncpg.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql+asyncpg - :name: asyncpg - :dbapi: asyncpg - :connectstring: postgresql+asyncpg://user:password@host:port/dbname[?key=value&key=value...] - :url: https://magicstack.github.io/asyncpg/ - -The asyncpg dialect is SQLAlchemy's first Python asyncio dialect. - -Using a special asyncio mediation layer, the asyncpg dialect is usable -as the backend for the :ref:`SQLAlchemy asyncio ` -extension package. - -This dialect should normally be used only with the -:func:`_asyncio.create_async_engine` engine creation function:: - - from sqlalchemy.ext.asyncio import create_async_engine - engine = create_async_engine("postgresql+asyncpg://user:pass@hostname/dbname") - -.. versionadded:: 1.4 - -.. note:: - - By default asyncpg does not decode the ``json`` and ``jsonb`` types and - returns them as strings. SQLAlchemy sets default type decoder for ``json`` - and ``jsonb`` types using the python builtin ``json.loads`` function. - The json implementation used can be changed by setting the attribute - ``json_deserializer`` when creating the engine with - :func:`create_engine` or :func:`create_async_engine`. - -.. _asyncpg_multihost: - -Multihost Connections --------------------------- - -The asyncpg dialect features support for multiple fallback hosts in the -same way as that of the psycopg2 and psycopg dialects. The -syntax is the same, -using ``host=:`` combinations as additional query string arguments; -however, there is no default port, so all hosts must have a complete port number -present, otherwise an exception is raised:: - - engine = create_async_engine( - "postgresql+asyncpg://user:password@/dbname?host=HostA:5432&host=HostB:5432&host=HostC:5432" - ) - -For complete background on this syntax, see :ref:`psycopg2_multi_host`. - -.. versionadded:: 2.0.18 - -.. seealso:: - - :ref:`psycopg2_multi_host` - -.. _asyncpg_prepared_statement_cache: - -Prepared Statement Cache --------------------------- - -The asyncpg SQLAlchemy dialect makes use of ``asyncpg.connection.prepare()`` -for all statements. The prepared statement objects are cached after -construction which appears to grant a 10% or more performance improvement for -statement invocation. The cache is on a per-DBAPI connection basis, which -means that the primary storage for prepared statements is within DBAPI -connections pooled within the connection pool. The size of this cache -defaults to 100 statements per DBAPI connection and may be adjusted using the -``prepared_statement_cache_size`` DBAPI argument (note that while this argument -is implemented by SQLAlchemy, it is part of the DBAPI emulation portion of the -asyncpg dialect, therefore is handled as a DBAPI argument, not a dialect -argument):: - - - engine = create_async_engine("postgresql+asyncpg://user:pass@hostname/dbname?prepared_statement_cache_size=500") - -To disable the prepared statement cache, use a value of zero:: - - engine = create_async_engine("postgresql+asyncpg://user:pass@hostname/dbname?prepared_statement_cache_size=0") - -.. versionadded:: 1.4.0b2 Added ``prepared_statement_cache_size`` for asyncpg. - - -.. warning:: The ``asyncpg`` database driver necessarily uses caches for - PostgreSQL type OIDs, which become stale when custom PostgreSQL datatypes - such as ``ENUM`` objects are changed via DDL operations. Additionally, - prepared statements themselves which are optionally cached by SQLAlchemy's - driver as described above may also become "stale" when DDL has been emitted - to the PostgreSQL database which modifies the tables or other objects - involved in a particular prepared statement. - - The SQLAlchemy asyncpg dialect will invalidate these caches within its local - process when statements that represent DDL are emitted on a local - connection, but this is only controllable within a single Python process / - database engine. If DDL changes are made from other database engines - and/or processes, a running application may encounter asyncpg exceptions - ``InvalidCachedStatementError`` and/or ``InternalServerError("cache lookup - failed for type ")`` if it refers to pooled database connections which - operated upon the previous structures. The SQLAlchemy asyncpg dialect will - recover from these error cases when the driver raises these exceptions by - clearing its internal caches as well as those of the asyncpg driver in - response to them, but cannot prevent them from being raised in the first - place if the cached prepared statement or asyncpg type caches have gone - stale, nor can it retry the statement as the PostgreSQL transaction is - invalidated when these errors occur. - -.. _asyncpg_prepared_statement_name: - -Prepared Statement Name with PGBouncer --------------------------------------- - -By default, asyncpg enumerates prepared statements in numeric order, which -can lead to errors if a name has already been taken for another prepared -statement. This issue can arise if your application uses database proxies -such as PgBouncer to handle connections. One possible workaround is to -use dynamic prepared statement names, which asyncpg now supports through -an optional ``name`` value for the statement name. This allows you to -generate your own unique names that won't conflict with existing ones. -To achieve this, you can provide a function that will be called every time -a prepared statement is prepared:: - - from uuid import uuid4 - - engine = create_async_engine( - "postgresql+asyncpg://user:pass@somepgbouncer/dbname", - poolclass=NullPool, - connect_args={ - 'prepared_statement_name_func': lambda: f'__asyncpg_{uuid4()}__', - }, - ) - -.. seealso:: - - https://github.com/MagicStack/asyncpg/issues/837 - - https://github.com/sqlalchemy/sqlalchemy/issues/6467 - -.. warning:: When using PGBouncer, to prevent a buildup of useless prepared statements in - your application, it's important to use the :class:`.NullPool` pool - class, and to configure PgBouncer to use `DISCARD `_ - when returning connections. The DISCARD command is used to release resources held by the db connection, - including prepared statements. Without proper setup, prepared statements can - accumulate quickly and cause performance issues. - -Disabling the PostgreSQL JIT to improve ENUM datatype handling ---------------------------------------------------------------- - -Asyncpg has an `issue `_ when -using PostgreSQL ENUM datatypes, where upon the creation of new database -connections, an expensive query may be emitted in order to retrieve metadata -regarding custom types which has been shown to negatively affect performance. -To mitigate this issue, the PostgreSQL "jit" setting may be disabled from the -client using this setting passed to :func:`_asyncio.create_async_engine`:: - - engine = create_async_engine( - "postgresql+asyncpg://user:password@localhost/tmp", - connect_args={"server_settings": {"jit": "off"}}, - ) - -.. seealso:: - - https://github.com/MagicStack/asyncpg/issues/727 - -""" # noqa - -from __future__ import annotations - -import collections -import decimal -import json as _py_json -import re -import time - -from . import json -from . import ranges -from .array import ARRAY as PGARRAY -from .base import _DECIMAL_TYPES -from .base import _FLOAT_TYPES -from .base import _INT_TYPES -from .base import ENUM -from .base import INTERVAL -from .base import OID -from .base import PGCompiler -from .base import PGDialect -from .base import PGExecutionContext -from .base import PGIdentifierPreparer -from .base import REGCLASS -from .base import REGCONFIG -from .types import BIT -from .types import BYTEA -from .types import CITEXT -from ... import exc -from ... import pool -from ... import util -from ...engine import AdaptedConnection -from ...engine import processors -from ...sql import sqltypes -from ...util.concurrency import asyncio -from ...util.concurrency import await_fallback -from ...util.concurrency import await_only - - -class AsyncpgARRAY(PGARRAY): - render_bind_cast = True - - -class AsyncpgString(sqltypes.String): - render_bind_cast = True - - -class AsyncpgREGCONFIG(REGCONFIG): - render_bind_cast = True - - -class AsyncpgTime(sqltypes.Time): - render_bind_cast = True - - -class AsyncpgBit(BIT): - render_bind_cast = True - - -class AsyncpgByteA(BYTEA): - render_bind_cast = True - - -class AsyncpgDate(sqltypes.Date): - render_bind_cast = True - - -class AsyncpgDateTime(sqltypes.DateTime): - render_bind_cast = True - - -class AsyncpgBoolean(sqltypes.Boolean): - render_bind_cast = True - - -class AsyncPgInterval(INTERVAL): - render_bind_cast = True - - @classmethod - def adapt_emulated_to_native(cls, interval, **kw): - return AsyncPgInterval(precision=interval.second_precision) - - -class AsyncPgEnum(ENUM): - render_bind_cast = True - - -class AsyncpgInteger(sqltypes.Integer): - render_bind_cast = True - - -class AsyncpgBigInteger(sqltypes.BigInteger): - render_bind_cast = True - - -class AsyncpgJSON(json.JSON): - render_bind_cast = True - - def result_processor(self, dialect, coltype): - return None - - -class AsyncpgJSONB(json.JSONB): - render_bind_cast = True - - def result_processor(self, dialect, coltype): - return None - - -class AsyncpgJSONIndexType(sqltypes.JSON.JSONIndexType): - pass - - -class AsyncpgJSONIntIndexType(sqltypes.JSON.JSONIntIndexType): - __visit_name__ = "json_int_index" - - render_bind_cast = True - - -class AsyncpgJSONStrIndexType(sqltypes.JSON.JSONStrIndexType): - __visit_name__ = "json_str_index" - - render_bind_cast = True - - -class AsyncpgJSONPathType(json.JSONPathType): - def bind_processor(self, dialect): - def process(value): - if isinstance(value, str): - # If it's already a string assume that it's in json path - # format. This allows using cast with json paths literals - return value - elif value: - tokens = [str(elem) for elem in value] - return tokens - else: - return [] - - return process - - -class AsyncpgNumeric(sqltypes.Numeric): - render_bind_cast = True - - def bind_processor(self, dialect): - return None - - def result_processor(self, dialect, coltype): - if self.asdecimal: - if coltype in _FLOAT_TYPES: - return processors.to_decimal_processor_factory( - decimal.Decimal, self._effective_decimal_return_scale - ) - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - # pg8000 returns Decimal natively for 1700 - return None - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - else: - if coltype in _FLOAT_TYPES: - # pg8000 returns float natively for 701 - return None - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - return processors.to_float - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - - -class AsyncpgFloat(AsyncpgNumeric, sqltypes.Float): - __visit_name__ = "float" - render_bind_cast = True - - -class AsyncpgREGCLASS(REGCLASS): - render_bind_cast = True - - -class AsyncpgOID(OID): - render_bind_cast = True - - -class AsyncpgCHAR(sqltypes.CHAR): - render_bind_cast = True - - -class _AsyncpgRange(ranges.AbstractSingleRangeImpl): - def bind_processor(self, dialect): - asyncpg_Range = dialect.dbapi.asyncpg.Range - - def to_range(value): - if isinstance(value, ranges.Range): - value = asyncpg_Range( - value.lower, - value.upper, - lower_inc=value.bounds[0] == "[", - upper_inc=value.bounds[1] == "]", - empty=value.empty, - ) - return value - - return to_range - - def result_processor(self, dialect, coltype): - def to_range(value): - if value is not None: - empty = value.isempty - value = ranges.Range( - value.lower, - value.upper, - bounds=f"{'[' if empty or value.lower_inc else '('}" # type: ignore # noqa: E501 - f"{']' if not empty and value.upper_inc else ')'}", - empty=empty, - ) - return value - - return to_range - - -class _AsyncpgMultiRange(ranges.AbstractMultiRangeImpl): - def bind_processor(self, dialect): - asyncpg_Range = dialect.dbapi.asyncpg.Range - - NoneType = type(None) - - def to_range(value): - if isinstance(value, (str, NoneType)): - return value - - def to_range(value): - if isinstance(value, ranges.Range): - value = asyncpg_Range( - value.lower, - value.upper, - lower_inc=value.bounds[0] == "[", - upper_inc=value.bounds[1] == "]", - empty=value.empty, - ) - return value - - return [to_range(element) for element in value] - - return to_range - - def result_processor(self, dialect, coltype): - def to_range_array(value): - def to_range(rvalue): - if rvalue is not None: - empty = rvalue.isempty - rvalue = ranges.Range( - rvalue.lower, - rvalue.upper, - bounds=f"{'[' if empty or rvalue.lower_inc else '('}" # type: ignore # noqa: E501 - f"{']' if not empty and rvalue.upper_inc else ')'}", - empty=empty, - ) - return rvalue - - if value is not None: - value = ranges.MultiRange(to_range(elem) for elem in value) - - return value - - return to_range_array - - -class PGExecutionContext_asyncpg(PGExecutionContext): - def handle_dbapi_exception(self, e): - if isinstance( - e, - ( - self.dialect.dbapi.InvalidCachedStatementError, - self.dialect.dbapi.InternalServerError, - ), - ): - self.dialect._invalidate_schema_cache() - - def pre_exec(self): - if self.isddl: - self.dialect._invalidate_schema_cache() - - self.cursor._invalidate_schema_cache_asof = ( - self.dialect._invalidate_schema_cache_asof - ) - - if not self.compiled: - return - - def create_server_side_cursor(self): - return self._dbapi_connection.cursor(server_side=True) - - -class PGCompiler_asyncpg(PGCompiler): - pass - - -class PGIdentifierPreparer_asyncpg(PGIdentifierPreparer): - pass - - -class AsyncAdapt_asyncpg_cursor: - __slots__ = ( - "_adapt_connection", - "_connection", - "_rows", - "description", - "arraysize", - "rowcount", - "_cursor", - "_invalidate_schema_cache_asof", - ) - - server_side = False - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self._rows = [] - self._cursor = None - self.description = None - self.arraysize = 1 - self.rowcount = -1 - self._invalidate_schema_cache_asof = 0 - - def close(self): - self._rows[:] = [] - - def _handle_exception(self, error): - self._adapt_connection._handle_exception(error) - - async def _prepare_and_execute(self, operation, parameters): - adapt_connection = self._adapt_connection - - async with adapt_connection._execute_mutex: - if not adapt_connection._started: - await adapt_connection._start_transaction() - - if parameters is None: - parameters = () - - try: - prepared_stmt, attributes = await adapt_connection._prepare( - operation, self._invalidate_schema_cache_asof - ) - - if attributes: - self.description = [ - ( - attr.name, - attr.type.oid, - None, - None, - None, - None, - None, - ) - for attr in attributes - ] - else: - self.description = None - - if self.server_side: - self._cursor = await prepared_stmt.cursor(*parameters) - self.rowcount = -1 - else: - self._rows = await prepared_stmt.fetch(*parameters) - status = prepared_stmt.get_statusmsg() - - reg = re.match( - r"(?:SELECT|UPDATE|DELETE|INSERT \d+) (\d+)", status - ) - if reg: - self.rowcount = int(reg.group(1)) - else: - self.rowcount = -1 - - except Exception as error: - self._handle_exception(error) - - async def _executemany(self, operation, seq_of_parameters): - adapt_connection = self._adapt_connection - - self.description = None - async with adapt_connection._execute_mutex: - await adapt_connection._check_type_cache_invalidation( - self._invalidate_schema_cache_asof - ) - - if not adapt_connection._started: - await adapt_connection._start_transaction() - - try: - return await self._connection.executemany( - operation, seq_of_parameters - ) - except Exception as error: - self._handle_exception(error) - - def execute(self, operation, parameters=None): - self._adapt_connection.await_( - self._prepare_and_execute(operation, parameters) - ) - - def executemany(self, operation, seq_of_parameters): - return self._adapt_connection.await_( - self._executemany(operation, seq_of_parameters) - ) - - def setinputsizes(self, *inputsizes): - raise NotImplementedError() - - def __iter__(self): - while self._rows: - yield self._rows.pop(0) - - def fetchone(self): - if self._rows: - return self._rows.pop(0) - else: - return None - - def fetchmany(self, size=None): - if size is None: - size = self.arraysize - - retval = self._rows[0:size] - self._rows[:] = self._rows[size:] - return retval - - def fetchall(self): - retval = self._rows[:] - self._rows[:] = [] - return retval - - -class AsyncAdapt_asyncpg_ss_cursor(AsyncAdapt_asyncpg_cursor): - server_side = True - __slots__ = ("_rowbuffer",) - - def __init__(self, adapt_connection): - super().__init__(adapt_connection) - self._rowbuffer = None - - def close(self): - self._cursor = None - self._rowbuffer = None - - def _buffer_rows(self): - new_rows = self._adapt_connection.await_(self._cursor.fetch(50)) - self._rowbuffer = collections.deque(new_rows) - - def __aiter__(self): - return self - - async def __anext__(self): - if not self._rowbuffer: - self._buffer_rows() - - while True: - while self._rowbuffer: - yield self._rowbuffer.popleft() - - self._buffer_rows() - if not self._rowbuffer: - break - - def fetchone(self): - if not self._rowbuffer: - self._buffer_rows() - if not self._rowbuffer: - return None - return self._rowbuffer.popleft() - - def fetchmany(self, size=None): - if size is None: - return self.fetchall() - - if not self._rowbuffer: - self._buffer_rows() - - buf = list(self._rowbuffer) - lb = len(buf) - if size > lb: - buf.extend( - self._adapt_connection.await_(self._cursor.fetch(size - lb)) - ) - - result = buf[0:size] - self._rowbuffer = collections.deque(buf[size:]) - return result - - def fetchall(self): - ret = list(self._rowbuffer) + list( - self._adapt_connection.await_(self._all()) - ) - self._rowbuffer.clear() - return ret - - async def _all(self): - rows = [] - - # TODO: looks like we have to hand-roll some kind of batching here. - # hardcoding for the moment but this should be improved. - while True: - batch = await self._cursor.fetch(1000) - if batch: - rows.extend(batch) - continue - else: - break - return rows - - def executemany(self, operation, seq_of_parameters): - raise NotImplementedError( - "server side cursor doesn't support executemany yet" - ) - - -class AsyncAdapt_asyncpg_connection(AdaptedConnection): - __slots__ = ( - "dbapi", - "isolation_level", - "_isolation_setting", - "readonly", - "deferrable", - "_transaction", - "_started", - "_prepared_statement_cache", - "_prepared_statement_name_func", - "_invalidate_schema_cache_asof", - "_execute_mutex", - ) - - await_ = staticmethod(await_only) - - def __init__( - self, - dbapi, - connection, - prepared_statement_cache_size=100, - prepared_statement_name_func=None, - ): - self.dbapi = dbapi - self._connection = connection - self.isolation_level = self._isolation_setting = "read_committed" - self.readonly = False - self.deferrable = False - self._transaction = None - self._started = False - self._invalidate_schema_cache_asof = time.time() - self._execute_mutex = asyncio.Lock() - - if prepared_statement_cache_size: - self._prepared_statement_cache = util.LRUCache( - prepared_statement_cache_size - ) - else: - self._prepared_statement_cache = None - - if prepared_statement_name_func: - self._prepared_statement_name_func = prepared_statement_name_func - else: - self._prepared_statement_name_func = self._default_name_func - - async def _check_type_cache_invalidation(self, invalidate_timestamp): - if invalidate_timestamp > self._invalidate_schema_cache_asof: - await self._connection.reload_schema_state() - self._invalidate_schema_cache_asof = invalidate_timestamp - - async def _prepare(self, operation, invalidate_timestamp): - await self._check_type_cache_invalidation(invalidate_timestamp) - - cache = self._prepared_statement_cache - if cache is None: - prepared_stmt = await self._connection.prepare( - operation, name=self._prepared_statement_name_func() - ) - attributes = prepared_stmt.get_attributes() - return prepared_stmt, attributes - - # asyncpg uses a type cache for the "attributes" which seems to go - # stale independently of the PreparedStatement itself, so place that - # collection in the cache as well. - if operation in cache: - prepared_stmt, attributes, cached_timestamp = cache[operation] - - # preparedstatements themselves also go stale for certain DDL - # changes such as size of a VARCHAR changing, so there is also - # a cross-connection invalidation timestamp - if cached_timestamp > invalidate_timestamp: - return prepared_stmt, attributes - - prepared_stmt = await self._connection.prepare( - operation, name=self._prepared_statement_name_func() - ) - attributes = prepared_stmt.get_attributes() - cache[operation] = (prepared_stmt, attributes, time.time()) - - return prepared_stmt, attributes - - def _handle_exception(self, error): - if self._connection.is_closed(): - self._transaction = None - self._started = False - - if not isinstance(error, AsyncAdapt_asyncpg_dbapi.Error): - exception_mapping = self.dbapi._asyncpg_error_translate - - for super_ in type(error).__mro__: - if super_ in exception_mapping: - translated_error = exception_mapping[super_]( - "%s: %s" % (type(error), error) - ) - translated_error.pgcode = translated_error.sqlstate = ( - getattr(error, "sqlstate", None) - ) - raise translated_error from error - else: - raise error - else: - raise error - - @property - def autocommit(self): - return self.isolation_level == "autocommit" - - @autocommit.setter - def autocommit(self, value): - if value: - self.isolation_level = "autocommit" - else: - self.isolation_level = self._isolation_setting - - def ping(self): - try: - _ = self.await_(self._async_ping()) - except Exception as error: - self._handle_exception(error) - - async def _async_ping(self): - if self._transaction is None and self.isolation_level != "autocommit": - # create a tranasction explicitly to support pgbouncer - # transaction mode. See #10226 - tr = self._connection.transaction() - await tr.start() - try: - await self._connection.fetchrow(";") - finally: - await tr.rollback() - else: - await self._connection.fetchrow(";") - - def set_isolation_level(self, level): - if self._started: - self.rollback() - self.isolation_level = self._isolation_setting = level - - async def _start_transaction(self): - if self.isolation_level == "autocommit": - return - - try: - self._transaction = self._connection.transaction( - isolation=self.isolation_level, - readonly=self.readonly, - deferrable=self.deferrable, - ) - await self._transaction.start() - except Exception as error: - self._handle_exception(error) - else: - self._started = True - - def cursor(self, server_side=False): - if server_side: - return AsyncAdapt_asyncpg_ss_cursor(self) - else: - return AsyncAdapt_asyncpg_cursor(self) - - def rollback(self): - if self._started: - try: - self.await_(self._transaction.rollback()) - except Exception as error: - self._handle_exception(error) - finally: - self._transaction = None - self._started = False - - def commit(self): - if self._started: - try: - self.await_(self._transaction.commit()) - except Exception as error: - self._handle_exception(error) - finally: - self._transaction = None - self._started = False - - def close(self): - self.rollback() - - self.await_(self._connection.close()) - - def terminate(self): - if util.concurrency.in_greenlet(): - # in a greenlet; this is the connection was invalidated - # case. - try: - # try to gracefully close; see #10717 - # timeout added in asyncpg 0.14.0 December 2017 - self.await_(self._connection.close(timeout=2)) - except ( - asyncio.TimeoutError, - OSError, - self.dbapi.asyncpg.PostgresError, - ): - # in the case where we are recycling an old connection - # that may have already been disconnected, close() will - # fail with the above timeout. in this case, terminate - # the connection without any further waiting. - # see issue #8419 - self._connection.terminate() - else: - # not in a greenlet; this is the gc cleanup case - self._connection.terminate() - self._started = False - - @staticmethod - def _default_name_func(): - return None - - -class AsyncAdaptFallback_asyncpg_connection(AsyncAdapt_asyncpg_connection): - __slots__ = () - - await_ = staticmethod(await_fallback) - - -class AsyncAdapt_asyncpg_dbapi: - def __init__(self, asyncpg): - self.asyncpg = asyncpg - self.paramstyle = "numeric_dollar" - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - creator_fn = kw.pop("async_creator_fn", self.asyncpg.connect) - prepared_statement_cache_size = kw.pop( - "prepared_statement_cache_size", 100 - ) - prepared_statement_name_func = kw.pop( - "prepared_statement_name_func", None - ) - - if util.asbool(async_fallback): - return AsyncAdaptFallback_asyncpg_connection( - self, - await_fallback(creator_fn(*arg, **kw)), - prepared_statement_cache_size=prepared_statement_cache_size, - prepared_statement_name_func=prepared_statement_name_func, - ) - else: - return AsyncAdapt_asyncpg_connection( - self, - await_only(creator_fn(*arg, **kw)), - prepared_statement_cache_size=prepared_statement_cache_size, - prepared_statement_name_func=prepared_statement_name_func, - ) - - class Error(Exception): - pass - - class Warning(Exception): # noqa - pass - - class InterfaceError(Error): - pass - - class DatabaseError(Error): - pass - - class InternalError(DatabaseError): - pass - - class OperationalError(DatabaseError): - pass - - class ProgrammingError(DatabaseError): - pass - - class IntegrityError(DatabaseError): - pass - - class DataError(DatabaseError): - pass - - class NotSupportedError(DatabaseError): - pass - - class InternalServerError(InternalError): - pass - - class InvalidCachedStatementError(NotSupportedError): - def __init__(self, message): - super().__init__( - message + " (SQLAlchemy asyncpg dialect will now invalidate " - "all prepared caches in response to this exception)", - ) - - # pep-249 datatype placeholders. As of SQLAlchemy 2.0 these aren't - # used, however the test suite looks for these in a few cases. - STRING = util.symbol("STRING") - NUMBER = util.symbol("NUMBER") - DATETIME = util.symbol("DATETIME") - - @util.memoized_property - def _asyncpg_error_translate(self): - import asyncpg - - return { - asyncpg.exceptions.IntegrityConstraintViolationError: self.IntegrityError, # noqa: E501 - asyncpg.exceptions.PostgresError: self.Error, - asyncpg.exceptions.SyntaxOrAccessError: self.ProgrammingError, - asyncpg.exceptions.InterfaceError: self.InterfaceError, - asyncpg.exceptions.InvalidCachedStatementError: self.InvalidCachedStatementError, # noqa: E501 - asyncpg.exceptions.InternalServerError: self.InternalServerError, - } - - def Binary(self, value): - return value - - -class PGDialect_asyncpg(PGDialect): - driver = "asyncpg" - supports_statement_cache = True - - supports_server_side_cursors = True - - render_bind_cast = True - has_terminate = True - - default_paramstyle = "numeric_dollar" - supports_sane_multi_rowcount = False - execution_ctx_cls = PGExecutionContext_asyncpg - statement_compiler = PGCompiler_asyncpg - preparer = PGIdentifierPreparer_asyncpg - - colspecs = util.update_copy( - PGDialect.colspecs, - { - sqltypes.String: AsyncpgString, - sqltypes.ARRAY: AsyncpgARRAY, - BIT: AsyncpgBit, - CITEXT: CITEXT, - REGCONFIG: AsyncpgREGCONFIG, - sqltypes.Time: AsyncpgTime, - sqltypes.Date: AsyncpgDate, - sqltypes.DateTime: AsyncpgDateTime, - sqltypes.Interval: AsyncPgInterval, - INTERVAL: AsyncPgInterval, - sqltypes.Boolean: AsyncpgBoolean, - sqltypes.Integer: AsyncpgInteger, - sqltypes.BigInteger: AsyncpgBigInteger, - sqltypes.Numeric: AsyncpgNumeric, - sqltypes.Float: AsyncpgFloat, - sqltypes.JSON: AsyncpgJSON, - sqltypes.LargeBinary: AsyncpgByteA, - json.JSONB: AsyncpgJSONB, - sqltypes.JSON.JSONPathType: AsyncpgJSONPathType, - sqltypes.JSON.JSONIndexType: AsyncpgJSONIndexType, - sqltypes.JSON.JSONIntIndexType: AsyncpgJSONIntIndexType, - sqltypes.JSON.JSONStrIndexType: AsyncpgJSONStrIndexType, - sqltypes.Enum: AsyncPgEnum, - OID: AsyncpgOID, - REGCLASS: AsyncpgREGCLASS, - sqltypes.CHAR: AsyncpgCHAR, - ranges.AbstractSingleRange: _AsyncpgRange, - ranges.AbstractMultiRange: _AsyncpgMultiRange, - }, - ) - is_async = True - _invalidate_schema_cache_asof = 0 - - def _invalidate_schema_cache(self): - self._invalidate_schema_cache_asof = time.time() - - @util.memoized_property - def _dbapi_version(self): - if self.dbapi and hasattr(self.dbapi, "__version__"): - return tuple( - [ - int(x) - for x in re.findall( - r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__ - ) - ] - ) - else: - return (99, 99, 99) - - @classmethod - def import_dbapi(cls): - return AsyncAdapt_asyncpg_dbapi(__import__("asyncpg")) - - @util.memoized_property - def _isolation_lookup(self): - return { - "AUTOCOMMIT": "autocommit", - "READ COMMITTED": "read_committed", - "REPEATABLE READ": "repeatable_read", - "SERIALIZABLE": "serializable", - } - - def get_isolation_level_values(self, dbapi_connection): - return list(self._isolation_lookup) - - def set_isolation_level(self, dbapi_connection, level): - dbapi_connection.set_isolation_level(self._isolation_lookup[level]) - - def set_readonly(self, connection, value): - connection.readonly = value - - def get_readonly(self, connection): - return connection.readonly - - def set_deferrable(self, connection, value): - connection.deferrable = value - - def get_deferrable(self, connection): - return connection.deferrable - - def do_terminate(self, dbapi_connection) -> None: - dbapi_connection.terminate() - - def create_connect_args(self, url): - opts = url.translate_connect_args(username="user") - multihosts, multiports = self._split_multihost_from_url(url) - - opts.update(url.query) - - if multihosts: - assert multiports - if len(multihosts) == 1: - opts["host"] = multihosts[0] - if multiports[0] is not None: - opts["port"] = multiports[0] - elif not all(multihosts): - raise exc.ArgumentError( - "All hosts are required to be present" - " for asyncpg multiple host URL" - ) - elif not all(multiports): - raise exc.ArgumentError( - "All ports are required to be present" - " for asyncpg multiple host URL" - ) - else: - opts["host"] = list(multihosts) - opts["port"] = list(multiports) - else: - util.coerce_kw_type(opts, "port", int) - util.coerce_kw_type(opts, "prepared_statement_cache_size", int) - return ([], opts) - - def do_ping(self, dbapi_connection): - dbapi_connection.ping() - return True - - @classmethod - def get_pool_class(cls, url): - async_fallback = url.query.get("async_fallback", False) - - if util.asbool(async_fallback): - return pool.FallbackAsyncAdaptedQueuePool - else: - return pool.AsyncAdaptedQueuePool - - def is_disconnect(self, e, connection, cursor): - if connection: - return connection._connection.is_closed() - else: - return isinstance( - e, self.dbapi.InterfaceError - ) and "connection is closed" in str(e) - - async def setup_asyncpg_json_codec(self, conn): - """set up JSON codec for asyncpg. - - This occurs for all new connections and - can be overridden by third party dialects. - - .. versionadded:: 1.4.27 - - """ - - asyncpg_connection = conn._connection - deserializer = self._json_deserializer or _py_json.loads - - def _json_decoder(bin_value): - return deserializer(bin_value.decode()) - - await asyncpg_connection.set_type_codec( - "json", - encoder=str.encode, - decoder=_json_decoder, - schema="pg_catalog", - format="binary", - ) - - async def setup_asyncpg_jsonb_codec(self, conn): - """set up JSONB codec for asyncpg. - - This occurs for all new connections and - can be overridden by third party dialects. - - .. versionadded:: 1.4.27 - - """ - - asyncpg_connection = conn._connection - deserializer = self._json_deserializer or _py_json.loads - - def _jsonb_encoder(str_value): - # \x01 is the prefix for jsonb used by PostgreSQL. - # asyncpg requires it when format='binary' - return b"\x01" + str_value.encode() - - deserializer = self._json_deserializer or _py_json.loads - - def _jsonb_decoder(bin_value): - # the byte is the \x01 prefix for jsonb used by PostgreSQL. - # asyncpg returns it when format='binary' - return deserializer(bin_value[1:].decode()) - - await asyncpg_connection.set_type_codec( - "jsonb", - encoder=_jsonb_encoder, - decoder=_jsonb_decoder, - schema="pg_catalog", - format="binary", - ) - - async def _disable_asyncpg_inet_codecs(self, conn): - asyncpg_connection = conn._connection - - await asyncpg_connection.set_type_codec( - "inet", - encoder=lambda s: s, - decoder=lambda s: s, - schema="pg_catalog", - format="text", - ) - - await asyncpg_connection.set_type_codec( - "cidr", - encoder=lambda s: s, - decoder=lambda s: s, - schema="pg_catalog", - format="text", - ) - - def on_connect(self): - """on_connect for asyncpg - - A major component of this for asyncpg is to set up type decoders at the - asyncpg level. - - See https://github.com/MagicStack/asyncpg/issues/623 for - notes on JSON/JSONB implementation. - - """ - - super_connect = super().on_connect() - - def connect(conn): - conn.await_(self.setup_asyncpg_json_codec(conn)) - conn.await_(self.setup_asyncpg_jsonb_codec(conn)) - - if self._native_inet_types is False: - conn.await_(self._disable_asyncpg_inet_codecs(conn)) - if super_connect is not None: - super_connect(conn) - - return connect - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = PGDialect_asyncpg diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/base.py deleted file mode 100644 index 4ab3ca2..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/base.py +++ /dev/null @@ -1,5007 +0,0 @@ -# dialects/postgresql/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql - :name: PostgreSQL - :full_support: 12, 13, 14, 15 - :normal_support: 9.6+ - :best_effort: 9+ - -.. _postgresql_sequences: - -Sequences/SERIAL/IDENTITY -------------------------- - -PostgreSQL supports sequences, and SQLAlchemy uses these as the default means -of creating new primary key values for integer-based primary key columns. When -creating tables, SQLAlchemy will issue the ``SERIAL`` datatype for -integer-based primary key columns, which generates a sequence and server side -default corresponding to the column. - -To specify a specific named sequence to be used for primary key generation, -use the :func:`~sqlalchemy.schema.Sequence` construct:: - - Table( - "sometable", - metadata, - Column( - "id", Integer, Sequence("some_id_seq", start=1), primary_key=True - ) - ) - -When SQLAlchemy issues a single INSERT statement, to fulfill the contract of -having the "last insert identifier" available, a RETURNING clause is added to -the INSERT statement which specifies the primary key columns should be -returned after the statement completes. The RETURNING functionality only takes -place if PostgreSQL 8.2 or later is in use. As a fallback approach, the -sequence, whether specified explicitly or implicitly via ``SERIAL``, is -executed independently beforehand, the returned value to be used in the -subsequent insert. Note that when an -:func:`~sqlalchemy.sql.expression.insert()` construct is executed using -"executemany" semantics, the "last inserted identifier" functionality does not -apply; no RETURNING clause is emitted nor is the sequence pre-executed in this -case. - - -PostgreSQL 10 and above IDENTITY columns -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -PostgreSQL 10 and above have a new IDENTITY feature that supersedes the use -of SERIAL. The :class:`_schema.Identity` construct in a -:class:`_schema.Column` can be used to control its behavior:: - - from sqlalchemy import Table, Column, MetaData, Integer, Computed - - metadata = MetaData() - - data = Table( - "data", - metadata, - Column( - 'id', Integer, Identity(start=42, cycle=True), primary_key=True - ), - Column('data', String) - ) - -The CREATE TABLE for the above :class:`_schema.Table` object would be: - -.. sourcecode:: sql - - CREATE TABLE data ( - id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 42 CYCLE), - data VARCHAR, - PRIMARY KEY (id) - ) - -.. versionchanged:: 1.4 Added :class:`_schema.Identity` construct - in a :class:`_schema.Column` to specify the option of an autoincrementing - column. - -.. note:: - - Previous versions of SQLAlchemy did not have built-in support for rendering - of IDENTITY, and could use the following compilation hook to replace - occurrences of SERIAL with IDENTITY:: - - from sqlalchemy.schema import CreateColumn - from sqlalchemy.ext.compiler import compiles - - - @compiles(CreateColumn, 'postgresql') - def use_identity(element, compiler, **kw): - text = compiler.visit_create_column(element, **kw) - text = text.replace( - "SERIAL", "INT GENERATED BY DEFAULT AS IDENTITY" - ) - return text - - Using the above, a table such as:: - - t = Table( - 't', m, - Column('id', Integer, primary_key=True), - Column('data', String) - ) - - Will generate on the backing database as:: - - CREATE TABLE t ( - id INT GENERATED BY DEFAULT AS IDENTITY, - data VARCHAR, - PRIMARY KEY (id) - ) - -.. _postgresql_ss_cursors: - -Server Side Cursors -------------------- - -Server-side cursor support is available for the psycopg2, asyncpg -dialects and may also be available in others. - -Server side cursors are enabled on a per-statement basis by using the -:paramref:`.Connection.execution_options.stream_results` connection execution -option:: - - with engine.connect() as conn: - result = conn.execution_options(stream_results=True).execute(text("select * from table")) - -Note that some kinds of SQL statements may not be supported with -server side cursors; generally, only SQL statements that return rows should be -used with this option. - -.. deprecated:: 1.4 The dialect-level server_side_cursors flag is deprecated - and will be removed in a future release. Please use the - :paramref:`_engine.Connection.stream_results` execution option for - unbuffered cursor support. - -.. seealso:: - - :ref:`engine_stream_results` - -.. _postgresql_isolation_level: - -Transaction Isolation Level ---------------------------- - -Most SQLAlchemy dialects support setting of transaction isolation level -using the :paramref:`_sa.create_engine.isolation_level` parameter -at the :func:`_sa.create_engine` level, and at the :class:`_engine.Connection` -level via the :paramref:`.Connection.execution_options.isolation_level` -parameter. - -For PostgreSQL dialects, this feature works either by making use of the -DBAPI-specific features, such as psycopg2's isolation level flags which will -embed the isolation level setting inline with the ``"BEGIN"`` statement, or for -DBAPIs with no direct support by emitting ``SET SESSION CHARACTERISTICS AS -TRANSACTION ISOLATION LEVEL `` ahead of the ``"BEGIN"`` statement -emitted by the DBAPI. For the special AUTOCOMMIT isolation level, -DBAPI-specific techniques are used which is typically an ``.autocommit`` -flag on the DBAPI connection object. - -To set isolation level using :func:`_sa.create_engine`:: - - engine = create_engine( - "postgresql+pg8000://scott:tiger@localhost/test", - isolation_level = "REPEATABLE READ" - ) - -To set using per-connection execution options:: - - with engine.connect() as conn: - conn = conn.execution_options( - isolation_level="REPEATABLE READ" - ) - with conn.begin(): - # ... work with transaction - -There are also more options for isolation level configurations, such as -"sub-engine" objects linked to a main :class:`_engine.Engine` which each apply -different isolation level settings. See the discussion at -:ref:`dbapi_autocommit` for background. - -Valid values for ``isolation_level`` on most PostgreSQL dialects include: - -* ``READ COMMITTED`` -* ``READ UNCOMMITTED`` -* ``REPEATABLE READ`` -* ``SERIALIZABLE`` -* ``AUTOCOMMIT`` - -.. seealso:: - - :ref:`dbapi_autocommit` - - :ref:`postgresql_readonly_deferrable` - - :ref:`psycopg2_isolation_level` - - :ref:`pg8000_isolation_level` - -.. _postgresql_readonly_deferrable: - -Setting READ ONLY / DEFERRABLE ------------------------------- - -Most PostgreSQL dialects support setting the "READ ONLY" and "DEFERRABLE" -characteristics of the transaction, which is in addition to the isolation level -setting. These two attributes can be established either in conjunction with or -independently of the isolation level by passing the ``postgresql_readonly`` and -``postgresql_deferrable`` flags with -:meth:`_engine.Connection.execution_options`. The example below illustrates -passing the ``"SERIALIZABLE"`` isolation level at the same time as setting -"READ ONLY" and "DEFERRABLE":: - - with engine.connect() as conn: - conn = conn.execution_options( - isolation_level="SERIALIZABLE", - postgresql_readonly=True, - postgresql_deferrable=True - ) - with conn.begin(): - # ... work with transaction - -Note that some DBAPIs such as asyncpg only support "readonly" with -SERIALIZABLE isolation. - -.. versionadded:: 1.4 added support for the ``postgresql_readonly`` - and ``postgresql_deferrable`` execution options. - -.. _postgresql_reset_on_return: - -Temporary Table / Resource Reset for Connection Pooling -------------------------------------------------------- - -The :class:`.QueuePool` connection pool implementation used -by the SQLAlchemy :class:`.Engine` object includes -:ref:`reset on return ` behavior that will invoke -the DBAPI ``.rollback()`` method when connections are returned to the pool. -While this rollback will clear out the immediate state used by the previous -transaction, it does not cover a wider range of session-level state, including -temporary tables as well as other server state such as prepared statement -handles and statement caches. The PostgreSQL database includes a variety -of commands which may be used to reset this state, including -``DISCARD``, ``RESET``, ``DEALLOCATE``, and ``UNLISTEN``. - - -To install -one or more of these commands as the means of performing reset-on-return, -the :meth:`.PoolEvents.reset` event hook may be used, as demonstrated -in the example below. The implementation -will end transactions in progress as well as discard temporary tables -using the ``CLOSE``, ``RESET`` and ``DISCARD`` commands; see the PostgreSQL -documentation for background on what each of these statements do. - -The :paramref:`_sa.create_engine.pool_reset_on_return` parameter -is set to ``None`` so that the custom scheme can replace the default behavior -completely. The custom hook implementation calls ``.rollback()`` in any case, -as it's usually important that the DBAPI's own tracking of commit/rollback -will remain consistent with the state of the transaction:: - - - from sqlalchemy import create_engine - from sqlalchemy import event - - postgresql_engine = create_engine( - "postgresql+pyscopg2://scott:tiger@hostname/dbname", - - # disable default reset-on-return scheme - pool_reset_on_return=None, - ) - - - @event.listens_for(postgresql_engine, "reset") - def _reset_postgresql(dbapi_connection, connection_record, reset_state): - if not reset_state.terminate_only: - dbapi_connection.execute("CLOSE ALL") - dbapi_connection.execute("RESET ALL") - dbapi_connection.execute("DISCARD TEMP") - - # so that the DBAPI itself knows that the connection has been - # reset - dbapi_connection.rollback() - -.. versionchanged:: 2.0.0b3 Added additional state arguments to - the :meth:`.PoolEvents.reset` event and additionally ensured the event - is invoked for all "reset" occurrences, so that it's appropriate - as a place for custom "reset" handlers. Previous schemes which - use the :meth:`.PoolEvents.checkin` handler remain usable as well. - -.. seealso:: - - :ref:`pool_reset_on_return` - in the :ref:`pooling_toplevel` documentation - -.. _postgresql_alternate_search_path: - -Setting Alternate Search Paths on Connect ------------------------------------------- - -The PostgreSQL ``search_path`` variable refers to the list of schema names -that will be implicitly referenced when a particular table or other -object is referenced in a SQL statement. As detailed in the next section -:ref:`postgresql_schema_reflection`, SQLAlchemy is generally organized around -the concept of keeping this variable at its default value of ``public``, -however, in order to have it set to any arbitrary name or names when connections -are used automatically, the "SET SESSION search_path" command may be invoked -for all connections in a pool using the following event handler, as discussed -at :ref:`schema_set_default_connections`:: - - from sqlalchemy import event - from sqlalchemy import create_engine - - engine = create_engine("postgresql+psycopg2://scott:tiger@host/dbname") - - @event.listens_for(engine, "connect", insert=True) - def set_search_path(dbapi_connection, connection_record): - existing_autocommit = dbapi_connection.autocommit - dbapi_connection.autocommit = True - cursor = dbapi_connection.cursor() - cursor.execute("SET SESSION search_path='%s'" % schema_name) - cursor.close() - dbapi_connection.autocommit = existing_autocommit - -The reason the recipe is complicated by use of the ``.autocommit`` DBAPI -attribute is so that when the ``SET SESSION search_path`` directive is invoked, -it is invoked outside of the scope of any transaction and therefore will not -be reverted when the DBAPI connection has a rollback. - -.. seealso:: - - :ref:`schema_set_default_connections` - in the :ref:`metadata_toplevel` documentation - - - - -.. _postgresql_schema_reflection: - -Remote-Schema Table Introspection and PostgreSQL search_path ------------------------------------------------------------- - -.. admonition:: Section Best Practices Summarized - - keep the ``search_path`` variable set to its default of ``public``, without - any other schema names. Ensure the username used to connect **does not** - match remote schemas, or ensure the ``"$user"`` token is **removed** from - ``search_path``. For other schema names, name these explicitly - within :class:`_schema.Table` definitions. Alternatively, the - ``postgresql_ignore_search_path`` option will cause all reflected - :class:`_schema.Table` objects to have a :attr:`_schema.Table.schema` - attribute set up. - -The PostgreSQL dialect can reflect tables from any schema, as outlined in -:ref:`metadata_reflection_schemas`. - -In all cases, the first thing SQLAlchemy does when reflecting tables is -to **determine the default schema for the current database connection**. -It does this using the PostgreSQL ``current_schema()`` -function, illustated below using a PostgreSQL client session (i.e. using -the ``psql`` tool):: - - test=> select current_schema(); - current_schema - ---------------- - public - (1 row) - -Above we see that on a plain install of PostgreSQL, the default schema name -is the name ``public``. - -However, if your database username **matches the name of a schema**, PostgreSQL's -default is to then **use that name as the default schema**. Below, we log in -using the username ``scott``. When we create a schema named ``scott``, **it -implicitly changes the default schema**:: - - test=> select current_schema(); - current_schema - ---------------- - public - (1 row) - - test=> create schema scott; - CREATE SCHEMA - test=> select current_schema(); - current_schema - ---------------- - scott - (1 row) - -The behavior of ``current_schema()`` is derived from the -`PostgreSQL search path -`_ -variable ``search_path``, which in modern PostgreSQL versions defaults to this:: - - test=> show search_path; - search_path - ----------------- - "$user", public - (1 row) - -Where above, the ``"$user"`` variable will inject the current username as the -default schema, if one exists. Otherwise, ``public`` is used. - -When a :class:`_schema.Table` object is reflected, if it is present in the -schema indicated by the ``current_schema()`` function, **the schema name assigned -to the ".schema" attribute of the Table is the Python "None" value**. Otherwise, the -".schema" attribute will be assigned the string name of that schema. - -With regards to tables which these :class:`_schema.Table` -objects refer to via foreign key constraint, a decision must be made as to how -the ``.schema`` is represented in those remote tables, in the case where that -remote schema name is also a member of the current ``search_path``. - -By default, the PostgreSQL dialect mimics the behavior encouraged by -PostgreSQL's own ``pg_get_constraintdef()`` builtin procedure. This function -returns a sample definition for a particular foreign key constraint, -omitting the referenced schema name from that definition when the name is -also in the PostgreSQL schema search path. The interaction below -illustrates this behavior:: - - test=> CREATE TABLE test_schema.referred(id INTEGER PRIMARY KEY); - CREATE TABLE - test=> CREATE TABLE referring( - test(> id INTEGER PRIMARY KEY, - test(> referred_id INTEGER REFERENCES test_schema.referred(id)); - CREATE TABLE - test=> SET search_path TO public, test_schema; - test=> SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM - test-> pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n - test-> ON n.oid = c.relnamespace - test-> JOIN pg_catalog.pg_constraint r ON c.oid = r.conrelid - test-> WHERE c.relname='referring' AND r.contype = 'f' - test-> ; - pg_get_constraintdef - --------------------------------------------------- - FOREIGN KEY (referred_id) REFERENCES referred(id) - (1 row) - -Above, we created a table ``referred`` as a member of the remote schema -``test_schema``, however when we added ``test_schema`` to the -PG ``search_path`` and then asked ``pg_get_constraintdef()`` for the -``FOREIGN KEY`` syntax, ``test_schema`` was not included in the output of -the function. - -On the other hand, if we set the search path back to the typical default -of ``public``:: - - test=> SET search_path TO public; - SET - -The same query against ``pg_get_constraintdef()`` now returns the fully -schema-qualified name for us:: - - test=> SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM - test-> pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n - test-> ON n.oid = c.relnamespace - test-> JOIN pg_catalog.pg_constraint r ON c.oid = r.conrelid - test-> WHERE c.relname='referring' AND r.contype = 'f'; - pg_get_constraintdef - --------------------------------------------------------------- - FOREIGN KEY (referred_id) REFERENCES test_schema.referred(id) - (1 row) - -SQLAlchemy will by default use the return value of ``pg_get_constraintdef()`` -in order to determine the remote schema name. That is, if our ``search_path`` -were set to include ``test_schema``, and we invoked a table -reflection process as follows:: - - >>> from sqlalchemy import Table, MetaData, create_engine, text - >>> engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test") - >>> with engine.connect() as conn: - ... conn.execute(text("SET search_path TO test_schema, public")) - ... metadata_obj = MetaData() - ... referring = Table('referring', metadata_obj, - ... autoload_with=conn) - ... - - -The above process would deliver to the :attr:`_schema.MetaData.tables` -collection -``referred`` table named **without** the schema:: - - >>> metadata_obj.tables['referred'].schema is None - True - -To alter the behavior of reflection such that the referred schema is -maintained regardless of the ``search_path`` setting, use the -``postgresql_ignore_search_path`` option, which can be specified as a -dialect-specific argument to both :class:`_schema.Table` as well as -:meth:`_schema.MetaData.reflect`:: - - >>> with engine.connect() as conn: - ... conn.execute(text("SET search_path TO test_schema, public")) - ... metadata_obj = MetaData() - ... referring = Table('referring', metadata_obj, - ... autoload_with=conn, - ... postgresql_ignore_search_path=True) - ... - - -We will now have ``test_schema.referred`` stored as schema-qualified:: - - >>> metadata_obj.tables['test_schema.referred'].schema - 'test_schema' - -.. sidebar:: Best Practices for PostgreSQL Schema reflection - - The description of PostgreSQL schema reflection behavior is complex, and - is the product of many years of dealing with widely varied use cases and - user preferences. But in fact, there's no need to understand any of it if - you just stick to the simplest use pattern: leave the ``search_path`` set - to its default of ``public`` only, never refer to the name ``public`` as - an explicit schema name otherwise, and refer to all other schema names - explicitly when building up a :class:`_schema.Table` object. The options - described here are only for those users who can't, or prefer not to, stay - within these guidelines. - -.. seealso:: - - :ref:`reflection_schema_qualified_interaction` - discussion of the issue - from a backend-agnostic perspective - - `The Schema Search Path - `_ - - on the PostgreSQL website. - -INSERT/UPDATE...RETURNING -------------------------- - -The dialect supports PG 8.2's ``INSERT..RETURNING``, ``UPDATE..RETURNING`` and -``DELETE..RETURNING`` syntaxes. ``INSERT..RETURNING`` is used by default -for single-row INSERT statements in order to fetch newly generated -primary key identifiers. To specify an explicit ``RETURNING`` clause, -use the :meth:`._UpdateBase.returning` method on a per-statement basis:: - - # INSERT..RETURNING - result = table.insert().returning(table.c.col1, table.c.col2).\ - values(name='foo') - print(result.fetchall()) - - # UPDATE..RETURNING - result = table.update().returning(table.c.col1, table.c.col2).\ - where(table.c.name=='foo').values(name='bar') - print(result.fetchall()) - - # DELETE..RETURNING - result = table.delete().returning(table.c.col1, table.c.col2).\ - where(table.c.name=='foo') - print(result.fetchall()) - -.. _postgresql_insert_on_conflict: - -INSERT...ON CONFLICT (Upsert) ------------------------------- - -Starting with version 9.5, PostgreSQL allows "upserts" (update or insert) of -rows into a table via the ``ON CONFLICT`` clause of the ``INSERT`` statement. A -candidate row will only be inserted if that row does not violate any unique -constraints. In the case of a unique constraint violation, a secondary action -can occur which can be either "DO UPDATE", indicating that the data in the -target row should be updated, or "DO NOTHING", which indicates to silently skip -this row. - -Conflicts are determined using existing unique constraints and indexes. These -constraints may be identified either using their name as stated in DDL, -or they may be inferred by stating the columns and conditions that comprise -the indexes. - -SQLAlchemy provides ``ON CONFLICT`` support via the PostgreSQL-specific -:func:`_postgresql.insert()` function, which provides -the generative methods :meth:`_postgresql.Insert.on_conflict_do_update` -and :meth:`~.postgresql.Insert.on_conflict_do_nothing`: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy.dialects.postgresql import insert - >>> insert_stmt = insert(my_table).values( - ... id='some_existing_id', - ... data='inserted value') - >>> do_nothing_stmt = insert_stmt.on_conflict_do_nothing( - ... index_elements=['id'] - ... ) - >>> print(do_nothing_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO NOTHING - {stop} - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... constraint='pk_my_table', - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT ON CONSTRAINT pk_my_table DO UPDATE SET data = %(param_1)s - -.. seealso:: - - `INSERT .. ON CONFLICT - `_ - - in the PostgreSQL documentation. - -Specifying the Target -^^^^^^^^^^^^^^^^^^^^^ - -Both methods supply the "target" of the conflict using either the -named constraint or by column inference: - -* The :paramref:`_postgresql.Insert.on_conflict_do_update.index_elements` argument - specifies a sequence containing string column names, :class:`_schema.Column` - objects, and/or SQL expression elements, which would identify a unique - index: - - .. sourcecode:: pycon+sql - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s - {stop} - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... index_elements=[my_table.c.id], - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s - -* When using :paramref:`_postgresql.Insert.on_conflict_do_update.index_elements` to - infer an index, a partial index can be inferred by also specifying the - use the :paramref:`_postgresql.Insert.on_conflict_do_update.index_where` parameter: - - .. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(user_email='a@b.com', data='inserted data') - >>> stmt = stmt.on_conflict_do_update( - ... index_elements=[my_table.c.user_email], - ... index_where=my_table.c.user_email.like('%@gmail.com'), - ... set_=dict(data=stmt.excluded.data) - ... ) - >>> print(stmt) - {printsql}INSERT INTO my_table (data, user_email) - VALUES (%(data)s, %(user_email)s) ON CONFLICT (user_email) - WHERE user_email LIKE %(user_email_1)s DO UPDATE SET data = excluded.data - -* The :paramref:`_postgresql.Insert.on_conflict_do_update.constraint` argument is - used to specify an index directly rather than inferring it. This can be - the name of a UNIQUE constraint, a PRIMARY KEY constraint, or an INDEX: - - .. sourcecode:: pycon+sql - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... constraint='my_table_idx_1', - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT ON CONSTRAINT my_table_idx_1 DO UPDATE SET data = %(param_1)s - {stop} - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... constraint='my_table_pk', - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT ON CONSTRAINT my_table_pk DO UPDATE SET data = %(param_1)s - {stop} - -* The :paramref:`_postgresql.Insert.on_conflict_do_update.constraint` argument may - also refer to a SQLAlchemy construct representing a constraint, - e.g. :class:`.UniqueConstraint`, :class:`.PrimaryKeyConstraint`, - :class:`.Index`, or :class:`.ExcludeConstraint`. In this use, - if the constraint has a name, it is used directly. Otherwise, if the - constraint is unnamed, then inference will be used, where the expressions - and optional WHERE clause of the constraint will be spelled out in the - construct. This use is especially convenient - to refer to the named or unnamed primary key of a :class:`_schema.Table` - using the - :attr:`_schema.Table.primary_key` attribute: - - .. sourcecode:: pycon+sql - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... constraint=my_table.primary_key, - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s - -The SET Clause -^^^^^^^^^^^^^^^ - -``ON CONFLICT...DO UPDATE`` is used to perform an update of the already -existing row, using any combination of new values as well as values -from the proposed insertion. These values are specified using the -:paramref:`_postgresql.Insert.on_conflict_do_update.set_` parameter. This -parameter accepts a dictionary which consists of direct values -for UPDATE: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - >>> do_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value') - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s - -.. warning:: - - The :meth:`_expression.Insert.on_conflict_do_update` - method does **not** take into - account Python-side default UPDATE values or generation functions, e.g. - those specified using :paramref:`_schema.Column.onupdate`. - These values will not be exercised for an ON CONFLICT style of UPDATE, - unless they are manually specified in the - :paramref:`_postgresql.Insert.on_conflict_do_update.set_` dictionary. - -Updating using the Excluded INSERT Values -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to refer to the proposed insertion row, the special alias -:attr:`~.postgresql.Insert.excluded` is available as an attribute on -the :class:`_postgresql.Insert` object; this object is a -:class:`_expression.ColumnCollection` -which alias contains all columns of the target -table: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values( - ... id='some_id', - ... data='inserted value', - ... author='jlh' - ... ) - >>> do_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value', author=stmt.excluded.author) - ... ) - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data, author) - VALUES (%(id)s, %(data)s, %(author)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s, author = excluded.author - -Additional WHERE Criteria -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The :meth:`_expression.Insert.on_conflict_do_update` method also accepts -a WHERE clause using the :paramref:`_postgresql.Insert.on_conflict_do_update.where` -parameter, which will limit those rows which receive an UPDATE: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values( - ... id='some_id', - ... data='inserted value', - ... author='jlh' - ... ) - >>> on_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value', author=stmt.excluded.author), - ... where=(my_table.c.status == 2) - ... ) - >>> print(on_update_stmt) - {printsql}INSERT INTO my_table (id, data, author) - VALUES (%(id)s, %(data)s, %(author)s) - ON CONFLICT (id) DO UPDATE SET data = %(param_1)s, author = excluded.author - WHERE my_table.status = %(status_1)s - -Skipping Rows with DO NOTHING -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``ON CONFLICT`` may be used to skip inserting a row entirely -if any conflict with a unique or exclusion constraint occurs; below -this is illustrated using the -:meth:`~.postgresql.Insert.on_conflict_do_nothing` method: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - >>> stmt = stmt.on_conflict_do_nothing(index_elements=['id']) - >>> print(stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT (id) DO NOTHING - -If ``DO NOTHING`` is used without specifying any columns or constraint, -it has the effect of skipping the INSERT for any unique or exclusion -constraint violation which occurs: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - >>> stmt = stmt.on_conflict_do_nothing() - >>> print(stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (%(id)s, %(data)s) - ON CONFLICT DO NOTHING - -.. _postgresql_match: - -Full Text Search ----------------- - -PostgreSQL's full text search system is available through the use of the -:data:`.func` namespace, combined with the use of custom operators -via the :meth:`.Operators.bool_op` method. For simple cases with some -degree of cross-backend compatibility, the :meth:`.Operators.match` operator -may also be used. - -.. _postgresql_simple_match: - -Simple plain text matching with ``match()`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The :meth:`.Operators.match` operator provides for cross-compatible simple -text matching. For the PostgreSQL backend, it's hardcoded to generate -an expression using the ``@@`` operator in conjunction with the -``plainto_tsquery()`` PostgreSQL function. - -On the PostgreSQL dialect, an expression like the following:: - - select(sometable.c.text.match("search string")) - -would emit to the database:: - - SELECT text @@ plainto_tsquery('search string') FROM table - -Above, passing a plain string to :meth:`.Operators.match` will automatically -make use of ``plainto_tsquery()`` to specify the type of tsquery. This -establishes basic database cross-compatibility for :meth:`.Operators.match` -with other backends. - -.. versionchanged:: 2.0 The default tsquery generation function used by the - PostgreSQL dialect with :meth:`.Operators.match` is ``plainto_tsquery()``. - - To render exactly what was rendered in 1.4, use the following form:: - - from sqlalchemy import func - - select( - sometable.c.text.bool_op("@@")(func.to_tsquery("search string")) - ) - - Which would emit:: - - SELECT text @@ to_tsquery('search string') FROM table - -Using PostgreSQL full text functions and operators directly -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Text search operations beyond the simple use of :meth:`.Operators.match` -may make use of the :data:`.func` namespace to generate PostgreSQL full-text -functions, in combination with :meth:`.Operators.bool_op` to generate -any boolean operator. - -For example, the query:: - - select( - func.to_tsquery('cat').bool_op("@>")(func.to_tsquery('cat & rat')) - ) - -would generate: - -.. sourcecode:: sql - - SELECT to_tsquery('cat') @> to_tsquery('cat & rat') - - -The :class:`_postgresql.TSVECTOR` type can provide for explicit CAST:: - - from sqlalchemy.dialects.postgresql import TSVECTOR - from sqlalchemy import select, cast - select(cast("some text", TSVECTOR)) - -produces a statement equivalent to:: - - SELECT CAST('some text' AS TSVECTOR) AS anon_1 - -The ``func`` namespace is augmented by the PostgreSQL dialect to set up -correct argument and return types for most full text search functions. -These functions are used automatically by the :attr:`_sql.func` namespace -assuming the ``sqlalchemy.dialects.postgresql`` package has been imported, -or :func:`_sa.create_engine` has been invoked using a ``postgresql`` -dialect. These functions are documented at: - -* :class:`_postgresql.to_tsvector` -* :class:`_postgresql.to_tsquery` -* :class:`_postgresql.plainto_tsquery` -* :class:`_postgresql.phraseto_tsquery` -* :class:`_postgresql.websearch_to_tsquery` -* :class:`_postgresql.ts_headline` - -Specifying the "regconfig" with ``match()`` or custom operators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -PostgreSQL's ``plainto_tsquery()`` function accepts an optional -"regconfig" argument that is used to instruct PostgreSQL to use a -particular pre-computed GIN or GiST index in order to perform the search. -When using :meth:`.Operators.match`, this additional parameter may be -specified using the ``postgresql_regconfig`` parameter, such as:: - - select(mytable.c.id).where( - mytable.c.title.match('somestring', postgresql_regconfig='english') - ) - -Which would emit:: - - SELECT mytable.id FROM mytable - WHERE mytable.title @@ plainto_tsquery('english', 'somestring') - -When using other PostgreSQL search functions with :data:`.func`, the -"regconfig" parameter may be passed directly as the initial argument:: - - select(mytable.c.id).where( - func.to_tsvector("english", mytable.c.title).bool_op("@@")( - func.to_tsquery("english", "somestring") - ) - ) - -produces a statement equivalent to:: - - SELECT mytable.id FROM mytable - WHERE to_tsvector('english', mytable.title) @@ - to_tsquery('english', 'somestring') - -It is recommended that you use the ``EXPLAIN ANALYZE...`` tool from -PostgreSQL to ensure that you are generating queries with SQLAlchemy that -take full advantage of any indexes you may have created for full text search. - -.. seealso:: - - `Full Text Search `_ - in the PostgreSQL documentation - - -FROM ONLY ... -------------- - -The dialect supports PostgreSQL's ONLY keyword for targeting only a particular -table in an inheritance hierarchy. This can be used to produce the -``SELECT ... FROM ONLY``, ``UPDATE ONLY ...``, and ``DELETE FROM ONLY ...`` -syntaxes. It uses SQLAlchemy's hints mechanism:: - - # SELECT ... FROM ONLY ... - result = table.select().with_hint(table, 'ONLY', 'postgresql') - print(result.fetchall()) - - # UPDATE ONLY ... - table.update(values=dict(foo='bar')).with_hint('ONLY', - dialect_name='postgresql') - - # DELETE FROM ONLY ... - table.delete().with_hint('ONLY', dialect_name='postgresql') - - -.. _postgresql_indexes: - -PostgreSQL-Specific Index Options ---------------------------------- - -Several extensions to the :class:`.Index` construct are available, specific -to the PostgreSQL dialect. - -Covering Indexes -^^^^^^^^^^^^^^^^ - -The ``postgresql_include`` option renders INCLUDE(colname) for the given -string names:: - - Index("my_index", table.c.x, postgresql_include=['y']) - -would render the index as ``CREATE INDEX my_index ON table (x) INCLUDE (y)`` - -Note that this feature requires PostgreSQL 11 or later. - -.. versionadded:: 1.4 - -.. _postgresql_partial_indexes: - -Partial Indexes -^^^^^^^^^^^^^^^ - -Partial indexes add criterion to the index definition so that the index is -applied to a subset of rows. These can be specified on :class:`.Index` -using the ``postgresql_where`` keyword argument:: - - Index('my_index', my_table.c.id, postgresql_where=my_table.c.value > 10) - -.. _postgresql_operator_classes: - -Operator Classes -^^^^^^^^^^^^^^^^ - -PostgreSQL allows the specification of an *operator class* for each column of -an index (see -https://www.postgresql.org/docs/current/interactive/indexes-opclass.html). -The :class:`.Index` construct allows these to be specified via the -``postgresql_ops`` keyword argument:: - - Index( - 'my_index', my_table.c.id, my_table.c.data, - postgresql_ops={ - 'data': 'text_pattern_ops', - 'id': 'int4_ops' - }) - -Note that the keys in the ``postgresql_ops`` dictionaries are the -"key" name of the :class:`_schema.Column`, i.e. the name used to access it from -the ``.c`` collection of :class:`_schema.Table`, which can be configured to be -different than the actual name of the column as expressed in the database. - -If ``postgresql_ops`` is to be used against a complex SQL expression such -as a function call, then to apply to the column it must be given a label -that is identified in the dictionary by name, e.g.:: - - Index( - 'my_index', my_table.c.id, - func.lower(my_table.c.data).label('data_lower'), - postgresql_ops={ - 'data_lower': 'text_pattern_ops', - 'id': 'int4_ops' - }) - -Operator classes are also supported by the -:class:`_postgresql.ExcludeConstraint` construct using the -:paramref:`_postgresql.ExcludeConstraint.ops` parameter. See that parameter for -details. - -.. versionadded:: 1.3.21 added support for operator classes with - :class:`_postgresql.ExcludeConstraint`. - - -Index Types -^^^^^^^^^^^ - -PostgreSQL provides several index types: B-Tree, Hash, GiST, and GIN, as well -as the ability for users to create their own (see -https://www.postgresql.org/docs/current/static/indexes-types.html). These can be -specified on :class:`.Index` using the ``postgresql_using`` keyword argument:: - - Index('my_index', my_table.c.data, postgresql_using='gin') - -The value passed to the keyword argument will be simply passed through to the -underlying CREATE INDEX command, so it *must* be a valid index type for your -version of PostgreSQL. - -.. _postgresql_index_storage: - -Index Storage Parameters -^^^^^^^^^^^^^^^^^^^^^^^^ - -PostgreSQL allows storage parameters to be set on indexes. The storage -parameters available depend on the index method used by the index. Storage -parameters can be specified on :class:`.Index` using the ``postgresql_with`` -keyword argument:: - - Index('my_index', my_table.c.data, postgresql_with={"fillfactor": 50}) - -PostgreSQL allows to define the tablespace in which to create the index. -The tablespace can be specified on :class:`.Index` using the -``postgresql_tablespace`` keyword argument:: - - Index('my_index', my_table.c.data, postgresql_tablespace='my_tablespace') - -Note that the same option is available on :class:`_schema.Table` as well. - -.. _postgresql_index_concurrently: - -Indexes with CONCURRENTLY -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The PostgreSQL index option CONCURRENTLY is supported by passing the -flag ``postgresql_concurrently`` to the :class:`.Index` construct:: - - tbl = Table('testtbl', m, Column('data', Integer)) - - idx1 = Index('test_idx1', tbl.c.data, postgresql_concurrently=True) - -The above index construct will render DDL for CREATE INDEX, assuming -PostgreSQL 8.2 or higher is detected or for a connection-less dialect, as:: - - CREATE INDEX CONCURRENTLY test_idx1 ON testtbl (data) - -For DROP INDEX, assuming PostgreSQL 9.2 or higher is detected or for -a connection-less dialect, it will emit:: - - DROP INDEX CONCURRENTLY test_idx1 - -When using CONCURRENTLY, the PostgreSQL database requires that the statement -be invoked outside of a transaction block. The Python DBAPI enforces that -even for a single statement, a transaction is present, so to use this -construct, the DBAPI's "autocommit" mode must be used:: - - metadata = MetaData() - table = Table( - "foo", metadata, - Column("id", String)) - index = Index( - "foo_idx", table.c.id, postgresql_concurrently=True) - - with engine.connect() as conn: - with conn.execution_options(isolation_level='AUTOCOMMIT'): - table.create(conn) - -.. seealso:: - - :ref:`postgresql_isolation_level` - -.. _postgresql_index_reflection: - -PostgreSQL Index Reflection ---------------------------- - -The PostgreSQL database creates a UNIQUE INDEX implicitly whenever the -UNIQUE CONSTRAINT construct is used. When inspecting a table using -:class:`_reflection.Inspector`, the :meth:`_reflection.Inspector.get_indexes` -and the :meth:`_reflection.Inspector.get_unique_constraints` -will report on these -two constructs distinctly; in the case of the index, the key -``duplicates_constraint`` will be present in the index entry if it is -detected as mirroring a constraint. When performing reflection using -``Table(..., autoload_with=engine)``, the UNIQUE INDEX is **not** returned -in :attr:`_schema.Table.indexes` when it is detected as mirroring a -:class:`.UniqueConstraint` in the :attr:`_schema.Table.constraints` collection -. - -Special Reflection Options --------------------------- - -The :class:`_reflection.Inspector` -used for the PostgreSQL backend is an instance -of :class:`.PGInspector`, which offers additional methods:: - - from sqlalchemy import create_engine, inspect - - engine = create_engine("postgresql+psycopg2://localhost/test") - insp = inspect(engine) # will be a PGInspector - - print(insp.get_enums()) - -.. autoclass:: PGInspector - :members: - -.. _postgresql_table_options: - -PostgreSQL Table Options ------------------------- - -Several options for CREATE TABLE are supported directly by the PostgreSQL -dialect in conjunction with the :class:`_schema.Table` construct: - -* ``INHERITS``:: - - Table("some_table", metadata, ..., postgresql_inherits="some_supertable") - - Table("some_table", metadata, ..., postgresql_inherits=("t1", "t2", ...)) - -* ``ON COMMIT``:: - - Table("some_table", metadata, ..., postgresql_on_commit='PRESERVE ROWS') - -* ``PARTITION BY``:: - - Table("some_table", metadata, ..., - postgresql_partition_by='LIST (part_column)') - - .. versionadded:: 1.2.6 - -* ``TABLESPACE``:: - - Table("some_table", metadata, ..., postgresql_tablespace='some_tablespace') - - The above option is also available on the :class:`.Index` construct. - -* ``USING``:: - - Table("some_table", metadata, ..., postgresql_using='heap') - - .. versionadded:: 2.0.26 - -* ``WITH OIDS``:: - - Table("some_table", metadata, ..., postgresql_with_oids=True) - -* ``WITHOUT OIDS``:: - - Table("some_table", metadata, ..., postgresql_with_oids=False) - -.. seealso:: - - `PostgreSQL CREATE TABLE options - `_ - - in the PostgreSQL documentation. - -.. _postgresql_constraint_options: - -PostgreSQL Constraint Options ------------------------------ - -The following option(s) are supported by the PostgreSQL dialect in conjunction -with selected constraint constructs: - -* ``NOT VALID``: This option applies towards CHECK and FOREIGN KEY constraints - when the constraint is being added to an existing table via ALTER TABLE, - and has the effect that existing rows are not scanned during the ALTER - operation against the constraint being added. - - When using a SQL migration tool such as `Alembic `_ - that renders ALTER TABLE constructs, the ``postgresql_not_valid`` argument - may be specified as an additional keyword argument within the operation - that creates the constraint, as in the following Alembic example:: - - def update(): - op.create_foreign_key( - "fk_user_address", - "address", - "user", - ["user_id"], - ["id"], - postgresql_not_valid=True - ) - - The keyword is ultimately accepted directly by the - :class:`_schema.CheckConstraint`, :class:`_schema.ForeignKeyConstraint` - and :class:`_schema.ForeignKey` constructs; when using a tool like - Alembic, dialect-specific keyword arguments are passed through to - these constructs from the migration operation directives:: - - CheckConstraint("some_field IS NOT NULL", postgresql_not_valid=True) - - ForeignKeyConstraint(["some_id"], ["some_table.some_id"], postgresql_not_valid=True) - - .. versionadded:: 1.4.32 - - .. seealso:: - - `PostgreSQL ALTER TABLE options - `_ - - in the PostgreSQL documentation. - -.. _postgresql_table_valued_overview: - -Table values, Table and Column valued functions, Row and Tuple objects ------------------------------------------------------------------------ - -PostgreSQL makes great use of modern SQL forms such as table-valued functions, -tables and rows as values. These constructs are commonly used as part -of PostgreSQL's support for complex datatypes such as JSON, ARRAY, and other -datatypes. SQLAlchemy's SQL expression language has native support for -most table-valued and row-valued forms. - -.. _postgresql_table_valued: - -Table-Valued Functions -^^^^^^^^^^^^^^^^^^^^^^^ - -Many PostgreSQL built-in functions are intended to be used in the FROM clause -of a SELECT statement, and are capable of returning table rows or sets of table -rows. A large portion of PostgreSQL's JSON functions for example such as -``json_array_elements()``, ``json_object_keys()``, ``json_each_text()``, -``json_each()``, ``json_to_record()``, ``json_populate_recordset()`` use such -forms. These classes of SQL function calling forms in SQLAlchemy are available -using the :meth:`_functions.FunctionElement.table_valued` method in conjunction -with :class:`_functions.Function` objects generated from the :data:`_sql.func` -namespace. - -Examples from PostgreSQL's reference documentation follow below: - -* ``json_each()``: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import select, func - >>> stmt = select(func.json_each('{"a":"foo", "b":"bar"}').table_valued("key", "value")) - >>> print(stmt) - {printsql}SELECT anon_1.key, anon_1.value - FROM json_each(:json_each_1) AS anon_1 - -* ``json_populate_record()``: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import select, func, literal_column - >>> stmt = select( - ... func.json_populate_record( - ... literal_column("null::myrowtype"), - ... '{"a":1,"b":2}' - ... ).table_valued("a", "b", name="x") - ... ) - >>> print(stmt) - {printsql}SELECT x.a, x.b - FROM json_populate_record(null::myrowtype, :json_populate_record_1) AS x - -* ``json_to_record()`` - this form uses a PostgreSQL specific form of derived - columns in the alias, where we may make use of :func:`_sql.column` elements with - types to produce them. The :meth:`_functions.FunctionElement.table_valued` - method produces a :class:`_sql.TableValuedAlias` construct, and the method - :meth:`_sql.TableValuedAlias.render_derived` method sets up the derived - columns specification: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import select, func, column, Integer, Text - >>> stmt = select( - ... func.json_to_record('{"a":1,"b":[1,2,3],"c":"bar"}').table_valued( - ... column("a", Integer), column("b", Text), column("d", Text), - ... ).render_derived(name="x", with_types=True) - ... ) - >>> print(stmt) - {printsql}SELECT x.a, x.b, x.d - FROM json_to_record(:json_to_record_1) AS x(a INTEGER, b TEXT, d TEXT) - -* ``WITH ORDINALITY`` - part of the SQL standard, ``WITH ORDINALITY`` adds an - ordinal counter to the output of a function and is accepted by a limited set - of PostgreSQL functions including ``unnest()`` and ``generate_series()``. The - :meth:`_functions.FunctionElement.table_valued` method accepts a keyword - parameter ``with_ordinality`` for this purpose, which accepts the string name - that will be applied to the "ordinality" column: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import select, func - >>> stmt = select( - ... func.generate_series(4, 1, -1). - ... table_valued("value", with_ordinality="ordinality"). - ... render_derived() - ... ) - >>> print(stmt) - {printsql}SELECT anon_1.value, anon_1.ordinality - FROM generate_series(:generate_series_1, :generate_series_2, :generate_series_3) - WITH ORDINALITY AS anon_1(value, ordinality) - -.. versionadded:: 1.4.0b2 - -.. seealso:: - - :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial` - -.. _postgresql_column_valued: - -Column Valued Functions -^^^^^^^^^^^^^^^^^^^^^^^ - -Similar to the table valued function, a column valued function is present -in the FROM clause, but delivers itself to the columns clause as a single -scalar value. PostgreSQL functions such as ``json_array_elements()``, -``unnest()`` and ``generate_series()`` may use this form. Column valued functions are available using the -:meth:`_functions.FunctionElement.column_valued` method of :class:`_functions.FunctionElement`: - -* ``json_array_elements()``: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import select, func - >>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x")) - >>> print(stmt) - {printsql}SELECT x - FROM json_array_elements(:json_array_elements_1) AS x - -* ``unnest()`` - in order to generate a PostgreSQL ARRAY literal, the - :func:`_postgresql.array` construct may be used: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy.dialects.postgresql import array - >>> from sqlalchemy import select, func - >>> stmt = select(func.unnest(array([1, 2])).column_valued()) - >>> print(stmt) - {printsql}SELECT anon_1 - FROM unnest(ARRAY[%(param_1)s, %(param_2)s]) AS anon_1 - - The function can of course be used against an existing table-bound column - that's of type :class:`_types.ARRAY`: - - .. sourcecode:: pycon+sql - - >>> from sqlalchemy import table, column, ARRAY, Integer - >>> from sqlalchemy import select, func - >>> t = table("t", column('value', ARRAY(Integer))) - >>> stmt = select(func.unnest(t.c.value).column_valued("unnested_value")) - >>> print(stmt) - {printsql}SELECT unnested_value - FROM unnest(t.value) AS unnested_value - -.. seealso:: - - :ref:`tutorial_functions_column_valued` - in the :ref:`unified_tutorial` - - -Row Types -^^^^^^^^^ - -Built-in support for rendering a ``ROW`` may be approximated using -``func.ROW`` with the :attr:`_sa.func` namespace, or by using the -:func:`_sql.tuple_` construct: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy import table, column, func, tuple_ - >>> t = table("t", column("id"), column("fk")) - >>> stmt = t.select().where( - ... tuple_(t.c.id, t.c.fk) > (1,2) - ... ).where( - ... func.ROW(t.c.id, t.c.fk) < func.ROW(3, 7) - ... ) - >>> print(stmt) - {printsql}SELECT t.id, t.fk - FROM t - WHERE (t.id, t.fk) > (:param_1, :param_2) AND ROW(t.id, t.fk) < ROW(:ROW_1, :ROW_2) - -.. seealso:: - - `PostgreSQL Row Constructors - `_ - - `PostgreSQL Row Constructor Comparison - `_ - -Table Types passed to Functions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -PostgreSQL supports passing a table as an argument to a function, which is -known as a "record" type. SQLAlchemy :class:`_sql.FromClause` objects -such as :class:`_schema.Table` support this special form using the -:meth:`_sql.FromClause.table_valued` method, which is comparable to the -:meth:`_functions.FunctionElement.table_valued` method except that the collection -of columns is already established by that of the :class:`_sql.FromClause` -itself: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy import table, column, func, select - >>> a = table( "a", column("id"), column("x"), column("y")) - >>> stmt = select(func.row_to_json(a.table_valued())) - >>> print(stmt) - {printsql}SELECT row_to_json(a) AS row_to_json_1 - FROM a - -.. versionadded:: 1.4.0b2 - - - -""" # noqa: E501 - -from __future__ import annotations - -from collections import defaultdict -from functools import lru_cache -import re -from typing import Any -from typing import cast -from typing import List -from typing import Optional -from typing import Tuple -from typing import TYPE_CHECKING -from typing import Union - -from . import arraylib as _array -from . import json as _json -from . import pg_catalog -from . import ranges as _ranges -from .ext import _regconfig_fn -from .ext import aggregate_order_by -from .hstore import HSTORE -from .named_types import CreateDomainType as CreateDomainType # noqa: F401 -from .named_types import CreateEnumType as CreateEnumType # noqa: F401 -from .named_types import DOMAIN as DOMAIN # noqa: F401 -from .named_types import DropDomainType as DropDomainType # noqa: F401 -from .named_types import DropEnumType as DropEnumType # noqa: F401 -from .named_types import ENUM as ENUM # noqa: F401 -from .named_types import NamedType as NamedType # noqa: F401 -from .types import _DECIMAL_TYPES # noqa: F401 -from .types import _FLOAT_TYPES # noqa: F401 -from .types import _INT_TYPES # noqa: F401 -from .types import BIT as BIT -from .types import BYTEA as BYTEA -from .types import CIDR as CIDR -from .types import CITEXT as CITEXT -from .types import INET as INET -from .types import INTERVAL as INTERVAL -from .types import MACADDR as MACADDR -from .types import MACADDR8 as MACADDR8 -from .types import MONEY as MONEY -from .types import OID as OID -from .types import PGBit as PGBit # noqa: F401 -from .types import PGCidr as PGCidr # noqa: F401 -from .types import PGInet as PGInet # noqa: F401 -from .types import PGInterval as PGInterval # noqa: F401 -from .types import PGMacAddr as PGMacAddr # noqa: F401 -from .types import PGMacAddr8 as PGMacAddr8 # noqa: F401 -from .types import PGUuid as PGUuid -from .types import REGCLASS as REGCLASS -from .types import REGCONFIG as REGCONFIG # noqa: F401 -from .types import TIME as TIME -from .types import TIMESTAMP as TIMESTAMP -from .types import TSVECTOR as TSVECTOR -from ... import exc -from ... import schema -from ... import select -from ... import sql -from ... import util -from ...engine import characteristics -from ...engine import default -from ...engine import interfaces -from ...engine import ObjectKind -from ...engine import ObjectScope -from ...engine import reflection -from ...engine import URL -from ...engine.reflection import ReflectionDefaults -from ...sql import bindparam -from ...sql import coercions -from ...sql import compiler -from ...sql import elements -from ...sql import expression -from ...sql import roles -from ...sql import sqltypes -from ...sql import util as sql_util -from ...sql.compiler import InsertmanyvaluesSentinelOpts -from ...sql.visitors import InternalTraversal -from ...types import BIGINT -from ...types import BOOLEAN -from ...types import CHAR -from ...types import DATE -from ...types import DOUBLE_PRECISION -from ...types import FLOAT -from ...types import INTEGER -from ...types import NUMERIC -from ...types import REAL -from ...types import SMALLINT -from ...types import TEXT -from ...types import UUID as UUID -from ...types import VARCHAR -from ...util.typing import TypedDict - -IDX_USING = re.compile(r"^(?:btree|hash|gist|gin|[\w_]+)$", re.I) - -RESERVED_WORDS = { - "all", - "analyse", - "analyze", - "and", - "any", - "array", - "as", - "asc", - "asymmetric", - "both", - "case", - "cast", - "check", - "collate", - "column", - "constraint", - "create", - "current_catalog", - "current_date", - "current_role", - "current_time", - "current_timestamp", - "current_user", - "default", - "deferrable", - "desc", - "distinct", - "do", - "else", - "end", - "except", - "false", - "fetch", - "for", - "foreign", - "from", - "grant", - "group", - "having", - "in", - "initially", - "intersect", - "into", - "leading", - "limit", - "localtime", - "localtimestamp", - "new", - "not", - "null", - "of", - "off", - "offset", - "old", - "on", - "only", - "or", - "order", - "placing", - "primary", - "references", - "returning", - "select", - "session_user", - "some", - "symmetric", - "table", - "then", - "to", - "trailing", - "true", - "union", - "unique", - "user", - "using", - "variadic", - "when", - "where", - "window", - "with", - "authorization", - "between", - "binary", - "cross", - "current_schema", - "freeze", - "full", - "ilike", - "inner", - "is", - "isnull", - "join", - "left", - "like", - "natural", - "notnull", - "outer", - "over", - "overlaps", - "right", - "similar", - "verbose", -} - -colspecs = { - sqltypes.ARRAY: _array.ARRAY, - sqltypes.Interval: INTERVAL, - sqltypes.Enum: ENUM, - sqltypes.JSON.JSONPathType: _json.JSONPATH, - sqltypes.JSON: _json.JSON, - sqltypes.Uuid: PGUuid, -} - - -ischema_names = { - "_array": _array.ARRAY, - "hstore": HSTORE, - "json": _json.JSON, - "jsonb": _json.JSONB, - "int4range": _ranges.INT4RANGE, - "int8range": _ranges.INT8RANGE, - "numrange": _ranges.NUMRANGE, - "daterange": _ranges.DATERANGE, - "tsrange": _ranges.TSRANGE, - "tstzrange": _ranges.TSTZRANGE, - "int4multirange": _ranges.INT4MULTIRANGE, - "int8multirange": _ranges.INT8MULTIRANGE, - "nummultirange": _ranges.NUMMULTIRANGE, - "datemultirange": _ranges.DATEMULTIRANGE, - "tsmultirange": _ranges.TSMULTIRANGE, - "tstzmultirange": _ranges.TSTZMULTIRANGE, - "integer": INTEGER, - "bigint": BIGINT, - "smallint": SMALLINT, - "character varying": VARCHAR, - "character": CHAR, - '"char"': sqltypes.String, - "name": sqltypes.String, - "text": TEXT, - "numeric": NUMERIC, - "float": FLOAT, - "real": REAL, - "inet": INET, - "cidr": CIDR, - "citext": CITEXT, - "uuid": UUID, - "bit": BIT, - "bit varying": BIT, - "macaddr": MACADDR, - "macaddr8": MACADDR8, - "money": MONEY, - "oid": OID, - "regclass": REGCLASS, - "double precision": DOUBLE_PRECISION, - "timestamp": TIMESTAMP, - "timestamp with time zone": TIMESTAMP, - "timestamp without time zone": TIMESTAMP, - "time with time zone": TIME, - "time without time zone": TIME, - "date": DATE, - "time": TIME, - "bytea": BYTEA, - "boolean": BOOLEAN, - "interval": INTERVAL, - "tsvector": TSVECTOR, -} - - -class PGCompiler(compiler.SQLCompiler): - def visit_to_tsvector_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def visit_to_tsquery_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def visit_plainto_tsquery_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def visit_phraseto_tsquery_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def visit_websearch_to_tsquery_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def visit_ts_headline_func(self, element, **kw): - return self._assert_pg_ts_ext(element, **kw) - - def _assert_pg_ts_ext(self, element, **kw): - if not isinstance(element, _regconfig_fn): - # other options here include trying to rewrite the function - # with the correct types. however, that means we have to - # "un-SQL-ize" the first argument, which can't work in a - # generalized way. Also, parent compiler class has already added - # the incorrect return type to the result map. So let's just - # make sure the function we want is used up front. - - raise exc.CompileError( - f'Can\'t compile "{element.name}()" full text search ' - f"function construct that does not originate from the " - f'"sqlalchemy.dialects.postgresql" package. ' - f'Please ensure "import sqlalchemy.dialects.postgresql" is ' - f"called before constructing " - f'"sqlalchemy.func.{element.name}()" to ensure registration ' - f"of the correct argument and return types." - ) - - return f"{element.name}{self.function_argspec(element, **kw)}" - - def render_bind_cast(self, type_, dbapi_type, sqltext): - if dbapi_type._type_affinity is sqltypes.String and dbapi_type.length: - # use VARCHAR with no length for VARCHAR cast. - # see #9511 - dbapi_type = sqltypes.STRINGTYPE - return f"""{sqltext}::{ - self.dialect.type_compiler_instance.process( - dbapi_type, identifier_preparer=self.preparer - ) - }""" - - def visit_array(self, element, **kw): - return "ARRAY[%s]" % self.visit_clauselist(element, **kw) - - def visit_slice(self, element, **kw): - return "%s:%s" % ( - self.process(element.start, **kw), - self.process(element.stop, **kw), - ) - - def visit_bitwise_xor_op_binary(self, binary, operator, **kw): - return self._generate_generic_binary(binary, " # ", **kw) - - def visit_json_getitem_op_binary( - self, binary, operator, _cast_applied=False, **kw - ): - if ( - not _cast_applied - and binary.type._type_affinity is not sqltypes.JSON - ): - kw["_cast_applied"] = True - return self.process(sql.cast(binary, binary.type), **kw) - - kw["eager_grouping"] = True - - return self._generate_generic_binary( - binary, " -> " if not _cast_applied else " ->> ", **kw - ) - - def visit_json_path_getitem_op_binary( - self, binary, operator, _cast_applied=False, **kw - ): - if ( - not _cast_applied - and binary.type._type_affinity is not sqltypes.JSON - ): - kw["_cast_applied"] = True - return self.process(sql.cast(binary, binary.type), **kw) - - kw["eager_grouping"] = True - return self._generate_generic_binary( - binary, " #> " if not _cast_applied else " #>> ", **kw - ) - - def visit_getitem_binary(self, binary, operator, **kw): - return "%s[%s]" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_aggregate_order_by(self, element, **kw): - return "%s ORDER BY %s" % ( - self.process(element.target, **kw), - self.process(element.order_by, **kw), - ) - - def visit_match_op_binary(self, binary, operator, **kw): - if "postgresql_regconfig" in binary.modifiers: - regconfig = self.render_literal_value( - binary.modifiers["postgresql_regconfig"], sqltypes.STRINGTYPE - ) - if regconfig: - return "%s @@ plainto_tsquery(%s, %s)" % ( - self.process(binary.left, **kw), - regconfig, - self.process(binary.right, **kw), - ) - return "%s @@ plainto_tsquery(%s)" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_ilike_case_insensitive_operand(self, element, **kw): - return element.element._compiler_dispatch(self, **kw) - - def visit_ilike_op_binary(self, binary, operator, **kw): - escape = binary.modifiers.get("escape", None) - - return "%s ILIKE %s" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) + ( - " ESCAPE " + self.render_literal_value(escape, sqltypes.STRINGTYPE) - if escape is not None - else "" - ) - - def visit_not_ilike_op_binary(self, binary, operator, **kw): - escape = binary.modifiers.get("escape", None) - return "%s NOT ILIKE %s" % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) + ( - " ESCAPE " + self.render_literal_value(escape, sqltypes.STRINGTYPE) - if escape is not None - else "" - ) - - def _regexp_match(self, base_op, binary, operator, kw): - flags = binary.modifiers["flags"] - if flags is None: - return self._generate_generic_binary( - binary, " %s " % base_op, **kw - ) - if flags == "i": - return self._generate_generic_binary( - binary, " %s* " % base_op, **kw - ) - return "%s %s CONCAT('(?', %s, ')', %s)" % ( - self.process(binary.left, **kw), - base_op, - self.render_literal_value(flags, sqltypes.STRINGTYPE), - self.process(binary.right, **kw), - ) - - def visit_regexp_match_op_binary(self, binary, operator, **kw): - return self._regexp_match("~", binary, operator, kw) - - def visit_not_regexp_match_op_binary(self, binary, operator, **kw): - return self._regexp_match("!~", binary, operator, kw) - - def visit_regexp_replace_op_binary(self, binary, operator, **kw): - string = self.process(binary.left, **kw) - pattern_replace = self.process(binary.right, **kw) - flags = binary.modifiers["flags"] - if flags is None: - return "REGEXP_REPLACE(%s, %s)" % ( - string, - pattern_replace, - ) - else: - return "REGEXP_REPLACE(%s, %s, %s)" % ( - string, - pattern_replace, - self.render_literal_value(flags, sqltypes.STRINGTYPE), - ) - - def visit_empty_set_expr(self, element_types, **kw): - # cast the empty set to the type we are comparing against. if - # we are comparing against the null type, pick an arbitrary - # datatype for the empty set - return "SELECT %s WHERE 1!=1" % ( - ", ".join( - "CAST(NULL AS %s)" - % self.dialect.type_compiler_instance.process( - INTEGER() if type_._isnull else type_ - ) - for type_ in element_types or [INTEGER()] - ), - ) - - def render_literal_value(self, value, type_): - value = super().render_literal_value(value, type_) - - if self.dialect._backslash_escapes: - value = value.replace("\\", "\\\\") - return value - - def visit_aggregate_strings_func(self, fn, **kw): - return "string_agg%s" % self.function_argspec(fn) - - def visit_sequence(self, seq, **kw): - return "nextval('%s')" % self.preparer.format_sequence(seq) - - def limit_clause(self, select, **kw): - text = "" - if select._limit_clause is not None: - text += " \n LIMIT " + self.process(select._limit_clause, **kw) - if select._offset_clause is not None: - if select._limit_clause is None: - text += "\n LIMIT ALL" - text += " OFFSET " + self.process(select._offset_clause, **kw) - return text - - def format_from_hint_text(self, sqltext, table, hint, iscrud): - if hint.upper() != "ONLY": - raise exc.CompileError("Unrecognized hint: %r" % hint) - return "ONLY " + sqltext - - def get_select_precolumns(self, select, **kw): - # Do not call super().get_select_precolumns because - # it will warn/raise when distinct on is present - if select._distinct or select._distinct_on: - if select._distinct_on: - return ( - "DISTINCT ON (" - + ", ".join( - [ - self.process(col, **kw) - for col in select._distinct_on - ] - ) - + ") " - ) - else: - return "DISTINCT " - else: - return "" - - def for_update_clause(self, select, **kw): - if select._for_update_arg.read: - if select._for_update_arg.key_share: - tmp = " FOR KEY SHARE" - else: - tmp = " FOR SHARE" - elif select._for_update_arg.key_share: - tmp = " FOR NO KEY UPDATE" - else: - tmp = " FOR UPDATE" - - if select._for_update_arg.of: - tables = util.OrderedSet() - for c in select._for_update_arg.of: - tables.update(sql_util.surface_selectables_only(c)) - - tmp += " OF " + ", ".join( - self.process(table, ashint=True, use_schema=False, **kw) - for table in tables - ) - - if select._for_update_arg.nowait: - tmp += " NOWAIT" - if select._for_update_arg.skip_locked: - tmp += " SKIP LOCKED" - - return tmp - - def visit_substring_func(self, func, **kw): - s = self.process(func.clauses.clauses[0], **kw) - start = self.process(func.clauses.clauses[1], **kw) - if len(func.clauses.clauses) > 2: - length = self.process(func.clauses.clauses[2], **kw) - return "SUBSTRING(%s FROM %s FOR %s)" % (s, start, length) - else: - return "SUBSTRING(%s FROM %s)" % (s, start) - - def _on_conflict_target(self, clause, **kw): - if clause.constraint_target is not None: - # target may be a name of an Index, UniqueConstraint or - # ExcludeConstraint. While there is a separate - # "max_identifier_length" for indexes, PostgreSQL uses the same - # length for all objects so we can use - # truncate_and_render_constraint_name - target_text = ( - "ON CONSTRAINT %s" - % self.preparer.truncate_and_render_constraint_name( - clause.constraint_target - ) - ) - elif clause.inferred_target_elements is not None: - target_text = "(%s)" % ", ".join( - ( - self.preparer.quote(c) - if isinstance(c, str) - else self.process(c, include_table=False, use_schema=False) - ) - for c in clause.inferred_target_elements - ) - if clause.inferred_target_whereclause is not None: - target_text += " WHERE %s" % self.process( - clause.inferred_target_whereclause, - include_table=False, - use_schema=False, - ) - else: - target_text = "" - - return target_text - - def visit_on_conflict_do_nothing(self, on_conflict, **kw): - target_text = self._on_conflict_target(on_conflict, **kw) - - if target_text: - return "ON CONFLICT %s DO NOTHING" % target_text - else: - return "ON CONFLICT DO NOTHING" - - def visit_on_conflict_do_update(self, on_conflict, **kw): - clause = on_conflict - - target_text = self._on_conflict_target(on_conflict, **kw) - - action_set_ops = [] - - set_parameters = dict(clause.update_values_to_set) - # create a list of column assignment clauses as tuples - - insert_statement = self.stack[-1]["selectable"] - cols = insert_statement.table.c - for c in cols: - col_key = c.key - - if col_key in set_parameters: - value = set_parameters.pop(col_key) - elif c in set_parameters: - value = set_parameters.pop(c) - else: - continue - - if coercions._is_literal(value): - value = elements.BindParameter(None, value, type_=c.type) - - else: - if ( - isinstance(value, elements.BindParameter) - and value.type._isnull - ): - value = value._clone() - value.type = c.type - value_text = self.process(value.self_group(), use_schema=False) - - key_text = self.preparer.quote(c.name) - action_set_ops.append("%s = %s" % (key_text, value_text)) - - # check for names that don't match columns - if set_parameters: - util.warn( - "Additional column names not matching " - "any column keys in table '%s': %s" - % ( - self.current_executable.table.name, - (", ".join("'%s'" % c for c in set_parameters)), - ) - ) - for k, v in set_parameters.items(): - key_text = ( - self.preparer.quote(k) - if isinstance(k, str) - else self.process(k, use_schema=False) - ) - value_text = self.process( - coercions.expect(roles.ExpressionElementRole, v), - use_schema=False, - ) - action_set_ops.append("%s = %s" % (key_text, value_text)) - - action_text = ", ".join(action_set_ops) - if clause.update_whereclause is not None: - action_text += " WHERE %s" % self.process( - clause.update_whereclause, include_table=True, use_schema=False - ) - - return "ON CONFLICT %s DO UPDATE SET %s" % (target_text, action_text) - - def update_from_clause( - self, update_stmt, from_table, extra_froms, from_hints, **kw - ): - kw["asfrom"] = True - return "FROM " + ", ".join( - t._compiler_dispatch(self, fromhints=from_hints, **kw) - for t in extra_froms - ) - - def delete_extra_from_clause( - self, delete_stmt, from_table, extra_froms, from_hints, **kw - ): - """Render the DELETE .. USING clause specific to PostgreSQL.""" - kw["asfrom"] = True - return "USING " + ", ".join( - t._compiler_dispatch(self, fromhints=from_hints, **kw) - for t in extra_froms - ) - - def fetch_clause(self, select, **kw): - # pg requires parens for non literal clauses. It's also required for - # bind parameters if a ::type casts is used by the driver (asyncpg), - # so it's easiest to just always add it - text = "" - if select._offset_clause is not None: - text += "\n OFFSET (%s) ROWS" % self.process( - select._offset_clause, **kw - ) - if select._fetch_clause is not None: - text += "\n FETCH FIRST (%s)%s ROWS %s" % ( - self.process(select._fetch_clause, **kw), - " PERCENT" if select._fetch_clause_options["percent"] else "", - ( - "WITH TIES" - if select._fetch_clause_options["with_ties"] - else "ONLY" - ), - ) - return text - - -class PGDDLCompiler(compiler.DDLCompiler): - def get_column_specification(self, column, **kwargs): - colspec = self.preparer.format_column(column) - impl_type = column.type.dialect_impl(self.dialect) - if isinstance(impl_type, sqltypes.TypeDecorator): - impl_type = impl_type.impl - - has_identity = ( - column.identity is not None - and self.dialect.supports_identity_columns - ) - - if ( - column.primary_key - and column is column.table._autoincrement_column - and ( - self.dialect.supports_smallserial - or not isinstance(impl_type, sqltypes.SmallInteger) - ) - and not has_identity - and ( - column.default is None - or ( - isinstance(column.default, schema.Sequence) - and column.default.optional - ) - ) - ): - if isinstance(impl_type, sqltypes.BigInteger): - colspec += " BIGSERIAL" - elif isinstance(impl_type, sqltypes.SmallInteger): - colspec += " SMALLSERIAL" - else: - colspec += " SERIAL" - else: - colspec += " " + self.dialect.type_compiler_instance.process( - column.type, - type_expression=column, - identifier_preparer=self.preparer, - ) - default = self.get_column_default_string(column) - if default is not None: - colspec += " DEFAULT " + default - - if column.computed is not None: - colspec += " " + self.process(column.computed) - if has_identity: - colspec += " " + self.process(column.identity) - - if not column.nullable and not has_identity: - colspec += " NOT NULL" - elif column.nullable and has_identity: - colspec += " NULL" - return colspec - - def _define_constraint_validity(self, constraint): - not_valid = constraint.dialect_options["postgresql"]["not_valid"] - return " NOT VALID" if not_valid else "" - - def visit_check_constraint(self, constraint, **kw): - if constraint._type_bound: - typ = list(constraint.columns)[0].type - if ( - isinstance(typ, sqltypes.ARRAY) - and isinstance(typ.item_type, sqltypes.Enum) - and not typ.item_type.native_enum - ): - raise exc.CompileError( - "PostgreSQL dialect cannot produce the CHECK constraint " - "for ARRAY of non-native ENUM; please specify " - "create_constraint=False on this Enum datatype." - ) - - text = super().visit_check_constraint(constraint) - text += self._define_constraint_validity(constraint) - return text - - def visit_foreign_key_constraint(self, constraint, **kw): - text = super().visit_foreign_key_constraint(constraint) - text += self._define_constraint_validity(constraint) - return text - - def visit_create_enum_type(self, create, **kw): - type_ = create.element - - return "CREATE TYPE %s AS ENUM (%s)" % ( - self.preparer.format_type(type_), - ", ".join( - self.sql_compiler.process(sql.literal(e), literal_binds=True) - for e in type_.enums - ), - ) - - def visit_drop_enum_type(self, drop, **kw): - type_ = drop.element - - return "DROP TYPE %s" % (self.preparer.format_type(type_)) - - def visit_create_domain_type(self, create, **kw): - domain: DOMAIN = create.element - - options = [] - if domain.collation is not None: - options.append(f"COLLATE {self.preparer.quote(domain.collation)}") - if domain.default is not None: - default = self.render_default_string(domain.default) - options.append(f"DEFAULT {default}") - if domain.constraint_name is not None: - name = self.preparer.truncate_and_render_constraint_name( - domain.constraint_name - ) - options.append(f"CONSTRAINT {name}") - if domain.not_null: - options.append("NOT NULL") - if domain.check is not None: - check = self.sql_compiler.process( - domain.check, include_table=False, literal_binds=True - ) - options.append(f"CHECK ({check})") - - return ( - f"CREATE DOMAIN {self.preparer.format_type(domain)} AS " - f"{self.type_compiler.process(domain.data_type)} " - f"{' '.join(options)}" - ) - - def visit_drop_domain_type(self, drop, **kw): - domain = drop.element - return f"DROP DOMAIN {self.preparer.format_type(domain)}" - - def visit_create_index(self, create, **kw): - preparer = self.preparer - index = create.element - self._verify_index_table(index) - text = "CREATE " - if index.unique: - text += "UNIQUE " - - text += "INDEX " - - if self.dialect._supports_create_index_concurrently: - concurrently = index.dialect_options["postgresql"]["concurrently"] - if concurrently: - text += "CONCURRENTLY " - - if create.if_not_exists: - text += "IF NOT EXISTS " - - text += "%s ON %s " % ( - self._prepared_index_name(index, include_schema=False), - preparer.format_table(index.table), - ) - - using = index.dialect_options["postgresql"]["using"] - if using: - text += ( - "USING %s " - % self.preparer.validate_sql_phrase(using, IDX_USING).lower() - ) - - ops = index.dialect_options["postgresql"]["ops"] - text += "(%s)" % ( - ", ".join( - [ - self.sql_compiler.process( - ( - expr.self_group() - if not isinstance(expr, expression.ColumnClause) - else expr - ), - include_table=False, - literal_binds=True, - ) - + ( - (" " + ops[expr.key]) - if hasattr(expr, "key") and expr.key in ops - else "" - ) - for expr in index.expressions - ] - ) - ) - - includeclause = index.dialect_options["postgresql"]["include"] - if includeclause: - inclusions = [ - index.table.c[col] if isinstance(col, str) else col - for col in includeclause - ] - text += " INCLUDE (%s)" % ", ".join( - [preparer.quote(c.name) for c in inclusions] - ) - - nulls_not_distinct = index.dialect_options["postgresql"][ - "nulls_not_distinct" - ] - if nulls_not_distinct is True: - text += " NULLS NOT DISTINCT" - elif nulls_not_distinct is False: - text += " NULLS DISTINCT" - - withclause = index.dialect_options["postgresql"]["with"] - if withclause: - text += " WITH (%s)" % ( - ", ".join( - [ - "%s = %s" % storage_parameter - for storage_parameter in withclause.items() - ] - ) - ) - - tablespace_name = index.dialect_options["postgresql"]["tablespace"] - if tablespace_name: - text += " TABLESPACE %s" % preparer.quote(tablespace_name) - - whereclause = index.dialect_options["postgresql"]["where"] - if whereclause is not None: - whereclause = coercions.expect( - roles.DDLExpressionRole, whereclause - ) - - where_compiled = self.sql_compiler.process( - whereclause, include_table=False, literal_binds=True - ) - text += " WHERE " + where_compiled - - return text - - def define_unique_constraint_distinct(self, constraint, **kw): - nulls_not_distinct = constraint.dialect_options["postgresql"][ - "nulls_not_distinct" - ] - if nulls_not_distinct is True: - nulls_not_distinct_param = "NULLS NOT DISTINCT " - elif nulls_not_distinct is False: - nulls_not_distinct_param = "NULLS DISTINCT " - else: - nulls_not_distinct_param = "" - return nulls_not_distinct_param - - def visit_drop_index(self, drop, **kw): - index = drop.element - - text = "\nDROP INDEX " - - if self.dialect._supports_drop_index_concurrently: - concurrently = index.dialect_options["postgresql"]["concurrently"] - if concurrently: - text += "CONCURRENTLY " - - if drop.if_exists: - text += "IF EXISTS " - - text += self._prepared_index_name(index, include_schema=True) - return text - - def visit_exclude_constraint(self, constraint, **kw): - text = "" - if constraint.name is not None: - text += "CONSTRAINT %s " % self.preparer.format_constraint( - constraint - ) - elements = [] - kw["include_table"] = False - kw["literal_binds"] = True - for expr, name, op in constraint._render_exprs: - exclude_element = self.sql_compiler.process(expr, **kw) + ( - (" " + constraint.ops[expr.key]) - if hasattr(expr, "key") and expr.key in constraint.ops - else "" - ) - - elements.append("%s WITH %s" % (exclude_element, op)) - text += "EXCLUDE USING %s (%s)" % ( - self.preparer.validate_sql_phrase( - constraint.using, IDX_USING - ).lower(), - ", ".join(elements), - ) - if constraint.where is not None: - text += " WHERE (%s)" % self.sql_compiler.process( - constraint.where, literal_binds=True - ) - text += self.define_constraint_deferrability(constraint) - return text - - def post_create_table(self, table): - table_opts = [] - pg_opts = table.dialect_options["postgresql"] - - inherits = pg_opts.get("inherits") - if inherits is not None: - if not isinstance(inherits, (list, tuple)): - inherits = (inherits,) - table_opts.append( - "\n INHERITS ( " - + ", ".join(self.preparer.quote(name) for name in inherits) - + " )" - ) - - if pg_opts["partition_by"]: - table_opts.append("\n PARTITION BY %s" % pg_opts["partition_by"]) - - if pg_opts["using"]: - table_opts.append("\n USING %s" % pg_opts["using"]) - - if pg_opts["with_oids"] is True: - table_opts.append("\n WITH OIDS") - elif pg_opts["with_oids"] is False: - table_opts.append("\n WITHOUT OIDS") - - if pg_opts["on_commit"]: - on_commit_options = pg_opts["on_commit"].replace("_", " ").upper() - table_opts.append("\n ON COMMIT %s" % on_commit_options) - - if pg_opts["tablespace"]: - tablespace_name = pg_opts["tablespace"] - table_opts.append( - "\n TABLESPACE %s" % self.preparer.quote(tablespace_name) - ) - - return "".join(table_opts) - - def visit_computed_column(self, generated, **kw): - if generated.persisted is False: - raise exc.CompileError( - "PostrgreSQL computed columns do not support 'virtual' " - "persistence; set the 'persisted' flag to None or True for " - "PostgreSQL support." - ) - - return "GENERATED ALWAYS AS (%s) STORED" % self.sql_compiler.process( - generated.sqltext, include_table=False, literal_binds=True - ) - - def visit_create_sequence(self, create, **kw): - prefix = None - if create.element.data_type is not None: - prefix = " AS %s" % self.type_compiler.process( - create.element.data_type - ) - - return super().visit_create_sequence(create, prefix=prefix, **kw) - - def _can_comment_on_constraint(self, ddl_instance): - constraint = ddl_instance.element - if constraint.name is None: - raise exc.CompileError( - f"Can't emit COMMENT ON for constraint {constraint!r}: " - "it has no name" - ) - if constraint.table is None: - raise exc.CompileError( - f"Can't emit COMMENT ON for constraint {constraint!r}: " - "it has no associated table" - ) - - def visit_set_constraint_comment(self, create, **kw): - self._can_comment_on_constraint(create) - return "COMMENT ON CONSTRAINT %s ON %s IS %s" % ( - self.preparer.format_constraint(create.element), - self.preparer.format_table(create.element.table), - self.sql_compiler.render_literal_value( - create.element.comment, sqltypes.String() - ), - ) - - def visit_drop_constraint_comment(self, drop, **kw): - self._can_comment_on_constraint(drop) - return "COMMENT ON CONSTRAINT %s ON %s IS NULL" % ( - self.preparer.format_constraint(drop.element), - self.preparer.format_table(drop.element.table), - ) - - -class PGTypeCompiler(compiler.GenericTypeCompiler): - def visit_TSVECTOR(self, type_, **kw): - return "TSVECTOR" - - def visit_TSQUERY(self, type_, **kw): - return "TSQUERY" - - def visit_INET(self, type_, **kw): - return "INET" - - def visit_CIDR(self, type_, **kw): - return "CIDR" - - def visit_CITEXT(self, type_, **kw): - return "CITEXT" - - def visit_MACADDR(self, type_, **kw): - return "MACADDR" - - def visit_MACADDR8(self, type_, **kw): - return "MACADDR8" - - def visit_MONEY(self, type_, **kw): - return "MONEY" - - def visit_OID(self, type_, **kw): - return "OID" - - def visit_REGCONFIG(self, type_, **kw): - return "REGCONFIG" - - def visit_REGCLASS(self, type_, **kw): - return "REGCLASS" - - def visit_FLOAT(self, type_, **kw): - if not type_.precision: - return "FLOAT" - else: - return "FLOAT(%(precision)s)" % {"precision": type_.precision} - - def visit_double(self, type_, **kw): - return self.visit_DOUBLE_PRECISION(type, **kw) - - def visit_BIGINT(self, type_, **kw): - return "BIGINT" - - def visit_HSTORE(self, type_, **kw): - return "HSTORE" - - def visit_JSON(self, type_, **kw): - return "JSON" - - def visit_JSONB(self, type_, **kw): - return "JSONB" - - def visit_INT4MULTIRANGE(self, type_, **kw): - return "INT4MULTIRANGE" - - def visit_INT8MULTIRANGE(self, type_, **kw): - return "INT8MULTIRANGE" - - def visit_NUMMULTIRANGE(self, type_, **kw): - return "NUMMULTIRANGE" - - def visit_DATEMULTIRANGE(self, type_, **kw): - return "DATEMULTIRANGE" - - def visit_TSMULTIRANGE(self, type_, **kw): - return "TSMULTIRANGE" - - def visit_TSTZMULTIRANGE(self, type_, **kw): - return "TSTZMULTIRANGE" - - def visit_INT4RANGE(self, type_, **kw): - return "INT4RANGE" - - def visit_INT8RANGE(self, type_, **kw): - return "INT8RANGE" - - def visit_NUMRANGE(self, type_, **kw): - return "NUMRANGE" - - def visit_DATERANGE(self, type_, **kw): - return "DATERANGE" - - def visit_TSRANGE(self, type_, **kw): - return "TSRANGE" - - def visit_TSTZRANGE(self, type_, **kw): - return "TSTZRANGE" - - def visit_json_int_index(self, type_, **kw): - return "INT" - - def visit_json_str_index(self, type_, **kw): - return "TEXT" - - def visit_datetime(self, type_, **kw): - return self.visit_TIMESTAMP(type_, **kw) - - def visit_enum(self, type_, **kw): - if not type_.native_enum or not self.dialect.supports_native_enum: - return super().visit_enum(type_, **kw) - else: - return self.visit_ENUM(type_, **kw) - - def visit_ENUM(self, type_, identifier_preparer=None, **kw): - if identifier_preparer is None: - identifier_preparer = self.dialect.identifier_preparer - return identifier_preparer.format_type(type_) - - def visit_DOMAIN(self, type_, identifier_preparer=None, **kw): - if identifier_preparer is None: - identifier_preparer = self.dialect.identifier_preparer - return identifier_preparer.format_type(type_) - - def visit_TIMESTAMP(self, type_, **kw): - return "TIMESTAMP%s %s" % ( - ( - "(%d)" % type_.precision - if getattr(type_, "precision", None) is not None - else "" - ), - (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE", - ) - - def visit_TIME(self, type_, **kw): - return "TIME%s %s" % ( - ( - "(%d)" % type_.precision - if getattr(type_, "precision", None) is not None - else "" - ), - (type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE", - ) - - def visit_INTERVAL(self, type_, **kw): - text = "INTERVAL" - if type_.fields is not None: - text += " " + type_.fields - if type_.precision is not None: - text += " (%d)" % type_.precision - return text - - def visit_BIT(self, type_, **kw): - if type_.varying: - compiled = "BIT VARYING" - if type_.length is not None: - compiled += "(%d)" % type_.length - else: - compiled = "BIT(%d)" % type_.length - return compiled - - def visit_uuid(self, type_, **kw): - if type_.native_uuid: - return self.visit_UUID(type_, **kw) - else: - return super().visit_uuid(type_, **kw) - - def visit_UUID(self, type_, **kw): - return "UUID" - - def visit_large_binary(self, type_, **kw): - return self.visit_BYTEA(type_, **kw) - - def visit_BYTEA(self, type_, **kw): - return "BYTEA" - - def visit_ARRAY(self, type_, **kw): - inner = self.process(type_.item_type, **kw) - return re.sub( - r"((?: COLLATE.*)?)$", - ( - r"%s\1" - % ( - "[]" - * (type_.dimensions if type_.dimensions is not None else 1) - ) - ), - inner, - count=1, - ) - - def visit_json_path(self, type_, **kw): - return self.visit_JSONPATH(type_, **kw) - - def visit_JSONPATH(self, type_, **kw): - return "JSONPATH" - - -class PGIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = RESERVED_WORDS - - def _unquote_identifier(self, value): - if value[0] == self.initial_quote: - value = value[1:-1].replace( - self.escape_to_quote, self.escape_quote - ) - return value - - def format_type(self, type_, use_schema=True): - if not type_.name: - raise exc.CompileError( - f"PostgreSQL {type_.__class__.__name__} type requires a name." - ) - - name = self.quote(type_.name) - effective_schema = self.schema_for_object(type_) - - if ( - not self.omit_schema - and use_schema - and effective_schema is not None - ): - name = f"{self.quote_schema(effective_schema)}.{name}" - return name - - -class ReflectedNamedType(TypedDict): - """Represents a reflected named type.""" - - name: str - """Name of the type.""" - schema: str - """The schema of the type.""" - visible: bool - """Indicates if this type is in the current search path.""" - - -class ReflectedDomainConstraint(TypedDict): - """Represents a reflect check constraint of a domain.""" - - name: str - """Name of the constraint.""" - check: str - """The check constraint text.""" - - -class ReflectedDomain(ReflectedNamedType): - """Represents a reflected enum.""" - - type: str - """The string name of the underlying data type of the domain.""" - nullable: bool - """Indicates if the domain allows null or not.""" - default: Optional[str] - """The string representation of the default value of this domain - or ``None`` if none present. - """ - constraints: List[ReflectedDomainConstraint] - """The constraints defined in the domain, if any. - The constraint are in order of evaluation by postgresql. - """ - collation: Optional[str] - """The collation for the domain.""" - - -class ReflectedEnum(ReflectedNamedType): - """Represents a reflected enum.""" - - labels: List[str] - """The labels that compose the enum.""" - - -class PGInspector(reflection.Inspector): - dialect: PGDialect - - def get_table_oid( - self, table_name: str, schema: Optional[str] = None - ) -> int: - """Return the OID for the given table name. - - :param table_name: string name of the table. For special quoting, - use :class:`.quoted_name`. - - :param schema: string schema name; if omitted, uses the default schema - of the database connection. For special quoting, - use :class:`.quoted_name`. - - """ - - with self._operation_context() as conn: - return self.dialect.get_table_oid( - conn, table_name, schema, info_cache=self.info_cache - ) - - def get_domains( - self, schema: Optional[str] = None - ) -> List[ReflectedDomain]: - """Return a list of DOMAIN objects. - - Each member is a dictionary containing these fields: - - * name - name of the domain - * schema - the schema name for the domain. - * visible - boolean, whether or not this domain is visible - in the default search path. - * type - the type defined by this domain. - * nullable - Indicates if this domain can be ``NULL``. - * default - The default value of the domain or ``None`` if the - domain has no default. - * constraints - A list of dict wit the constraint defined by this - domain. Each element constaints two keys: ``name`` of the - constraint and ``check`` with the constraint text. - - :param schema: schema name. If None, the default schema - (typically 'public') is used. May also be set to ``'*'`` to - indicate load domains for all schemas. - - .. versionadded:: 2.0 - - """ - with self._operation_context() as conn: - return self.dialect._load_domains( - conn, schema, info_cache=self.info_cache - ) - - def get_enums(self, schema: Optional[str] = None) -> List[ReflectedEnum]: - """Return a list of ENUM objects. - - Each member is a dictionary containing these fields: - - * name - name of the enum - * schema - the schema name for the enum. - * visible - boolean, whether or not this enum is visible - in the default search path. - * labels - a list of string labels that apply to the enum. - - :param schema: schema name. If None, the default schema - (typically 'public') is used. May also be set to ``'*'`` to - indicate load enums for all schemas. - - """ - with self._operation_context() as conn: - return self.dialect._load_enums( - conn, schema, info_cache=self.info_cache - ) - - def get_foreign_table_names( - self, schema: Optional[str] = None - ) -> List[str]: - """Return a list of FOREIGN TABLE names. - - Behavior is similar to that of - :meth:`_reflection.Inspector.get_table_names`, - except that the list is limited to those tables that report a - ``relkind`` value of ``f``. - - """ - with self._operation_context() as conn: - return self.dialect._get_foreign_table_names( - conn, schema, info_cache=self.info_cache - ) - - def has_type( - self, type_name: str, schema: Optional[str] = None, **kw: Any - ) -> bool: - """Return if the database has the specified type in the provided - schema. - - :param type_name: the type to check. - :param schema: schema name. If None, the default schema - (typically 'public') is used. May also be set to ``'*'`` to - check in all schemas. - - .. versionadded:: 2.0 - - """ - with self._operation_context() as conn: - return self.dialect.has_type( - conn, type_name, schema, info_cache=self.info_cache - ) - - -class PGExecutionContext(default.DefaultExecutionContext): - def fire_sequence(self, seq, type_): - return self._execute_scalar( - ( - "select nextval('%s')" - % self.identifier_preparer.format_sequence(seq) - ), - type_, - ) - - def get_insert_default(self, column): - if column.primary_key and column is column.table._autoincrement_column: - if column.server_default and column.server_default.has_argument: - # pre-execute passive defaults on primary key columns - return self._execute_scalar( - "select %s" % column.server_default.arg, column.type - ) - - elif column.default is None or ( - column.default.is_sequence and column.default.optional - ): - # execute the sequence associated with a SERIAL primary - # key column. for non-primary-key SERIAL, the ID just - # generates server side. - - try: - seq_name = column._postgresql_seq_name - except AttributeError: - tab = column.table.name - col = column.name - tab = tab[0 : 29 + max(0, (29 - len(col)))] - col = col[0 : 29 + max(0, (29 - len(tab)))] - name = "%s_%s_seq" % (tab, col) - column._postgresql_seq_name = seq_name = name - - if column.table is not None: - effective_schema = self.connection.schema_for_object( - column.table - ) - else: - effective_schema = None - - if effective_schema is not None: - exc = 'select nextval(\'"%s"."%s"\')' % ( - effective_schema, - seq_name, - ) - else: - exc = "select nextval('\"%s\"')" % (seq_name,) - - return self._execute_scalar(exc, column.type) - - return super().get_insert_default(column) - - -class PGReadOnlyConnectionCharacteristic( - characteristics.ConnectionCharacteristic -): - transactional = True - - def reset_characteristic(self, dialect, dbapi_conn): - dialect.set_readonly(dbapi_conn, False) - - def set_characteristic(self, dialect, dbapi_conn, value): - dialect.set_readonly(dbapi_conn, value) - - def get_characteristic(self, dialect, dbapi_conn): - return dialect.get_readonly(dbapi_conn) - - -class PGDeferrableConnectionCharacteristic( - characteristics.ConnectionCharacteristic -): - transactional = True - - def reset_characteristic(self, dialect, dbapi_conn): - dialect.set_deferrable(dbapi_conn, False) - - def set_characteristic(self, dialect, dbapi_conn, value): - dialect.set_deferrable(dbapi_conn, value) - - def get_characteristic(self, dialect, dbapi_conn): - return dialect.get_deferrable(dbapi_conn) - - -class PGDialect(default.DefaultDialect): - name = "postgresql" - supports_statement_cache = True - supports_alter = True - max_identifier_length = 63 - supports_sane_rowcount = True - - bind_typing = interfaces.BindTyping.RENDER_CASTS - - supports_native_enum = True - supports_native_boolean = True - supports_native_uuid = True - supports_smallserial = True - - supports_sequences = True - sequences_optional = True - preexecute_autoincrement_sequences = True - postfetch_lastrowid = False - use_insertmanyvalues = True - - returns_native_bytes = True - - insertmanyvalues_implicit_sentinel = ( - InsertmanyvaluesSentinelOpts.ANY_AUTOINCREMENT - | InsertmanyvaluesSentinelOpts.USE_INSERT_FROM_SELECT - | InsertmanyvaluesSentinelOpts.RENDER_SELECT_COL_CASTS - ) - - supports_comments = True - supports_constraint_comments = True - supports_default_values = True - - supports_default_metavalue = True - - supports_empty_insert = False - supports_multivalues_insert = True - - supports_identity_columns = True - - default_paramstyle = "pyformat" - ischema_names = ischema_names - colspecs = colspecs - - statement_compiler = PGCompiler - ddl_compiler = PGDDLCompiler - type_compiler_cls = PGTypeCompiler - preparer = PGIdentifierPreparer - execution_ctx_cls = PGExecutionContext - inspector = PGInspector - - update_returning = True - delete_returning = True - insert_returning = True - update_returning_multifrom = True - delete_returning_multifrom = True - - connection_characteristics = ( - default.DefaultDialect.connection_characteristics - ) - connection_characteristics = connection_characteristics.union( - { - "postgresql_readonly": PGReadOnlyConnectionCharacteristic(), - "postgresql_deferrable": PGDeferrableConnectionCharacteristic(), - } - ) - - construct_arguments = [ - ( - schema.Index, - { - "using": False, - "include": None, - "where": None, - "ops": {}, - "concurrently": False, - "with": {}, - "tablespace": None, - "nulls_not_distinct": None, - }, - ), - ( - schema.Table, - { - "ignore_search_path": False, - "tablespace": None, - "partition_by": None, - "with_oids": None, - "on_commit": None, - "inherits": None, - "using": None, - }, - ), - ( - schema.CheckConstraint, - { - "not_valid": False, - }, - ), - ( - schema.ForeignKeyConstraint, - { - "not_valid": False, - }, - ), - ( - schema.UniqueConstraint, - {"nulls_not_distinct": None}, - ), - ] - - reflection_options = ("postgresql_ignore_search_path",) - - _backslash_escapes = True - _supports_create_index_concurrently = True - _supports_drop_index_concurrently = True - - def __init__( - self, - native_inet_types=None, - json_serializer=None, - json_deserializer=None, - **kwargs, - ): - default.DefaultDialect.__init__(self, **kwargs) - - self._native_inet_types = native_inet_types - self._json_deserializer = json_deserializer - self._json_serializer = json_serializer - - def initialize(self, connection): - super().initialize(connection) - - # https://www.postgresql.org/docs/9.3/static/release-9-2.html#AEN116689 - self.supports_smallserial = self.server_version_info >= (9, 2) - - self._set_backslash_escapes(connection) - - self._supports_drop_index_concurrently = self.server_version_info >= ( - 9, - 2, - ) - self.supports_identity_columns = self.server_version_info >= (10,) - - def get_isolation_level_values(self, dbapi_conn): - # note the generic dialect doesn't have AUTOCOMMIT, however - # all postgresql dialects should include AUTOCOMMIT. - return ( - "SERIALIZABLE", - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - ) - - def set_isolation_level(self, dbapi_connection, level): - cursor = dbapi_connection.cursor() - cursor.execute( - "SET SESSION CHARACTERISTICS AS TRANSACTION " - f"ISOLATION LEVEL {level}" - ) - cursor.execute("COMMIT") - cursor.close() - - def get_isolation_level(self, dbapi_connection): - cursor = dbapi_connection.cursor() - cursor.execute("show transaction isolation level") - val = cursor.fetchone()[0] - cursor.close() - return val.upper() - - def set_readonly(self, connection, value): - raise NotImplementedError() - - def get_readonly(self, connection): - raise NotImplementedError() - - def set_deferrable(self, connection, value): - raise NotImplementedError() - - def get_deferrable(self, connection): - raise NotImplementedError() - - def _split_multihost_from_url(self, url: URL) -> Union[ - Tuple[None, None], - Tuple[Tuple[Optional[str], ...], Tuple[Optional[int], ...]], - ]: - hosts: Optional[Tuple[Optional[str], ...]] = None - ports_str: Union[str, Tuple[Optional[str], ...], None] = None - - integrated_multihost = False - - if "host" in url.query: - if isinstance(url.query["host"], (list, tuple)): - integrated_multihost = True - hosts, ports_str = zip( - *[ - token.split(":") if ":" in token else (token, None) - for token in url.query["host"] - ] - ) - - elif isinstance(url.query["host"], str): - hosts = tuple(url.query["host"].split(",")) - - if ( - "port" not in url.query - and len(hosts) == 1 - and ":" in hosts[0] - ): - # internet host is alphanumeric plus dots or hyphens. - # this is essentially rfc1123, which refers to rfc952. - # https://stackoverflow.com/questions/3523028/ - # valid-characters-of-a-hostname - host_port_match = re.match( - r"^([a-zA-Z0-9\-\.]*)(?:\:(\d*))?$", hosts[0] - ) - if host_port_match: - integrated_multihost = True - h, p = host_port_match.group(1, 2) - if TYPE_CHECKING: - assert isinstance(h, str) - assert isinstance(p, str) - hosts = (h,) - ports_str = cast( - "Tuple[Optional[str], ...]", (p,) if p else (None,) - ) - - if "port" in url.query: - if integrated_multihost: - raise exc.ArgumentError( - "Can't mix 'multihost' formats together; use " - '"host=h1,h2,h3&port=p1,p2,p3" or ' - '"host=h1:p1&host=h2:p2&host=h3:p3" separately' - ) - if isinstance(url.query["port"], (list, tuple)): - ports_str = url.query["port"] - elif isinstance(url.query["port"], str): - ports_str = tuple(url.query["port"].split(",")) - - ports: Optional[Tuple[Optional[int], ...]] = None - - if ports_str: - try: - ports = tuple(int(x) if x else None for x in ports_str) - except ValueError: - raise exc.ArgumentError( - f"Received non-integer port arguments: {ports_str}" - ) from None - - if ports and ( - (not hosts and len(ports) > 1) - or ( - hosts - and ports - and len(hosts) != len(ports) - and (len(hosts) > 1 or len(ports) > 1) - ) - ): - raise exc.ArgumentError("number of hosts and ports don't match") - - if hosts is not None: - if ports is None: - ports = tuple(None for _ in hosts) - - return hosts, ports # type: ignore - - def do_begin_twophase(self, connection, xid): - self.do_begin(connection.connection) - - def do_prepare_twophase(self, connection, xid): - connection.exec_driver_sql("PREPARE TRANSACTION '%s'" % xid) - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if is_prepared: - if recover: - # FIXME: ugly hack to get out of transaction - # context when committing recoverable transactions - # Must find out a way how to make the dbapi not - # open a transaction. - connection.exec_driver_sql("ROLLBACK") - connection.exec_driver_sql("ROLLBACK PREPARED '%s'" % xid) - connection.exec_driver_sql("BEGIN") - self.do_rollback(connection.connection) - else: - self.do_rollback(connection.connection) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if is_prepared: - if recover: - connection.exec_driver_sql("ROLLBACK") - connection.exec_driver_sql("COMMIT PREPARED '%s'" % xid) - connection.exec_driver_sql("BEGIN") - self.do_rollback(connection.connection) - else: - self.do_commit(connection.connection) - - def do_recover_twophase(self, connection): - return connection.scalars( - sql.text("SELECT gid FROM pg_prepared_xacts") - ).all() - - def _get_default_schema_name(self, connection): - return connection.exec_driver_sql("select current_schema()").scalar() - - @reflection.cache - def has_schema(self, connection, schema, **kw): - query = select(pg_catalog.pg_namespace.c.nspname).where( - pg_catalog.pg_namespace.c.nspname == schema - ) - return bool(connection.scalar(query)) - - def _pg_class_filter_scope_schema( - self, query, schema, scope, pg_class_table=None - ): - if pg_class_table is None: - pg_class_table = pg_catalog.pg_class - query = query.join( - pg_catalog.pg_namespace, - pg_catalog.pg_namespace.c.oid == pg_class_table.c.relnamespace, - ) - - if scope is ObjectScope.DEFAULT: - query = query.where(pg_class_table.c.relpersistence != "t") - elif scope is ObjectScope.TEMPORARY: - query = query.where(pg_class_table.c.relpersistence == "t") - - if schema is None: - query = query.where( - pg_catalog.pg_table_is_visible(pg_class_table.c.oid), - # ignore pg_catalog schema - pg_catalog.pg_namespace.c.nspname != "pg_catalog", - ) - else: - query = query.where(pg_catalog.pg_namespace.c.nspname == schema) - return query - - def _pg_class_relkind_condition(self, relkinds, pg_class_table=None): - if pg_class_table is None: - pg_class_table = pg_catalog.pg_class - # uses the any form instead of in otherwise postgresql complaings - # that 'IN could not convert type character to "char"' - return pg_class_table.c.relkind == sql.any_(_array.array(relkinds)) - - @lru_cache() - def _has_table_query(self, schema): - query = select(pg_catalog.pg_class.c.relname).where( - pg_catalog.pg_class.c.relname == bindparam("table_name"), - self._pg_class_relkind_condition( - pg_catalog.RELKINDS_ALL_TABLE_LIKE - ), - ) - return self._pg_class_filter_scope_schema( - query, schema, scope=ObjectScope.ANY - ) - - @reflection.cache - def has_table(self, connection, table_name, schema=None, **kw): - self._ensure_has_table_connection(connection) - query = self._has_table_query(schema) - return bool(connection.scalar(query, {"table_name": table_name})) - - @reflection.cache - def has_sequence(self, connection, sequence_name, schema=None, **kw): - query = select(pg_catalog.pg_class.c.relname).where( - pg_catalog.pg_class.c.relkind == "S", - pg_catalog.pg_class.c.relname == sequence_name, - ) - query = self._pg_class_filter_scope_schema( - query, schema, scope=ObjectScope.ANY - ) - return bool(connection.scalar(query)) - - @reflection.cache - def has_type(self, connection, type_name, schema=None, **kw): - query = ( - select(pg_catalog.pg_type.c.typname) - .join( - pg_catalog.pg_namespace, - pg_catalog.pg_namespace.c.oid - == pg_catalog.pg_type.c.typnamespace, - ) - .where(pg_catalog.pg_type.c.typname == type_name) - ) - if schema is None: - query = query.where( - pg_catalog.pg_type_is_visible(pg_catalog.pg_type.c.oid), - # ignore pg_catalog schema - pg_catalog.pg_namespace.c.nspname != "pg_catalog", - ) - elif schema != "*": - query = query.where(pg_catalog.pg_namespace.c.nspname == schema) - - return bool(connection.scalar(query)) - - def _get_server_version_info(self, connection): - v = connection.exec_driver_sql("select pg_catalog.version()").scalar() - m = re.match( - r".*(?:PostgreSQL|EnterpriseDB) " - r"(\d+)\.?(\d+)?(?:\.(\d+))?(?:\.\d+)?(?:devel|beta)?", - v, - ) - if not m: - raise AssertionError( - "Could not determine version from string '%s'" % v - ) - return tuple([int(x) for x in m.group(1, 2, 3) if x is not None]) - - @reflection.cache - def get_table_oid(self, connection, table_name, schema=None, **kw): - """Fetch the oid for schema.table_name.""" - query = select(pg_catalog.pg_class.c.oid).where( - pg_catalog.pg_class.c.relname == table_name, - self._pg_class_relkind_condition( - pg_catalog.RELKINDS_ALL_TABLE_LIKE - ), - ) - query = self._pg_class_filter_scope_schema( - query, schema, scope=ObjectScope.ANY - ) - table_oid = connection.scalar(query) - if table_oid is None: - raise exc.NoSuchTableError( - f"{schema}.{table_name}" if schema else table_name - ) - return table_oid - - @reflection.cache - def get_schema_names(self, connection, **kw): - query = ( - select(pg_catalog.pg_namespace.c.nspname) - .where(pg_catalog.pg_namespace.c.nspname.not_like("pg_%")) - .order_by(pg_catalog.pg_namespace.c.nspname) - ) - return connection.scalars(query).all() - - def _get_relnames_for_relkinds(self, connection, schema, relkinds, scope): - query = select(pg_catalog.pg_class.c.relname).where( - self._pg_class_relkind_condition(relkinds) - ) - query = self._pg_class_filter_scope_schema(query, schema, scope=scope) - return connection.scalars(query).all() - - @reflection.cache - def get_table_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, - schema, - pg_catalog.RELKINDS_TABLE_NO_FOREIGN, - scope=ObjectScope.DEFAULT, - ) - - @reflection.cache - def get_temp_table_names(self, connection, **kw): - return self._get_relnames_for_relkinds( - connection, - schema=None, - relkinds=pg_catalog.RELKINDS_TABLE_NO_FOREIGN, - scope=ObjectScope.TEMPORARY, - ) - - @reflection.cache - def _get_foreign_table_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, schema, relkinds=("f",), scope=ObjectScope.ANY - ) - - @reflection.cache - def get_view_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, - schema, - pg_catalog.RELKINDS_VIEW, - scope=ObjectScope.DEFAULT, - ) - - @reflection.cache - def get_materialized_view_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, - schema, - pg_catalog.RELKINDS_MAT_VIEW, - scope=ObjectScope.DEFAULT, - ) - - @reflection.cache - def get_temp_view_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, - schema, - # NOTE: do not include temp materialzied views (that do not - # seem to be a thing at least up to version 14) - pg_catalog.RELKINDS_VIEW, - scope=ObjectScope.TEMPORARY, - ) - - @reflection.cache - def get_sequence_names(self, connection, schema=None, **kw): - return self._get_relnames_for_relkinds( - connection, schema, relkinds=("S",), scope=ObjectScope.ANY - ) - - @reflection.cache - def get_view_definition(self, connection, view_name, schema=None, **kw): - query = ( - select(pg_catalog.pg_get_viewdef(pg_catalog.pg_class.c.oid)) - .select_from(pg_catalog.pg_class) - .where( - pg_catalog.pg_class.c.relname == view_name, - self._pg_class_relkind_condition( - pg_catalog.RELKINDS_VIEW + pg_catalog.RELKINDS_MAT_VIEW - ), - ) - ) - query = self._pg_class_filter_scope_schema( - query, schema, scope=ObjectScope.ANY - ) - res = connection.scalar(query) - if res is None: - raise exc.NoSuchTableError( - f"{schema}.{view_name}" if schema else view_name - ) - else: - return res - - def _value_or_raise(self, data, table, schema): - try: - return dict(data)[(schema, table)] - except KeyError: - raise exc.NoSuchTableError( - f"{schema}.{table}" if schema else table - ) from None - - def _prepare_filter_names(self, filter_names): - if filter_names: - return True, {"filter_names": filter_names} - else: - return False, {} - - def _kind_to_relkinds(self, kind: ObjectKind) -> Tuple[str, ...]: - if kind is ObjectKind.ANY: - return pg_catalog.RELKINDS_ALL_TABLE_LIKE - relkinds = () - if ObjectKind.TABLE in kind: - relkinds += pg_catalog.RELKINDS_TABLE - if ObjectKind.VIEW in kind: - relkinds += pg_catalog.RELKINDS_VIEW - if ObjectKind.MATERIALIZED_VIEW in kind: - relkinds += pg_catalog.RELKINDS_MAT_VIEW - return relkinds - - @reflection.cache - def get_columns(self, connection, table_name, schema=None, **kw): - data = self.get_multi_columns( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _columns_query(self, schema, has_filter_names, scope, kind): - # NOTE: the query with the default and identity options scalar - # subquery is faster than trying to use outer joins for them - generated = ( - pg_catalog.pg_attribute.c.attgenerated.label("generated") - if self.server_version_info >= (12,) - else sql.null().label("generated") - ) - if self.server_version_info >= (10,): - # join lateral performs worse (~2x slower) than a scalar_subquery - identity = ( - select( - sql.func.json_build_object( - "always", - pg_catalog.pg_attribute.c.attidentity == "a", - "start", - pg_catalog.pg_sequence.c.seqstart, - "increment", - pg_catalog.pg_sequence.c.seqincrement, - "minvalue", - pg_catalog.pg_sequence.c.seqmin, - "maxvalue", - pg_catalog.pg_sequence.c.seqmax, - "cache", - pg_catalog.pg_sequence.c.seqcache, - "cycle", - pg_catalog.pg_sequence.c.seqcycle, - ) - ) - .select_from(pg_catalog.pg_sequence) - .where( - # attidentity != '' is required or it will reflect also - # serial columns as identity. - pg_catalog.pg_attribute.c.attidentity != "", - pg_catalog.pg_sequence.c.seqrelid - == sql.cast( - sql.cast( - pg_catalog.pg_get_serial_sequence( - sql.cast( - sql.cast( - pg_catalog.pg_attribute.c.attrelid, - REGCLASS, - ), - TEXT, - ), - pg_catalog.pg_attribute.c.attname, - ), - REGCLASS, - ), - OID, - ), - ) - .correlate(pg_catalog.pg_attribute) - .scalar_subquery() - .label("identity_options") - ) - else: - identity = sql.null().label("identity_options") - - # join lateral performs the same as scalar_subquery here - default = ( - select( - pg_catalog.pg_get_expr( - pg_catalog.pg_attrdef.c.adbin, - pg_catalog.pg_attrdef.c.adrelid, - ) - ) - .select_from(pg_catalog.pg_attrdef) - .where( - pg_catalog.pg_attrdef.c.adrelid - == pg_catalog.pg_attribute.c.attrelid, - pg_catalog.pg_attrdef.c.adnum - == pg_catalog.pg_attribute.c.attnum, - pg_catalog.pg_attribute.c.atthasdef, - ) - .correlate(pg_catalog.pg_attribute) - .scalar_subquery() - .label("default") - ) - relkinds = self._kind_to_relkinds(kind) - query = ( - select( - pg_catalog.pg_attribute.c.attname.label("name"), - pg_catalog.format_type( - pg_catalog.pg_attribute.c.atttypid, - pg_catalog.pg_attribute.c.atttypmod, - ).label("format_type"), - default, - pg_catalog.pg_attribute.c.attnotnull.label("not_null"), - pg_catalog.pg_class.c.relname.label("table_name"), - pg_catalog.pg_description.c.description.label("comment"), - generated, - identity, - ) - .select_from(pg_catalog.pg_class) - # NOTE: postgresql support table with no user column, meaning - # there is no row with pg_attribute.attnum > 0. use a left outer - # join to avoid filtering these tables. - .outerjoin( - pg_catalog.pg_attribute, - sql.and_( - pg_catalog.pg_class.c.oid - == pg_catalog.pg_attribute.c.attrelid, - pg_catalog.pg_attribute.c.attnum > 0, - ~pg_catalog.pg_attribute.c.attisdropped, - ), - ) - .outerjoin( - pg_catalog.pg_description, - sql.and_( - pg_catalog.pg_description.c.objoid - == pg_catalog.pg_attribute.c.attrelid, - pg_catalog.pg_description.c.objsubid - == pg_catalog.pg_attribute.c.attnum, - ), - ) - .where(self._pg_class_relkind_condition(relkinds)) - .order_by( - pg_catalog.pg_class.c.relname, pg_catalog.pg_attribute.c.attnum - ) - ) - query = self._pg_class_filter_scope_schema(query, schema, scope=scope) - if has_filter_names: - query = query.where( - pg_catalog.pg_class.c.relname.in_(bindparam("filter_names")) - ) - return query - - def get_multi_columns( - self, connection, schema, filter_names, scope, kind, **kw - ): - has_filter_names, params = self._prepare_filter_names(filter_names) - query = self._columns_query(schema, has_filter_names, scope, kind) - rows = connection.execute(query, params).mappings() - - # dictionary with (name, ) if default search path or (schema, name) - # as keys - domains = { - ((d["schema"], d["name"]) if not d["visible"] else (d["name"],)): d - for d in self._load_domains( - connection, schema="*", info_cache=kw.get("info_cache") - ) - } - - # dictionary with (name, ) if default search path or (schema, name) - # as keys - enums = dict( - ( - ((rec["name"],), rec) - if rec["visible"] - else ((rec["schema"], rec["name"]), rec) - ) - for rec in self._load_enums( - connection, schema="*", info_cache=kw.get("info_cache") - ) - ) - - columns = self._get_columns_info(rows, domains, enums, schema) - - return columns.items() - - _format_type_args_pattern = re.compile(r"\((.*)\)") - _format_type_args_delim = re.compile(r"\s*,\s*") - _format_array_spec_pattern = re.compile(r"((?:\[\])*)$") - - def _reflect_type( - self, - format_type: Optional[str], - domains: dict[str, ReflectedDomain], - enums: dict[str, ReflectedEnum], - type_description: str, - ) -> sqltypes.TypeEngine[Any]: - """ - Attempts to reconstruct a column type defined in ischema_names based - on the information available in the format_type. - - If the `format_type` cannot be associated with a known `ischema_names`, - it is treated as a reference to a known PostgreSQL named `ENUM` or - `DOMAIN` type. - """ - type_description = type_description or "unknown type" - if format_type is None: - util.warn( - "PostgreSQL format_type() returned NULL for %s" - % type_description - ) - return sqltypes.NULLTYPE - - attype_args_match = self._format_type_args_pattern.search(format_type) - if attype_args_match and attype_args_match.group(1): - attype_args = self._format_type_args_delim.split( - attype_args_match.group(1) - ) - else: - attype_args = () - - match_array_dim = self._format_array_spec_pattern.search(format_type) - # Each "[]" in array specs corresponds to an array dimension - array_dim = len(match_array_dim.group(1) or "") // 2 - - # Remove all parameters and array specs from format_type to obtain an - # ischema_name candidate - attype = self._format_type_args_pattern.sub("", format_type) - attype = self._format_array_spec_pattern.sub("", attype) - - schema_type = self.ischema_names.get(attype.lower(), None) - args, kwargs = (), {} - - if attype == "numeric": - if len(attype_args) == 2: - precision, scale = map(int, attype_args) - args = (precision, scale) - - elif attype == "double precision": - args = (53,) - - elif attype == "integer": - args = () - - elif attype in ("timestamp with time zone", "time with time zone"): - kwargs["timezone"] = True - if len(attype_args) == 1: - kwargs["precision"] = int(attype_args[0]) - - elif attype in ( - "timestamp without time zone", - "time without time zone", - "time", - ): - kwargs["timezone"] = False - if len(attype_args) == 1: - kwargs["precision"] = int(attype_args[0]) - - elif attype == "bit varying": - kwargs["varying"] = True - if len(attype_args) == 1: - charlen = int(attype_args[0]) - args = (charlen,) - - elif attype.startswith("interval"): - schema_type = INTERVAL - - field_match = re.match(r"interval (.+)", attype) - if field_match: - kwargs["fields"] = field_match.group(1) - - if len(attype_args) == 1: - kwargs["precision"] = int(attype_args[0]) - - else: - enum_or_domain_key = tuple(util.quoted_token_parser(attype)) - - if enum_or_domain_key in enums: - schema_type = ENUM - enum = enums[enum_or_domain_key] - - args = tuple(enum["labels"]) - kwargs["name"] = enum["name"] - - if not enum["visible"]: - kwargs["schema"] = enum["schema"] - args = tuple(enum["labels"]) - elif enum_or_domain_key in domains: - schema_type = DOMAIN - domain = domains[enum_or_domain_key] - - data_type = self._reflect_type( - domain["type"], - domains, - enums, - type_description="DOMAIN '%s'" % domain["name"], - ) - args = (domain["name"], data_type) - - kwargs["collation"] = domain["collation"] - kwargs["default"] = domain["default"] - kwargs["not_null"] = not domain["nullable"] - kwargs["create_type"] = False - - if domain["constraints"]: - # We only support a single constraint - check_constraint = domain["constraints"][0] - - kwargs["constraint_name"] = check_constraint["name"] - kwargs["check"] = check_constraint["check"] - - if not domain["visible"]: - kwargs["schema"] = domain["schema"] - - else: - try: - charlen = int(attype_args[0]) - args = (charlen, *attype_args[1:]) - except (ValueError, IndexError): - args = attype_args - - if not schema_type: - util.warn( - "Did not recognize type '%s' of %s" - % (attype, type_description) - ) - return sqltypes.NULLTYPE - - data_type = schema_type(*args, **kwargs) - if array_dim >= 1: - # postgres does not preserve dimensionality or size of array types. - data_type = _array.ARRAY(data_type) - - return data_type - - def _get_columns_info(self, rows, domains, enums, schema): - columns = defaultdict(list) - for row_dict in rows: - # ensure that each table has an entry, even if it has no columns - if row_dict["name"] is None: - columns[(schema, row_dict["table_name"])] = ( - ReflectionDefaults.columns() - ) - continue - table_cols = columns[(schema, row_dict["table_name"])] - - coltype = self._reflect_type( - row_dict["format_type"], - domains, - enums, - type_description="column '%s'" % row_dict["name"], - ) - - default = row_dict["default"] - name = row_dict["name"] - generated = row_dict["generated"] - nullable = not row_dict["not_null"] - - if isinstance(coltype, DOMAIN): - if not default: - # domain can override the default value but - # cant set it to None - if coltype.default is not None: - default = coltype.default - - nullable = nullable and not coltype.not_null - - identity = row_dict["identity_options"] - - # If a zero byte or blank string depending on driver (is also - # absent for older PG versions), then not a generated column. - # Otherwise, s = stored. (Other values might be added in the - # future.) - if generated not in (None, "", b"\x00"): - computed = dict( - sqltext=default, persisted=generated in ("s", b"s") - ) - default = None - else: - computed = None - - # adjust the default value - autoincrement = False - if default is not None: - match = re.search(r"""(nextval\(')([^']+)('.*$)""", default) - if match is not None: - if issubclass(coltype._type_affinity, sqltypes.Integer): - autoincrement = True - # the default is related to a Sequence - if "." not in match.group(2) and schema is not None: - # unconditionally quote the schema name. this could - # later be enhanced to obey quoting rules / - # "quote schema" - default = ( - match.group(1) - + ('"%s"' % schema) - + "." - + match.group(2) - + match.group(3) - ) - - column_info = { - "name": name, - "type": coltype, - "nullable": nullable, - "default": default, - "autoincrement": autoincrement or identity is not None, - "comment": row_dict["comment"], - } - if computed is not None: - column_info["computed"] = computed - if identity is not None: - column_info["identity"] = identity - - table_cols.append(column_info) - - return columns - - @lru_cache() - def _table_oids_query(self, schema, has_filter_names, scope, kind): - relkinds = self._kind_to_relkinds(kind) - oid_q = select( - pg_catalog.pg_class.c.oid, pg_catalog.pg_class.c.relname - ).where(self._pg_class_relkind_condition(relkinds)) - oid_q = self._pg_class_filter_scope_schema(oid_q, schema, scope=scope) - - if has_filter_names: - oid_q = oid_q.where( - pg_catalog.pg_class.c.relname.in_(bindparam("filter_names")) - ) - return oid_q - - @reflection.flexi_cache( - ("schema", InternalTraversal.dp_string), - ("filter_names", InternalTraversal.dp_string_list), - ("kind", InternalTraversal.dp_plain_obj), - ("scope", InternalTraversal.dp_plain_obj), - ) - def _get_table_oids( - self, connection, schema, filter_names, scope, kind, **kw - ): - has_filter_names, params = self._prepare_filter_names(filter_names) - oid_q = self._table_oids_query(schema, has_filter_names, scope, kind) - result = connection.execute(oid_q, params) - return result.all() - - @lru_cache() - def _constraint_query(self, is_unique): - con_sq = ( - select( - pg_catalog.pg_constraint.c.conrelid, - pg_catalog.pg_constraint.c.conname, - pg_catalog.pg_constraint.c.conindid, - sql.func.unnest(pg_catalog.pg_constraint.c.conkey).label( - "attnum" - ), - sql.func.generate_subscripts( - pg_catalog.pg_constraint.c.conkey, 1 - ).label("ord"), - pg_catalog.pg_description.c.description, - ) - .outerjoin( - pg_catalog.pg_description, - pg_catalog.pg_description.c.objoid - == pg_catalog.pg_constraint.c.oid, - ) - .where( - pg_catalog.pg_constraint.c.contype == bindparam("contype"), - pg_catalog.pg_constraint.c.conrelid.in_(bindparam("oids")), - ) - .subquery("con") - ) - - attr_sq = ( - select( - con_sq.c.conrelid, - con_sq.c.conname, - con_sq.c.conindid, - con_sq.c.description, - con_sq.c.ord, - pg_catalog.pg_attribute.c.attname, - ) - .select_from(pg_catalog.pg_attribute) - .join( - con_sq, - sql.and_( - pg_catalog.pg_attribute.c.attnum == con_sq.c.attnum, - pg_catalog.pg_attribute.c.attrelid == con_sq.c.conrelid, - ), - ) - .where( - # NOTE: restate the condition here, since pg15 otherwise - # seems to get confused on pscopg2 sometimes, doing - # a sequential scan of pg_attribute. - # The condition in the con_sq subquery is not actually needed - # in pg15, but it may be needed in older versions. Keeping it - # does not seems to have any inpact in any case. - con_sq.c.conrelid.in_(bindparam("oids")) - ) - .subquery("attr") - ) - - constraint_query = ( - select( - attr_sq.c.conrelid, - sql.func.array_agg( - # NOTE: cast since some postgresql derivatives may - # not support array_agg on the name type - aggregate_order_by( - attr_sq.c.attname.cast(TEXT), attr_sq.c.ord - ) - ).label("cols"), - attr_sq.c.conname, - sql.func.min(attr_sq.c.description).label("description"), - ) - .group_by(attr_sq.c.conrelid, attr_sq.c.conname) - .order_by(attr_sq.c.conrelid, attr_sq.c.conname) - ) - - if is_unique: - if self.server_version_info >= (15,): - constraint_query = constraint_query.join( - pg_catalog.pg_index, - attr_sq.c.conindid == pg_catalog.pg_index.c.indexrelid, - ).add_columns( - sql.func.bool_and( - pg_catalog.pg_index.c.indnullsnotdistinct - ).label("indnullsnotdistinct") - ) - else: - constraint_query = constraint_query.add_columns( - sql.false().label("indnullsnotdistinct") - ) - else: - constraint_query = constraint_query.add_columns( - sql.null().label("extra") - ) - return constraint_query - - def _reflect_constraint( - self, connection, contype, schema, filter_names, scope, kind, **kw - ): - # used to reflect primary and unique constraint - table_oids = self._get_table_oids( - connection, schema, filter_names, scope, kind, **kw - ) - batches = list(table_oids) - is_unique = contype == "u" - - while batches: - batch = batches[0:3000] - batches[0:3000] = [] - - result = connection.execute( - self._constraint_query(is_unique), - {"oids": [r[0] for r in batch], "contype": contype}, - ) - - result_by_oid = defaultdict(list) - for oid, cols, constraint_name, comment, extra in result: - result_by_oid[oid].append( - (cols, constraint_name, comment, extra) - ) - - for oid, tablename in batch: - for_oid = result_by_oid.get(oid, ()) - if for_oid: - for cols, constraint, comment, extra in for_oid: - if is_unique: - yield tablename, cols, constraint, comment, { - "nullsnotdistinct": extra - } - else: - yield tablename, cols, constraint, comment, None - else: - yield tablename, None, None, None, None - - @reflection.cache - def get_pk_constraint(self, connection, table_name, schema=None, **kw): - data = self.get_multi_pk_constraint( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - def get_multi_pk_constraint( - self, connection, schema, filter_names, scope, kind, **kw - ): - result = self._reflect_constraint( - connection, "p", schema, filter_names, scope, kind, **kw - ) - - # only a single pk can be present for each table. Return an entry - # even if a table has no primary key - default = ReflectionDefaults.pk_constraint - return ( - ( - (schema, table_name), - ( - { - "constrained_columns": [] if cols is None else cols, - "name": pk_name, - "comment": comment, - } - if pk_name is not None - else default() - ), - ) - for table_name, cols, pk_name, comment, _ in result - ) - - @reflection.cache - def get_foreign_keys( - self, - connection, - table_name, - schema=None, - postgresql_ignore_search_path=False, - **kw, - ): - data = self.get_multi_foreign_keys( - connection, - schema=schema, - filter_names=[table_name], - postgresql_ignore_search_path=postgresql_ignore_search_path, - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _foreing_key_query(self, schema, has_filter_names, scope, kind): - pg_class_ref = pg_catalog.pg_class.alias("cls_ref") - pg_namespace_ref = pg_catalog.pg_namespace.alias("nsp_ref") - relkinds = self._kind_to_relkinds(kind) - query = ( - select( - pg_catalog.pg_class.c.relname, - pg_catalog.pg_constraint.c.conname, - # NOTE: avoid calling pg_get_constraintdef when not needed - # to speed up the query - sql.case( - ( - pg_catalog.pg_constraint.c.oid.is_not(None), - pg_catalog.pg_get_constraintdef( - pg_catalog.pg_constraint.c.oid, True - ), - ), - else_=None, - ), - pg_namespace_ref.c.nspname, - pg_catalog.pg_description.c.description, - ) - .select_from(pg_catalog.pg_class) - .outerjoin( - pg_catalog.pg_constraint, - sql.and_( - pg_catalog.pg_class.c.oid - == pg_catalog.pg_constraint.c.conrelid, - pg_catalog.pg_constraint.c.contype == "f", - ), - ) - .outerjoin( - pg_class_ref, - pg_class_ref.c.oid == pg_catalog.pg_constraint.c.confrelid, - ) - .outerjoin( - pg_namespace_ref, - pg_class_ref.c.relnamespace == pg_namespace_ref.c.oid, - ) - .outerjoin( - pg_catalog.pg_description, - pg_catalog.pg_description.c.objoid - == pg_catalog.pg_constraint.c.oid, - ) - .order_by( - pg_catalog.pg_class.c.relname, - pg_catalog.pg_constraint.c.conname, - ) - .where(self._pg_class_relkind_condition(relkinds)) - ) - query = self._pg_class_filter_scope_schema(query, schema, scope) - if has_filter_names: - query = query.where( - pg_catalog.pg_class.c.relname.in_(bindparam("filter_names")) - ) - return query - - @util.memoized_property - def _fk_regex_pattern(self): - # optionally quoted token - qtoken = '(?:"[^"]+"|[A-Za-z0-9_]+?)' - - # https://www.postgresql.org/docs/current/static/sql-createtable.html - return re.compile( - r"FOREIGN KEY \((.*?)\) " - rf"REFERENCES (?:({qtoken})\.)?({qtoken})\(((?:{qtoken}(?: *, *)?)+)\)" # noqa: E501 - r"[\s]?(MATCH (FULL|PARTIAL|SIMPLE)+)?" - r"[\s]?(ON UPDATE " - r"(CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT)+)?" - r"[\s]?(ON DELETE " - r"(CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT)+)?" - r"[\s]?(DEFERRABLE|NOT DEFERRABLE)?" - r"[\s]?(INITIALLY (DEFERRED|IMMEDIATE)+)?" - ) - - def get_multi_foreign_keys( - self, - connection, - schema, - filter_names, - scope, - kind, - postgresql_ignore_search_path=False, - **kw, - ): - preparer = self.identifier_preparer - - has_filter_names, params = self._prepare_filter_names(filter_names) - query = self._foreing_key_query(schema, has_filter_names, scope, kind) - result = connection.execute(query, params) - - FK_REGEX = self._fk_regex_pattern - - fkeys = defaultdict(list) - default = ReflectionDefaults.foreign_keys - for table_name, conname, condef, conschema, comment in result: - # ensure that each table has an entry, even if it has - # no foreign keys - if conname is None: - fkeys[(schema, table_name)] = default() - continue - table_fks = fkeys[(schema, table_name)] - m = re.search(FK_REGEX, condef).groups() - - ( - constrained_columns, - referred_schema, - referred_table, - referred_columns, - _, - match, - _, - onupdate, - _, - ondelete, - deferrable, - _, - initially, - ) = m - - if deferrable is not None: - deferrable = True if deferrable == "DEFERRABLE" else False - constrained_columns = [ - preparer._unquote_identifier(x) - for x in re.split(r"\s*,\s*", constrained_columns) - ] - - if postgresql_ignore_search_path: - # when ignoring search path, we use the actual schema - # provided it isn't the "default" schema - if conschema != self.default_schema_name: - referred_schema = conschema - else: - referred_schema = schema - elif referred_schema: - # referred_schema is the schema that we regexp'ed from - # pg_get_constraintdef(). If the schema is in the search - # path, pg_get_constraintdef() will give us None. - referred_schema = preparer._unquote_identifier(referred_schema) - elif schema is not None and schema == conschema: - # If the actual schema matches the schema of the table - # we're reflecting, then we will use that. - referred_schema = schema - - referred_table = preparer._unquote_identifier(referred_table) - referred_columns = [ - preparer._unquote_identifier(x) - for x in re.split(r"\s*,\s", referred_columns) - ] - options = { - k: v - for k, v in [ - ("onupdate", onupdate), - ("ondelete", ondelete), - ("initially", initially), - ("deferrable", deferrable), - ("match", match), - ] - if v is not None and v != "NO ACTION" - } - fkey_d = { - "name": conname, - "constrained_columns": constrained_columns, - "referred_schema": referred_schema, - "referred_table": referred_table, - "referred_columns": referred_columns, - "options": options, - "comment": comment, - } - table_fks.append(fkey_d) - return fkeys.items() - - @reflection.cache - def get_indexes(self, connection, table_name, schema=None, **kw): - data = self.get_multi_indexes( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @util.memoized_property - def _index_query(self): - pg_class_index = pg_catalog.pg_class.alias("cls_idx") - # NOTE: repeating oids clause improve query performance - - # subquery to get the columns - idx_sq = ( - select( - pg_catalog.pg_index.c.indexrelid, - pg_catalog.pg_index.c.indrelid, - sql.func.unnest(pg_catalog.pg_index.c.indkey).label("attnum"), - sql.func.generate_subscripts( - pg_catalog.pg_index.c.indkey, 1 - ).label("ord"), - ) - .where( - ~pg_catalog.pg_index.c.indisprimary, - pg_catalog.pg_index.c.indrelid.in_(bindparam("oids")), - ) - .subquery("idx") - ) - - attr_sq = ( - select( - idx_sq.c.indexrelid, - idx_sq.c.indrelid, - idx_sq.c.ord, - # NOTE: always using pg_get_indexdef is too slow so just - # invoke when the element is an expression - sql.case( - ( - idx_sq.c.attnum == 0, - pg_catalog.pg_get_indexdef( - idx_sq.c.indexrelid, idx_sq.c.ord + 1, True - ), - ), - # NOTE: need to cast this since attname is of type "name" - # that's limited to 63 bytes, while pg_get_indexdef - # returns "text" so its output may get cut - else_=pg_catalog.pg_attribute.c.attname.cast(TEXT), - ).label("element"), - (idx_sq.c.attnum == 0).label("is_expr"), - ) - .select_from(idx_sq) - .outerjoin( - # do not remove rows where idx_sq.c.attnum is 0 - pg_catalog.pg_attribute, - sql.and_( - pg_catalog.pg_attribute.c.attnum == idx_sq.c.attnum, - pg_catalog.pg_attribute.c.attrelid == idx_sq.c.indrelid, - ), - ) - .where(idx_sq.c.indrelid.in_(bindparam("oids"))) - .subquery("idx_attr") - ) - - cols_sq = ( - select( - attr_sq.c.indexrelid, - sql.func.min(attr_sq.c.indrelid), - sql.func.array_agg( - aggregate_order_by(attr_sq.c.element, attr_sq.c.ord) - ).label("elements"), - sql.func.array_agg( - aggregate_order_by(attr_sq.c.is_expr, attr_sq.c.ord) - ).label("elements_is_expr"), - ) - .group_by(attr_sq.c.indexrelid) - .subquery("idx_cols") - ) - - if self.server_version_info >= (11, 0): - indnkeyatts = pg_catalog.pg_index.c.indnkeyatts - else: - indnkeyatts = sql.null().label("indnkeyatts") - - if self.server_version_info >= (15,): - nulls_not_distinct = pg_catalog.pg_index.c.indnullsnotdistinct - else: - nulls_not_distinct = sql.false().label("indnullsnotdistinct") - - return ( - select( - pg_catalog.pg_index.c.indrelid, - pg_class_index.c.relname.label("relname_index"), - pg_catalog.pg_index.c.indisunique, - pg_catalog.pg_constraint.c.conrelid.is_not(None).label( - "has_constraint" - ), - pg_catalog.pg_index.c.indoption, - pg_class_index.c.reloptions, - pg_catalog.pg_am.c.amname, - # NOTE: pg_get_expr is very fast so this case has almost no - # performance impact - sql.case( - ( - pg_catalog.pg_index.c.indpred.is_not(None), - pg_catalog.pg_get_expr( - pg_catalog.pg_index.c.indpred, - pg_catalog.pg_index.c.indrelid, - ), - ), - else_=None, - ).label("filter_definition"), - indnkeyatts, - nulls_not_distinct, - cols_sq.c.elements, - cols_sq.c.elements_is_expr, - ) - .select_from(pg_catalog.pg_index) - .where( - pg_catalog.pg_index.c.indrelid.in_(bindparam("oids")), - ~pg_catalog.pg_index.c.indisprimary, - ) - .join( - pg_class_index, - pg_catalog.pg_index.c.indexrelid == pg_class_index.c.oid, - ) - .join( - pg_catalog.pg_am, - pg_class_index.c.relam == pg_catalog.pg_am.c.oid, - ) - .outerjoin( - cols_sq, - pg_catalog.pg_index.c.indexrelid == cols_sq.c.indexrelid, - ) - .outerjoin( - pg_catalog.pg_constraint, - sql.and_( - pg_catalog.pg_index.c.indrelid - == pg_catalog.pg_constraint.c.conrelid, - pg_catalog.pg_index.c.indexrelid - == pg_catalog.pg_constraint.c.conindid, - pg_catalog.pg_constraint.c.contype - == sql.any_(_array.array(("p", "u", "x"))), - ), - ) - .order_by(pg_catalog.pg_index.c.indrelid, pg_class_index.c.relname) - ) - - def get_multi_indexes( - self, connection, schema, filter_names, scope, kind, **kw - ): - table_oids = self._get_table_oids( - connection, schema, filter_names, scope, kind, **kw - ) - - indexes = defaultdict(list) - default = ReflectionDefaults.indexes - - batches = list(table_oids) - - while batches: - batch = batches[0:3000] - batches[0:3000] = [] - - result = connection.execute( - self._index_query, {"oids": [r[0] for r in batch]} - ).mappings() - - result_by_oid = defaultdict(list) - for row_dict in result: - result_by_oid[row_dict["indrelid"]].append(row_dict) - - for oid, table_name in batch: - if oid not in result_by_oid: - # ensure that each table has an entry, even if reflection - # is skipped because not supported - indexes[(schema, table_name)] = default() - continue - - for row in result_by_oid[oid]: - index_name = row["relname_index"] - - table_indexes = indexes[(schema, table_name)] - - all_elements = row["elements"] - all_elements_is_expr = row["elements_is_expr"] - indnkeyatts = row["indnkeyatts"] - # "The number of key columns in the index, not counting any - # included columns, which are merely stored and do not - # participate in the index semantics" - if indnkeyatts and len(all_elements) > indnkeyatts: - # this is a "covering index" which has INCLUDE columns - # as well as regular index columns - inc_cols = all_elements[indnkeyatts:] - idx_elements = all_elements[:indnkeyatts] - idx_elements_is_expr = all_elements_is_expr[ - :indnkeyatts - ] - # postgresql does not support expression on included - # columns as of v14: "ERROR: expressions are not - # supported in included columns". - assert all( - not is_expr - for is_expr in all_elements_is_expr[indnkeyatts:] - ) - else: - idx_elements = all_elements - idx_elements_is_expr = all_elements_is_expr - inc_cols = [] - - index = {"name": index_name, "unique": row["indisunique"]} - if any(idx_elements_is_expr): - index["column_names"] = [ - None if is_expr else expr - for expr, is_expr in zip( - idx_elements, idx_elements_is_expr - ) - ] - index["expressions"] = idx_elements - else: - index["column_names"] = idx_elements - - sorting = {} - for col_index, col_flags in enumerate(row["indoption"]): - col_sorting = () - # try to set flags only if they differ from PG - # defaults... - if col_flags & 0x01: - col_sorting += ("desc",) - if not (col_flags & 0x02): - col_sorting += ("nulls_last",) - else: - if col_flags & 0x02: - col_sorting += ("nulls_first",) - if col_sorting: - sorting[idx_elements[col_index]] = col_sorting - if sorting: - index["column_sorting"] = sorting - if row["has_constraint"]: - index["duplicates_constraint"] = index_name - - dialect_options = {} - if row["reloptions"]: - dialect_options["postgresql_with"] = dict( - [option.split("=") for option in row["reloptions"]] - ) - # it *might* be nice to include that this is 'btree' in the - # reflection info. But we don't want an Index object - # to have a ``postgresql_using`` in it that is just the - # default, so for the moment leaving this out. - amname = row["amname"] - if amname != "btree": - dialect_options["postgresql_using"] = row["amname"] - if row["filter_definition"]: - dialect_options["postgresql_where"] = row[ - "filter_definition" - ] - if self.server_version_info >= (11,): - # NOTE: this is legacy, this is part of - # dialect_options now as of #7382 - index["include_columns"] = inc_cols - dialect_options["postgresql_include"] = inc_cols - if row["indnullsnotdistinct"]: - # the default is False, so ignore it. - dialect_options["postgresql_nulls_not_distinct"] = row[ - "indnullsnotdistinct" - ] - - if dialect_options: - index["dialect_options"] = dialect_options - - table_indexes.append(index) - return indexes.items() - - @reflection.cache - def get_unique_constraints( - self, connection, table_name, schema=None, **kw - ): - data = self.get_multi_unique_constraints( - connection, - schema=schema, - filter_names=[table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - def get_multi_unique_constraints( - self, - connection, - schema, - filter_names, - scope, - kind, - **kw, - ): - result = self._reflect_constraint( - connection, "u", schema, filter_names, scope, kind, **kw - ) - - # each table can have multiple unique constraints - uniques = defaultdict(list) - default = ReflectionDefaults.unique_constraints - for table_name, cols, con_name, comment, options in result: - # ensure a list is created for each table. leave it empty if - # the table has no unique cosntraint - if con_name is None: - uniques[(schema, table_name)] = default() - continue - - uc_dict = { - "column_names": cols, - "name": con_name, - "comment": comment, - } - if options: - if options["nullsnotdistinct"]: - uc_dict["dialect_options"] = { - "postgresql_nulls_not_distinct": options[ - "nullsnotdistinct" - ] - } - - uniques[(schema, table_name)].append(uc_dict) - return uniques.items() - - @reflection.cache - def get_table_comment(self, connection, table_name, schema=None, **kw): - data = self.get_multi_table_comment( - connection, - schema, - [table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _comment_query(self, schema, has_filter_names, scope, kind): - relkinds = self._kind_to_relkinds(kind) - query = ( - select( - pg_catalog.pg_class.c.relname, - pg_catalog.pg_description.c.description, - ) - .select_from(pg_catalog.pg_class) - .outerjoin( - pg_catalog.pg_description, - sql.and_( - pg_catalog.pg_class.c.oid - == pg_catalog.pg_description.c.objoid, - pg_catalog.pg_description.c.objsubid == 0, - ), - ) - .where(self._pg_class_relkind_condition(relkinds)) - ) - query = self._pg_class_filter_scope_schema(query, schema, scope) - if has_filter_names: - query = query.where( - pg_catalog.pg_class.c.relname.in_(bindparam("filter_names")) - ) - return query - - def get_multi_table_comment( - self, connection, schema, filter_names, scope, kind, **kw - ): - has_filter_names, params = self._prepare_filter_names(filter_names) - query = self._comment_query(schema, has_filter_names, scope, kind) - result = connection.execute(query, params) - - default = ReflectionDefaults.table_comment - return ( - ( - (schema, table), - {"text": comment} if comment is not None else default(), - ) - for table, comment in result - ) - - @reflection.cache - def get_check_constraints(self, connection, table_name, schema=None, **kw): - data = self.get_multi_check_constraints( - connection, - schema, - [table_name], - scope=ObjectScope.ANY, - kind=ObjectKind.ANY, - **kw, - ) - return self._value_or_raise(data, table_name, schema) - - @lru_cache() - def _check_constraint_query(self, schema, has_filter_names, scope, kind): - relkinds = self._kind_to_relkinds(kind) - query = ( - select( - pg_catalog.pg_class.c.relname, - pg_catalog.pg_constraint.c.conname, - # NOTE: avoid calling pg_get_constraintdef when not needed - # to speed up the query - sql.case( - ( - pg_catalog.pg_constraint.c.oid.is_not(None), - pg_catalog.pg_get_constraintdef( - pg_catalog.pg_constraint.c.oid, True - ), - ), - else_=None, - ), - pg_catalog.pg_description.c.description, - ) - .select_from(pg_catalog.pg_class) - .outerjoin( - pg_catalog.pg_constraint, - sql.and_( - pg_catalog.pg_class.c.oid - == pg_catalog.pg_constraint.c.conrelid, - pg_catalog.pg_constraint.c.contype == "c", - ), - ) - .outerjoin( - pg_catalog.pg_description, - pg_catalog.pg_description.c.objoid - == pg_catalog.pg_constraint.c.oid, - ) - .order_by( - pg_catalog.pg_class.c.relname, - pg_catalog.pg_constraint.c.conname, - ) - .where(self._pg_class_relkind_condition(relkinds)) - ) - query = self._pg_class_filter_scope_schema(query, schema, scope) - if has_filter_names: - query = query.where( - pg_catalog.pg_class.c.relname.in_(bindparam("filter_names")) - ) - return query - - def get_multi_check_constraints( - self, connection, schema, filter_names, scope, kind, **kw - ): - has_filter_names, params = self._prepare_filter_names(filter_names) - query = self._check_constraint_query( - schema, has_filter_names, scope, kind - ) - result = connection.execute(query, params) - - check_constraints = defaultdict(list) - default = ReflectionDefaults.check_constraints - for table_name, check_name, src, comment in result: - # only two cases for check_name and src: both null or both defined - if check_name is None and src is None: - check_constraints[(schema, table_name)] = default() - continue - # samples: - # "CHECK (((a > 1) AND (a < 5)))" - # "CHECK (((a = 1) OR ((a > 2) AND (a < 5))))" - # "CHECK (((a > 1) AND (a < 5))) NOT VALID" - # "CHECK (some_boolean_function(a))" - # "CHECK (((a\n < 1)\n OR\n (a\n >= 5))\n)" - # "CHECK (a NOT NULL) NO INHERIT" - # "CHECK (a NOT NULL) NO INHERIT NOT VALID" - - m = re.match( - r"^CHECK *\((.+)\)( NO INHERIT)?( NOT VALID)?$", - src, - flags=re.DOTALL, - ) - if not m: - util.warn("Could not parse CHECK constraint text: %r" % src) - sqltext = "" - else: - sqltext = re.compile( - r"^[\s\n]*\((.+)\)[\s\n]*$", flags=re.DOTALL - ).sub(r"\1", m.group(1)) - entry = { - "name": check_name, - "sqltext": sqltext, - "comment": comment, - } - if m: - do = {} - if " NOT VALID" in m.groups(): - do["not_valid"] = True - if " NO INHERIT" in m.groups(): - do["no_inherit"] = True - if do: - entry["dialect_options"] = do - - check_constraints[(schema, table_name)].append(entry) - return check_constraints.items() - - def _pg_type_filter_schema(self, query, schema): - if schema is None: - query = query.where( - pg_catalog.pg_type_is_visible(pg_catalog.pg_type.c.oid), - # ignore pg_catalog schema - pg_catalog.pg_namespace.c.nspname != "pg_catalog", - ) - elif schema != "*": - query = query.where(pg_catalog.pg_namespace.c.nspname == schema) - return query - - @lru_cache() - def _enum_query(self, schema): - lbl_agg_sq = ( - select( - pg_catalog.pg_enum.c.enumtypid, - sql.func.array_agg( - aggregate_order_by( - # NOTE: cast since some postgresql derivatives may - # not support array_agg on the name type - pg_catalog.pg_enum.c.enumlabel.cast(TEXT), - pg_catalog.pg_enum.c.enumsortorder, - ) - ).label("labels"), - ) - .group_by(pg_catalog.pg_enum.c.enumtypid) - .subquery("lbl_agg") - ) - - query = ( - select( - pg_catalog.pg_type.c.typname.label("name"), - pg_catalog.pg_type_is_visible(pg_catalog.pg_type.c.oid).label( - "visible" - ), - pg_catalog.pg_namespace.c.nspname.label("schema"), - lbl_agg_sq.c.labels.label("labels"), - ) - .join( - pg_catalog.pg_namespace, - pg_catalog.pg_namespace.c.oid - == pg_catalog.pg_type.c.typnamespace, - ) - .outerjoin( - lbl_agg_sq, pg_catalog.pg_type.c.oid == lbl_agg_sq.c.enumtypid - ) - .where(pg_catalog.pg_type.c.typtype == "e") - .order_by( - pg_catalog.pg_namespace.c.nspname, pg_catalog.pg_type.c.typname - ) - ) - - return self._pg_type_filter_schema(query, schema) - - @reflection.cache - def _load_enums(self, connection, schema=None, **kw): - if not self.supports_native_enum: - return [] - - result = connection.execute(self._enum_query(schema)) - - enums = [] - for name, visible, schema, labels in result: - enums.append( - { - "name": name, - "schema": schema, - "visible": visible, - "labels": [] if labels is None else labels, - } - ) - return enums - - @lru_cache() - def _domain_query(self, schema): - con_sq = ( - select( - pg_catalog.pg_constraint.c.contypid, - sql.func.array_agg( - pg_catalog.pg_get_constraintdef( - pg_catalog.pg_constraint.c.oid, True - ) - ).label("condefs"), - sql.func.array_agg( - # NOTE: cast since some postgresql derivatives may - # not support array_agg on the name type - pg_catalog.pg_constraint.c.conname.cast(TEXT) - ).label("connames"), - ) - # The domain this constraint is on; zero if not a domain constraint - .where(pg_catalog.pg_constraint.c.contypid != 0) - .group_by(pg_catalog.pg_constraint.c.contypid) - .subquery("domain_constraints") - ) - - query = ( - select( - pg_catalog.pg_type.c.typname.label("name"), - pg_catalog.format_type( - pg_catalog.pg_type.c.typbasetype, - pg_catalog.pg_type.c.typtypmod, - ).label("attype"), - (~pg_catalog.pg_type.c.typnotnull).label("nullable"), - pg_catalog.pg_type.c.typdefault.label("default"), - pg_catalog.pg_type_is_visible(pg_catalog.pg_type.c.oid).label( - "visible" - ), - pg_catalog.pg_namespace.c.nspname.label("schema"), - con_sq.c.condefs, - con_sq.c.connames, - pg_catalog.pg_collation.c.collname, - ) - .join( - pg_catalog.pg_namespace, - pg_catalog.pg_namespace.c.oid - == pg_catalog.pg_type.c.typnamespace, - ) - .outerjoin( - pg_catalog.pg_collation, - pg_catalog.pg_type.c.typcollation - == pg_catalog.pg_collation.c.oid, - ) - .outerjoin( - con_sq, - pg_catalog.pg_type.c.oid == con_sq.c.contypid, - ) - .where(pg_catalog.pg_type.c.typtype == "d") - .order_by( - pg_catalog.pg_namespace.c.nspname, pg_catalog.pg_type.c.typname - ) - ) - return self._pg_type_filter_schema(query, schema) - - @reflection.cache - def _load_domains(self, connection, schema=None, **kw): - result = connection.execute(self._domain_query(schema)) - - domains: List[ReflectedDomain] = [] - for domain in result.mappings(): - # strip (30) from character varying(30) - attype = re.search(r"([^\(]+)", domain["attype"]).group(1) - constraints: List[ReflectedDomainConstraint] = [] - if domain["connames"]: - # When a domain has multiple CHECK constraints, they will - # be tested in alphabetical order by name. - sorted_constraints = sorted( - zip(domain["connames"], domain["condefs"]), - key=lambda t: t[0], - ) - for name, def_ in sorted_constraints: - # constraint is in the form "CHECK (expression)". - # remove "CHECK (" and the tailing ")". - check = def_[7:-1] - constraints.append({"name": name, "check": check}) - - domain_rec: ReflectedDomain = { - "name": domain["name"], - "schema": domain["schema"], - "visible": domain["visible"], - "type": attype, - "nullable": domain["nullable"], - "default": domain["default"], - "constraints": constraints, - "collation": domain["collname"], - } - domains.append(domain_rec) - - return domains - - def _set_backslash_escapes(self, connection): - # this method is provided as an override hook for descendant - # dialects (e.g. Redshift), so removing it may break them - std_string = connection.exec_driver_sql( - "show standard_conforming_strings" - ).scalar() - self._backslash_escapes = std_string == "off" diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/dml.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/dml.py deleted file mode 100644 index 4404ecd..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/dml.py +++ /dev/null @@ -1,310 +0,0 @@ -# dialects/postgresql/dml.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -from typing import Any -from typing import Optional - -from . import ext -from .._typing import _OnConflictConstraintT -from .._typing import _OnConflictIndexElementsT -from .._typing import _OnConflictIndexWhereT -from .._typing import _OnConflictSetT -from .._typing import _OnConflictWhereT -from ... import util -from ...sql import coercions -from ...sql import roles -from ...sql import schema -from ...sql._typing import _DMLTableArgument -from ...sql.base import _exclusive_against -from ...sql.base import _generative -from ...sql.base import ColumnCollection -from ...sql.base import ReadOnlyColumnCollection -from ...sql.dml import Insert as StandardInsert -from ...sql.elements import ClauseElement -from ...sql.elements import KeyedColumnElement -from ...sql.expression import alias -from ...util.typing import Self - - -__all__ = ("Insert", "insert") - - -def insert(table: _DMLTableArgument) -> Insert: - """Construct a PostgreSQL-specific variant :class:`_postgresql.Insert` - construct. - - .. container:: inherited_member - - The :func:`sqlalchemy.dialects.postgresql.insert` function creates - a :class:`sqlalchemy.dialects.postgresql.Insert`. This class is based - on the dialect-agnostic :class:`_sql.Insert` construct which may - be constructed using the :func:`_sql.insert` function in - SQLAlchemy Core. - - The :class:`_postgresql.Insert` construct includes additional methods - :meth:`_postgresql.Insert.on_conflict_do_update`, - :meth:`_postgresql.Insert.on_conflict_do_nothing`. - - """ - return Insert(table) - - -class Insert(StandardInsert): - """PostgreSQL-specific implementation of INSERT. - - Adds methods for PG-specific syntaxes such as ON CONFLICT. - - The :class:`_postgresql.Insert` object is created using the - :func:`sqlalchemy.dialects.postgresql.insert` function. - - """ - - stringify_dialect = "postgresql" - inherit_cache = False - - @util.memoized_property - def excluded( - self, - ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: - """Provide the ``excluded`` namespace for an ON CONFLICT statement - - PG's ON CONFLICT clause allows reference to the row that would - be inserted, known as ``excluded``. This attribute provides - all columns in this row to be referenceable. - - .. tip:: The :attr:`_postgresql.Insert.excluded` attribute is an - instance of :class:`_expression.ColumnCollection`, which provides - an interface the same as that of the :attr:`_schema.Table.c` - collection described at :ref:`metadata_tables_and_columns`. - With this collection, ordinary names are accessible like attributes - (e.g. ``stmt.excluded.some_column``), but special names and - dictionary method names should be accessed using indexed access, - such as ``stmt.excluded["column name"]`` or - ``stmt.excluded["values"]``. See the docstring for - :class:`_expression.ColumnCollection` for further examples. - - .. seealso:: - - :ref:`postgresql_insert_on_conflict` - example of how - to use :attr:`_expression.Insert.excluded` - - """ - return alias(self.table, name="excluded").columns - - _on_conflict_exclusive = _exclusive_against( - "_post_values_clause", - msgs={ - "_post_values_clause": "This Insert construct already has " - "an ON CONFLICT clause established" - }, - ) - - @_generative - @_on_conflict_exclusive - def on_conflict_do_update( - self, - constraint: _OnConflictConstraintT = None, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - set_: _OnConflictSetT = None, - where: _OnConflictWhereT = None, - ) -> Self: - r""" - Specifies a DO UPDATE SET action for ON CONFLICT clause. - - Either the ``constraint`` or ``index_elements`` argument is - required, but only one of these can be specified. - - :param constraint: - The name of a unique or exclusion constraint on the table, - or the constraint object itself if it has a .name attribute. - - :param index_elements: - A sequence consisting of string column names, :class:`_schema.Column` - objects, or other column expression objects that will be used - to infer a target index. - - :param index_where: - Additional WHERE criterion that can be used to infer a - conditional target index. - - :param set\_: - A dictionary or other mapping object - where the keys are either names of columns in the target table, - or :class:`_schema.Column` objects or other ORM-mapped columns - matching that of the target table, and expressions or literals - as values, specifying the ``SET`` actions to take. - - .. versionadded:: 1.4 The - :paramref:`_postgresql.Insert.on_conflict_do_update.set_` - parameter supports :class:`_schema.Column` objects from the target - :class:`_schema.Table` as keys. - - .. warning:: This dictionary does **not** take into account - Python-specified default UPDATE values or generation functions, - e.g. those specified using :paramref:`_schema.Column.onupdate`. - These values will not be exercised for an ON CONFLICT style of - UPDATE, unless they are manually specified in the - :paramref:`.Insert.on_conflict_do_update.set_` dictionary. - - :param where: - Optional argument. If present, can be a literal SQL - string or an acceptable expression for a ``WHERE`` clause - that restricts the rows affected by ``DO UPDATE SET``. Rows - not meeting the ``WHERE`` condition will not be updated - (effectively a ``DO NOTHING`` for those rows). - - - .. seealso:: - - :ref:`postgresql_insert_on_conflict` - - """ - self._post_values_clause = OnConflictDoUpdate( - constraint, index_elements, index_where, set_, where - ) - return self - - @_generative - @_on_conflict_exclusive - def on_conflict_do_nothing( - self, - constraint: _OnConflictConstraintT = None, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - ) -> Self: - """ - Specifies a DO NOTHING action for ON CONFLICT clause. - - The ``constraint`` and ``index_elements`` arguments - are optional, but only one of these can be specified. - - :param constraint: - The name of a unique or exclusion constraint on the table, - or the constraint object itself if it has a .name attribute. - - :param index_elements: - A sequence consisting of string column names, :class:`_schema.Column` - objects, or other column expression objects that will be used - to infer a target index. - - :param index_where: - Additional WHERE criterion that can be used to infer a - conditional target index. - - .. seealso:: - - :ref:`postgresql_insert_on_conflict` - - """ - self._post_values_clause = OnConflictDoNothing( - constraint, index_elements, index_where - ) - return self - - -class OnConflictClause(ClauseElement): - stringify_dialect = "postgresql" - - constraint_target: Optional[str] - inferred_target_elements: _OnConflictIndexElementsT - inferred_target_whereclause: _OnConflictIndexWhereT - - def __init__( - self, - constraint: _OnConflictConstraintT = None, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - ): - if constraint is not None: - if not isinstance(constraint, str) and isinstance( - constraint, - (schema.Constraint, ext.ExcludeConstraint), - ): - constraint = getattr(constraint, "name") or constraint - - if constraint is not None: - if index_elements is not None: - raise ValueError( - "'constraint' and 'index_elements' are mutually exclusive" - ) - - if isinstance(constraint, str): - self.constraint_target = constraint - self.inferred_target_elements = None - self.inferred_target_whereclause = None - elif isinstance(constraint, schema.Index): - index_elements = constraint.expressions - index_where = constraint.dialect_options["postgresql"].get( - "where" - ) - elif isinstance(constraint, ext.ExcludeConstraint): - index_elements = constraint.columns - index_where = constraint.where - else: - index_elements = constraint.columns - index_where = constraint.dialect_options["postgresql"].get( - "where" - ) - - if index_elements is not None: - self.constraint_target = None - self.inferred_target_elements = index_elements - self.inferred_target_whereclause = index_where - elif constraint is None: - self.constraint_target = self.inferred_target_elements = ( - self.inferred_target_whereclause - ) = None - - -class OnConflictDoNothing(OnConflictClause): - __visit_name__ = "on_conflict_do_nothing" - - -class OnConflictDoUpdate(OnConflictClause): - __visit_name__ = "on_conflict_do_update" - - def __init__( - self, - constraint: _OnConflictConstraintT = None, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - set_: _OnConflictSetT = None, - where: _OnConflictWhereT = None, - ): - super().__init__( - constraint=constraint, - index_elements=index_elements, - index_where=index_where, - ) - - if ( - self.inferred_target_elements is None - and self.constraint_target is None - ): - raise ValueError( - "Either constraint or index_elements, " - "but not both, must be specified unless DO NOTHING" - ) - - if isinstance(set_, dict): - if not set_: - raise ValueError("set parameter dictionary must not be empty") - elif isinstance(set_, ColumnCollection): - set_ = dict(set_) - else: - raise ValueError( - "set parameter must be a non-empty dictionary " - "or a ColumnCollection such as the `.c.` collection " - "of a Table object" - ) - self.update_values_to_set = [ - (coercions.expect(roles.DMLColumnRole, key), value) - for key, value in set_.items() - ] - self.update_whereclause = where diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ext.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ext.py deleted file mode 100644 index 7fc0895..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ext.py +++ /dev/null @@ -1,496 +0,0 @@ -# dialects/postgresql/ext.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from __future__ import annotations - -from typing import Any -from typing import TYPE_CHECKING -from typing import TypeVar - -from . import types -from .array import ARRAY -from ...sql import coercions -from ...sql import elements -from ...sql import expression -from ...sql import functions -from ...sql import roles -from ...sql import schema -from ...sql.schema import ColumnCollectionConstraint -from ...sql.sqltypes import TEXT -from ...sql.visitors import InternalTraversal - -_T = TypeVar("_T", bound=Any) - -if TYPE_CHECKING: - from ...sql.visitors import _TraverseInternalsType - - -class aggregate_order_by(expression.ColumnElement): - """Represent a PostgreSQL aggregate order by expression. - - E.g.:: - - from sqlalchemy.dialects.postgresql import aggregate_order_by - expr = func.array_agg(aggregate_order_by(table.c.a, table.c.b.desc())) - stmt = select(expr) - - would represent the expression:: - - SELECT array_agg(a ORDER BY b DESC) FROM table; - - Similarly:: - - expr = func.string_agg( - table.c.a, - aggregate_order_by(literal_column("','"), table.c.a) - ) - stmt = select(expr) - - Would represent:: - - SELECT string_agg(a, ',' ORDER BY a) FROM table; - - .. versionchanged:: 1.2.13 - the ORDER BY argument may be multiple terms - - .. seealso:: - - :class:`_functions.array_agg` - - """ - - __visit_name__ = "aggregate_order_by" - - stringify_dialect = "postgresql" - _traverse_internals: _TraverseInternalsType = [ - ("target", InternalTraversal.dp_clauseelement), - ("type", InternalTraversal.dp_type), - ("order_by", InternalTraversal.dp_clauseelement), - ] - - def __init__(self, target, *order_by): - self.target = coercions.expect(roles.ExpressionElementRole, target) - self.type = self.target.type - - _lob = len(order_by) - if _lob == 0: - raise TypeError("at least one ORDER BY element is required") - elif _lob == 1: - self.order_by = coercions.expect( - roles.ExpressionElementRole, order_by[0] - ) - else: - self.order_by = elements.ClauseList( - *order_by, _literal_as_text_role=roles.ExpressionElementRole - ) - - def self_group(self, against=None): - return self - - def get_children(self, **kwargs): - return self.target, self.order_by - - def _copy_internals(self, clone=elements._clone, **kw): - self.target = clone(self.target, **kw) - self.order_by = clone(self.order_by, **kw) - - @property - def _from_objects(self): - return self.target._from_objects + self.order_by._from_objects - - -class ExcludeConstraint(ColumnCollectionConstraint): - """A table-level EXCLUDE constraint. - - Defines an EXCLUDE constraint as described in the `PostgreSQL - documentation`__. - - __ https://www.postgresql.org/docs/current/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE - - """ # noqa - - __visit_name__ = "exclude_constraint" - - where = None - inherit_cache = False - - create_drop_stringify_dialect = "postgresql" - - @elements._document_text_coercion( - "where", - ":class:`.ExcludeConstraint`", - ":paramref:`.ExcludeConstraint.where`", - ) - def __init__(self, *elements, **kw): - r""" - Create an :class:`.ExcludeConstraint` object. - - E.g.:: - - const = ExcludeConstraint( - (Column('period'), '&&'), - (Column('group'), '='), - where=(Column('group') != 'some group'), - ops={'group': 'my_operator_class'} - ) - - The constraint is normally embedded into the :class:`_schema.Table` - construct - directly, or added later using :meth:`.append_constraint`:: - - some_table = Table( - 'some_table', metadata, - Column('id', Integer, primary_key=True), - Column('period', TSRANGE()), - Column('group', String) - ) - - some_table.append_constraint( - ExcludeConstraint( - (some_table.c.period, '&&'), - (some_table.c.group, '='), - where=some_table.c.group != 'some group', - name='some_table_excl_const', - ops={'group': 'my_operator_class'} - ) - ) - - The exclude constraint defined in this example requires the - ``btree_gist`` extension, that can be created using the - command ``CREATE EXTENSION btree_gist;``. - - :param \*elements: - - A sequence of two tuples of the form ``(column, operator)`` where - "column" is either a :class:`_schema.Column` object, or a SQL - expression element (e.g. ``func.int8range(table.from, table.to)``) - or the name of a column as string, and "operator" is a string - containing the operator to use (e.g. `"&&"` or `"="`). - - In order to specify a column name when a :class:`_schema.Column` - object is not available, while ensuring - that any necessary quoting rules take effect, an ad-hoc - :class:`_schema.Column` or :func:`_expression.column` - object should be used. - The ``column`` may also be a string SQL expression when - passed as :func:`_expression.literal_column` or - :func:`_expression.text` - - :param name: - Optional, the in-database name of this constraint. - - :param deferrable: - Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when - issuing DDL for this constraint. - - :param initially: - Optional string. If set, emit INITIALLY when issuing DDL - for this constraint. - - :param using: - Optional string. If set, emit USING when issuing DDL - for this constraint. Defaults to 'gist'. - - :param where: - Optional SQL expression construct or literal SQL string. - If set, emit WHERE when issuing DDL - for this constraint. - - :param ops: - Optional dictionary. Used to define operator classes for the - elements; works the same way as that of the - :ref:`postgresql_ops ` - parameter specified to the :class:`_schema.Index` construct. - - .. versionadded:: 1.3.21 - - .. seealso:: - - :ref:`postgresql_operator_classes` - general description of how - PostgreSQL operator classes are specified. - - """ - columns = [] - render_exprs = [] - self.operators = {} - - expressions, operators = zip(*elements) - - for (expr, column, strname, add_element), operator in zip( - coercions.expect_col_expression_collection( - roles.DDLConstraintColumnRole, expressions - ), - operators, - ): - if add_element is not None: - columns.append(add_element) - - name = column.name if column is not None else strname - - if name is not None: - # backwards compat - self.operators[name] = operator - - render_exprs.append((expr, name, operator)) - - self._render_exprs = render_exprs - - ColumnCollectionConstraint.__init__( - self, - *columns, - name=kw.get("name"), - deferrable=kw.get("deferrable"), - initially=kw.get("initially"), - ) - self.using = kw.get("using", "gist") - where = kw.get("where") - if where is not None: - self.where = coercions.expect(roles.StatementOptionRole, where) - - self.ops = kw.get("ops", {}) - - def _set_parent(self, table, **kw): - super()._set_parent(table) - - self._render_exprs = [ - ( - expr if not isinstance(expr, str) else table.c[expr], - name, - operator, - ) - for expr, name, operator in (self._render_exprs) - ] - - def _copy(self, target_table=None, **kw): - elements = [ - ( - schema._copy_expression(expr, self.parent, target_table), - operator, - ) - for expr, _, operator in self._render_exprs - ] - c = self.__class__( - *elements, - name=self.name, - deferrable=self.deferrable, - initially=self.initially, - where=self.where, - using=self.using, - ) - c.dispatch._update(self.dispatch) - return c - - -def array_agg(*arg, **kw): - """PostgreSQL-specific form of :class:`_functions.array_agg`, ensures - return type is :class:`_postgresql.ARRAY` and not - the plain :class:`_types.ARRAY`, unless an explicit ``type_`` - is passed. - - """ - kw["_default_array_type"] = ARRAY - return functions.func.array_agg(*arg, **kw) - - -class _regconfig_fn(functions.GenericFunction[_T]): - inherit_cache = True - - def __init__(self, *args, **kwargs): - args = list(args) - if len(args) > 1: - initial_arg = coercions.expect( - roles.ExpressionElementRole, - args.pop(0), - name=getattr(self, "name", None), - apply_propagate_attrs=self, - type_=types.REGCONFIG, - ) - initial_arg = [initial_arg] - else: - initial_arg = [] - - addtl_args = [ - coercions.expect( - roles.ExpressionElementRole, - c, - name=getattr(self, "name", None), - apply_propagate_attrs=self, - ) - for c in args - ] - super().__init__(*(initial_arg + addtl_args), **kwargs) - - -class to_tsvector(_regconfig_fn): - """The PostgreSQL ``to_tsvector`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_postgresql.TSVECTOR`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.to_tsvector` will be used automatically when invoking - ``sqlalchemy.func.to_tsvector()``, ensuring the correct argument and return - type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = types.TSVECTOR - - -class to_tsquery(_regconfig_fn): - """The PostgreSQL ``to_tsquery`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_postgresql.TSQUERY`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.to_tsquery` will be used automatically when invoking - ``sqlalchemy.func.to_tsquery()``, ensuring the correct argument and return - type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = types.TSQUERY - - -class plainto_tsquery(_regconfig_fn): - """The PostgreSQL ``plainto_tsquery`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_postgresql.TSQUERY`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.plainto_tsquery` will be used automatically when - invoking ``sqlalchemy.func.plainto_tsquery()``, ensuring the correct - argument and return type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = types.TSQUERY - - -class phraseto_tsquery(_regconfig_fn): - """The PostgreSQL ``phraseto_tsquery`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_postgresql.TSQUERY`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.phraseto_tsquery` will be used automatically when - invoking ``sqlalchemy.func.phraseto_tsquery()``, ensuring the correct - argument and return type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = types.TSQUERY - - -class websearch_to_tsquery(_regconfig_fn): - """The PostgreSQL ``websearch_to_tsquery`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_postgresql.TSQUERY`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.websearch_to_tsquery` will be used automatically when - invoking ``sqlalchemy.func.websearch_to_tsquery()``, ensuring the correct - argument and return type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = types.TSQUERY - - -class ts_headline(_regconfig_fn): - """The PostgreSQL ``ts_headline`` SQL function. - - This function applies automatic casting of the REGCONFIG argument - to use the :class:`_postgresql.REGCONFIG` datatype automatically, - and applies a return type of :class:`_types.TEXT`. - - Assuming the PostgreSQL dialect has been imported, either by invoking - ``from sqlalchemy.dialects import postgresql``, or by creating a PostgreSQL - engine using ``create_engine("postgresql...")``, - :class:`_postgresql.ts_headline` will be used automatically when invoking - ``sqlalchemy.func.ts_headline()``, ensuring the correct argument and return - type handlers are used at compile and execution time. - - .. versionadded:: 2.0.0rc1 - - """ - - inherit_cache = True - type = TEXT - - def __init__(self, *args, **kwargs): - args = list(args) - - # parse types according to - # https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-HEADLINE - if len(args) < 2: - # invalid args; don't do anything - has_regconfig = False - elif ( - isinstance(args[1], elements.ColumnElement) - and args[1].type._type_affinity is types.TSQUERY - ): - # tsquery is second argument, no regconfig argument - has_regconfig = False - else: - has_regconfig = True - - if has_regconfig: - initial_arg = coercions.expect( - roles.ExpressionElementRole, - args.pop(0), - apply_propagate_attrs=self, - name=getattr(self, "name", None), - type_=types.REGCONFIG, - ) - initial_arg = [initial_arg] - else: - initial_arg = [] - - addtl_args = [ - coercions.expect( - roles.ExpressionElementRole, - c, - name=getattr(self, "name", None), - apply_propagate_attrs=self, - ) - for c in args - ] - super().__init__(*(initial_arg + addtl_args), **kwargs) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/hstore.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/hstore.py deleted file mode 100644 index 04c8cf1..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/hstore.py +++ /dev/null @@ -1,397 +0,0 @@ -# dialects/postgresql/hstore.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -import re - -from .array import ARRAY -from .operators import CONTAINED_BY -from .operators import CONTAINS -from .operators import GETITEM -from .operators import HAS_ALL -from .operators import HAS_ANY -from .operators import HAS_KEY -from ... import types as sqltypes -from ...sql import functions as sqlfunc - - -__all__ = ("HSTORE", "hstore") - - -class HSTORE(sqltypes.Indexable, sqltypes.Concatenable, sqltypes.TypeEngine): - """Represent the PostgreSQL HSTORE type. - - The :class:`.HSTORE` type stores dictionaries containing strings, e.g.:: - - data_table = Table('data_table', metadata, - Column('id', Integer, primary_key=True), - Column('data', HSTORE) - ) - - with engine.connect() as conn: - conn.execute( - data_table.insert(), - data = {"key1": "value1", "key2": "value2"} - ) - - :class:`.HSTORE` provides for a wide range of operations, including: - - * Index operations:: - - data_table.c.data['some key'] == 'some value' - - * Containment operations:: - - data_table.c.data.has_key('some key') - - data_table.c.data.has_all(['one', 'two', 'three']) - - * Concatenation:: - - data_table.c.data + {"k1": "v1"} - - For a full list of special methods see - :class:`.HSTORE.comparator_factory`. - - .. container:: topic - - **Detecting Changes in HSTORE columns when using the ORM** - - For usage with the SQLAlchemy ORM, it may be desirable to combine the - usage of :class:`.HSTORE` with :class:`.MutableDict` dictionary now - part of the :mod:`sqlalchemy.ext.mutable` extension. This extension - will allow "in-place" changes to the dictionary, e.g. addition of new - keys or replacement/removal of existing keys to/from the current - dictionary, to produce events which will be detected by the unit of - work:: - - from sqlalchemy.ext.mutable import MutableDict - - class MyClass(Base): - __tablename__ = 'data_table' - - id = Column(Integer, primary_key=True) - data = Column(MutableDict.as_mutable(HSTORE)) - - my_object = session.query(MyClass).one() - - # in-place mutation, requires Mutable extension - # in order for the ORM to detect - my_object.data['some_key'] = 'some value' - - session.commit() - - When the :mod:`sqlalchemy.ext.mutable` extension is not used, the ORM - will not be alerted to any changes to the contents of an existing - dictionary, unless that dictionary value is re-assigned to the - HSTORE-attribute itself, thus generating a change event. - - .. seealso:: - - :class:`.hstore` - render the PostgreSQL ``hstore()`` function. - - - """ - - __visit_name__ = "HSTORE" - hashable = False - text_type = sqltypes.Text() - - def __init__(self, text_type=None): - """Construct a new :class:`.HSTORE`. - - :param text_type: the type that should be used for indexed values. - Defaults to :class:`_types.Text`. - - """ - if text_type is not None: - self.text_type = text_type - - class Comparator( - sqltypes.Indexable.Comparator, sqltypes.Concatenable.Comparator - ): - """Define comparison operations for :class:`.HSTORE`.""" - - def has_key(self, other): - """Boolean expression. Test for presence of a key. Note that the - key may be a SQLA expression. - """ - return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean) - - def has_all(self, other): - """Boolean expression. Test for presence of all keys in jsonb""" - return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean) - - def has_any(self, other): - """Boolean expression. Test for presence of any key in jsonb""" - return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean) - - def contains(self, other, **kwargs): - """Boolean expression. Test if keys (or array) are a superset - of/contained the keys of the argument jsonb expression. - - kwargs may be ignored by this operator but are required for API - conformance. - """ - return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) - - def contained_by(self, other): - """Boolean expression. Test if keys are a proper subset of the - keys of the argument jsonb expression. - """ - return self.operate( - CONTAINED_BY, other, result_type=sqltypes.Boolean - ) - - def _setup_getitem(self, index): - return GETITEM, index, self.type.text_type - - def defined(self, key): - """Boolean expression. Test for presence of a non-NULL value for - the key. Note that the key may be a SQLA expression. - """ - return _HStoreDefinedFunction(self.expr, key) - - def delete(self, key): - """HStore expression. Returns the contents of this hstore with the - given key deleted. Note that the key may be a SQLA expression. - """ - if isinstance(key, dict): - key = _serialize_hstore(key) - return _HStoreDeleteFunction(self.expr, key) - - def slice(self, array): - """HStore expression. Returns a subset of an hstore defined by - array of keys. - """ - return _HStoreSliceFunction(self.expr, array) - - def keys(self): - """Text array expression. Returns array of keys.""" - return _HStoreKeysFunction(self.expr) - - def vals(self): - """Text array expression. Returns array of values.""" - return _HStoreValsFunction(self.expr) - - def array(self): - """Text array expression. Returns array of alternating keys and - values. - """ - return _HStoreArrayFunction(self.expr) - - def matrix(self): - """Text array expression. Returns array of [key, value] pairs.""" - return _HStoreMatrixFunction(self.expr) - - comparator_factory = Comparator - - def bind_processor(self, dialect): - def process(value): - if isinstance(value, dict): - return _serialize_hstore(value) - else: - return value - - return process - - def result_processor(self, dialect, coltype): - def process(value): - if value is not None: - return _parse_hstore(value) - else: - return value - - return process - - -class hstore(sqlfunc.GenericFunction): - """Construct an hstore value within a SQL expression using the - PostgreSQL ``hstore()`` function. - - The :class:`.hstore` function accepts one or two arguments as described - in the PostgreSQL documentation. - - E.g.:: - - from sqlalchemy.dialects.postgresql import array, hstore - - select(hstore('key1', 'value1')) - - select( - hstore( - array(['key1', 'key2', 'key3']), - array(['value1', 'value2', 'value3']) - ) - ) - - .. seealso:: - - :class:`.HSTORE` - the PostgreSQL ``HSTORE`` datatype. - - """ - - type = HSTORE - name = "hstore" - inherit_cache = True - - -class _HStoreDefinedFunction(sqlfunc.GenericFunction): - type = sqltypes.Boolean - name = "defined" - inherit_cache = True - - -class _HStoreDeleteFunction(sqlfunc.GenericFunction): - type = HSTORE - name = "delete" - inherit_cache = True - - -class _HStoreSliceFunction(sqlfunc.GenericFunction): - type = HSTORE - name = "slice" - inherit_cache = True - - -class _HStoreKeysFunction(sqlfunc.GenericFunction): - type = ARRAY(sqltypes.Text) - name = "akeys" - inherit_cache = True - - -class _HStoreValsFunction(sqlfunc.GenericFunction): - type = ARRAY(sqltypes.Text) - name = "avals" - inherit_cache = True - - -class _HStoreArrayFunction(sqlfunc.GenericFunction): - type = ARRAY(sqltypes.Text) - name = "hstore_to_array" - inherit_cache = True - - -class _HStoreMatrixFunction(sqlfunc.GenericFunction): - type = ARRAY(sqltypes.Text) - name = "hstore_to_matrix" - inherit_cache = True - - -# -# parsing. note that none of this is used with the psycopg2 backend, -# which provides its own native extensions. -# - -# My best guess at the parsing rules of hstore literals, since no formal -# grammar is given. This is mostly reverse engineered from PG's input parser -# behavior. -HSTORE_PAIR_RE = re.compile( - r""" -( - "(?P (\\ . | [^"])* )" # Quoted key -) -[ ]* => [ ]* # Pair operator, optional adjoining whitespace -( - (?P NULL ) # NULL value - | "(?P (\\ . | [^"])* )" # Quoted value -) -""", - re.VERBOSE, -) - -HSTORE_DELIMITER_RE = re.compile( - r""" -[ ]* , [ ]* -""", - re.VERBOSE, -) - - -def _parse_error(hstore_str, pos): - """format an unmarshalling error.""" - - ctx = 20 - hslen = len(hstore_str) - - parsed_tail = hstore_str[max(pos - ctx - 1, 0) : min(pos, hslen)] - residual = hstore_str[min(pos, hslen) : min(pos + ctx + 1, hslen)] - - if len(parsed_tail) > ctx: - parsed_tail = "[...]" + parsed_tail[1:] - if len(residual) > ctx: - residual = residual[:-1] + "[...]" - - return "After %r, could not parse residual at position %d: %r" % ( - parsed_tail, - pos, - residual, - ) - - -def _parse_hstore(hstore_str): - """Parse an hstore from its literal string representation. - - Attempts to approximate PG's hstore input parsing rules as closely as - possible. Although currently this is not strictly necessary, since the - current implementation of hstore's output syntax is stricter than what it - accepts as input, the documentation makes no guarantees that will always - be the case. - - - - """ - result = {} - pos = 0 - pair_match = HSTORE_PAIR_RE.match(hstore_str) - - while pair_match is not None: - key = pair_match.group("key").replace(r"\"", '"').replace("\\\\", "\\") - if pair_match.group("value_null"): - value = None - else: - value = ( - pair_match.group("value") - .replace(r"\"", '"') - .replace("\\\\", "\\") - ) - result[key] = value - - pos += pair_match.end() - - delim_match = HSTORE_DELIMITER_RE.match(hstore_str[pos:]) - if delim_match is not None: - pos += delim_match.end() - - pair_match = HSTORE_PAIR_RE.match(hstore_str[pos:]) - - if pos != len(hstore_str): - raise ValueError(_parse_error(hstore_str, pos)) - - return result - - -def _serialize_hstore(val): - """Serialize a dictionary into an hstore literal. Keys and values must - both be strings (except None for values). - - """ - - def esc(s, position): - if position == "value" and s is None: - return "NULL" - elif isinstance(s, str): - return '"%s"' % s.replace("\\", "\\\\").replace('"', r"\"") - else: - raise ValueError( - "%r in %s position is not a string." % (s, position) - ) - - return ", ".join( - "%s=>%s" % (esc(k, "key"), esc(v, "value")) for k, v in val.items() - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/json.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/json.py deleted file mode 100644 index 3790fa3..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/json.py +++ /dev/null @@ -1,325 +0,0 @@ -# dialects/postgresql/json.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -from .array import ARRAY -from .array import array as _pg_array -from .operators import ASTEXT -from .operators import CONTAINED_BY -from .operators import CONTAINS -from .operators import DELETE_PATH -from .operators import HAS_ALL -from .operators import HAS_ANY -from .operators import HAS_KEY -from .operators import JSONPATH_ASTEXT -from .operators import PATH_EXISTS -from .operators import PATH_MATCH -from ... import types as sqltypes -from ...sql import cast - -__all__ = ("JSON", "JSONB") - - -class JSONPathType(sqltypes.JSON.JSONPathType): - def _processor(self, dialect, super_proc): - def process(value): - if isinstance(value, str): - # If it's already a string assume that it's in json path - # format. This allows using cast with json paths literals - return value - elif value: - # If it's already a string assume that it's in json path - # format. This allows using cast with json paths literals - value = "{%s}" % (", ".join(map(str, value))) - else: - value = "{}" - if super_proc: - value = super_proc(value) - return value - - return process - - def bind_processor(self, dialect): - return self._processor(dialect, self.string_bind_processor(dialect)) - - def literal_processor(self, dialect): - return self._processor(dialect, self.string_literal_processor(dialect)) - - -class JSONPATH(JSONPathType): - """JSON Path Type. - - This is usually required to cast literal values to json path when using - json search like function, such as ``jsonb_path_query_array`` or - ``jsonb_path_exists``:: - - stmt = sa.select( - sa.func.jsonb_path_query_array( - table.c.jsonb_col, cast("$.address.id", JSONPATH) - ) - ) - - """ - - __visit_name__ = "JSONPATH" - - -class JSON(sqltypes.JSON): - """Represent the PostgreSQL JSON type. - - :class:`_postgresql.JSON` is used automatically whenever the base - :class:`_types.JSON` datatype is used against a PostgreSQL backend, - however base :class:`_types.JSON` datatype does not provide Python - accessors for PostgreSQL-specific comparison methods such as - :meth:`_postgresql.JSON.Comparator.astext`; additionally, to use - PostgreSQL ``JSONB``, the :class:`_postgresql.JSONB` datatype should - be used explicitly. - - .. seealso:: - - :class:`_types.JSON` - main documentation for the generic - cross-platform JSON datatype. - - The operators provided by the PostgreSQL version of :class:`_types.JSON` - include: - - * Index operations (the ``->`` operator):: - - data_table.c.data['some key'] - - data_table.c.data[5] - - - * Index operations returning text (the ``->>`` operator):: - - data_table.c.data['some key'].astext == 'some value' - - Note that equivalent functionality is available via the - :attr:`.JSON.Comparator.as_string` accessor. - - * Index operations with CAST - (equivalent to ``CAST(col ->> ['some key'] AS )``):: - - data_table.c.data['some key'].astext.cast(Integer) == 5 - - Note that equivalent functionality is available via the - :attr:`.JSON.Comparator.as_integer` and similar accessors. - - * Path index operations (the ``#>`` operator):: - - data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')] - - * Path index operations returning text (the ``#>>`` operator):: - - data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')].astext == 'some value' - - Index operations return an expression object whose type defaults to - :class:`_types.JSON` by default, - so that further JSON-oriented instructions - may be called upon the result type. - - Custom serializers and deserializers are specified at the dialect level, - that is using :func:`_sa.create_engine`. The reason for this is that when - using psycopg2, the DBAPI only allows serializers at the per-cursor - or per-connection level. E.g.:: - - engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test", - json_serializer=my_serialize_fn, - json_deserializer=my_deserialize_fn - ) - - When using the psycopg2 dialect, the json_deserializer is registered - against the database using ``psycopg2.extras.register_default_json``. - - .. seealso:: - - :class:`_types.JSON` - Core level JSON type - - :class:`_postgresql.JSONB` - - """ # noqa - - astext_type = sqltypes.Text() - - def __init__(self, none_as_null=False, astext_type=None): - """Construct a :class:`_types.JSON` type. - - :param none_as_null: if True, persist the value ``None`` as a - SQL NULL value, not the JSON encoding of ``null``. Note that - when this flag is False, the :func:`.null` construct can still - be used to persist a NULL value:: - - from sqlalchemy import null - conn.execute(table.insert(), {"data": null()}) - - .. seealso:: - - :attr:`_types.JSON.NULL` - - :param astext_type: the type to use for the - :attr:`.JSON.Comparator.astext` - accessor on indexed attributes. Defaults to :class:`_types.Text`. - - """ - super().__init__(none_as_null=none_as_null) - if astext_type is not None: - self.astext_type = astext_type - - class Comparator(sqltypes.JSON.Comparator): - """Define comparison operations for :class:`_types.JSON`.""" - - @property - def astext(self): - """On an indexed expression, use the "astext" (e.g. "->>") - conversion when rendered in SQL. - - E.g.:: - - select(data_table.c.data['some key'].astext) - - .. seealso:: - - :meth:`_expression.ColumnElement.cast` - - """ - if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType): - return self.expr.left.operate( - JSONPATH_ASTEXT, - self.expr.right, - result_type=self.type.astext_type, - ) - else: - return self.expr.left.operate( - ASTEXT, self.expr.right, result_type=self.type.astext_type - ) - - comparator_factory = Comparator - - -class JSONB(JSON): - """Represent the PostgreSQL JSONB type. - - The :class:`_postgresql.JSONB` type stores arbitrary JSONB format data, - e.g.:: - - data_table = Table('data_table', metadata, - Column('id', Integer, primary_key=True), - Column('data', JSONB) - ) - - with engine.connect() as conn: - conn.execute( - data_table.insert(), - data = {"key1": "value1", "key2": "value2"} - ) - - The :class:`_postgresql.JSONB` type includes all operations provided by - :class:`_types.JSON`, including the same behaviors for indexing - operations. - It also adds additional operators specific to JSONB, including - :meth:`.JSONB.Comparator.has_key`, :meth:`.JSONB.Comparator.has_all`, - :meth:`.JSONB.Comparator.has_any`, :meth:`.JSONB.Comparator.contains`, - :meth:`.JSONB.Comparator.contained_by`, - :meth:`.JSONB.Comparator.delete_path`, - :meth:`.JSONB.Comparator.path_exists` and - :meth:`.JSONB.Comparator.path_match`. - - Like the :class:`_types.JSON` type, the :class:`_postgresql.JSONB` - type does not detect - in-place changes when used with the ORM, unless the - :mod:`sqlalchemy.ext.mutable` extension is used. - - Custom serializers and deserializers - are shared with the :class:`_types.JSON` class, - using the ``json_serializer`` - and ``json_deserializer`` keyword arguments. These must be specified - at the dialect level using :func:`_sa.create_engine`. When using - psycopg2, the serializers are associated with the jsonb type using - ``psycopg2.extras.register_default_jsonb`` on a per-connection basis, - in the same way that ``psycopg2.extras.register_default_json`` is used - to register these handlers with the json type. - - .. seealso:: - - :class:`_types.JSON` - - """ - - __visit_name__ = "JSONB" - - class Comparator(JSON.Comparator): - """Define comparison operations for :class:`_types.JSON`.""" - - def has_key(self, other): - """Boolean expression. Test for presence of a key. Note that the - key may be a SQLA expression. - """ - return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean) - - def has_all(self, other): - """Boolean expression. Test for presence of all keys in jsonb""" - return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean) - - def has_any(self, other): - """Boolean expression. Test for presence of any key in jsonb""" - return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean) - - def contains(self, other, **kwargs): - """Boolean expression. Test if keys (or array) are a superset - of/contained the keys of the argument jsonb expression. - - kwargs may be ignored by this operator but are required for API - conformance. - """ - return self.operate(CONTAINS, other, result_type=sqltypes.Boolean) - - def contained_by(self, other): - """Boolean expression. Test if keys are a proper subset of the - keys of the argument jsonb expression. - """ - return self.operate( - CONTAINED_BY, other, result_type=sqltypes.Boolean - ) - - def delete_path(self, array): - """JSONB expression. Deletes field or array element specified in - the argument array. - - The input may be a list of strings that will be coerced to an - ``ARRAY`` or an instance of :meth:`_postgres.array`. - - .. versionadded:: 2.0 - """ - if not isinstance(array, _pg_array): - array = _pg_array(array) - right_side = cast(array, ARRAY(sqltypes.TEXT)) - return self.operate(DELETE_PATH, right_side, result_type=JSONB) - - def path_exists(self, other): - """Boolean expression. Test for presence of item given by the - argument JSONPath expression. - - .. versionadded:: 2.0 - """ - return self.operate( - PATH_EXISTS, other, result_type=sqltypes.Boolean - ) - - def path_match(self, other): - """Boolean expression. Test if JSONPath predicate given by the - argument JSONPath expression matches. - - Only the first item of the result is taken into account. - - .. versionadded:: 2.0 - """ - return self.operate( - PATH_MATCH, other, result_type=sqltypes.Boolean - ) - - comparator_factory = Comparator diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/named_types.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/named_types.py deleted file mode 100644 index 16e5c86..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/named_types.py +++ /dev/null @@ -1,509 +0,0 @@ -# dialects/postgresql/named_types.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from __future__ import annotations - -from typing import Any -from typing import Optional -from typing import Type -from typing import TYPE_CHECKING -from typing import Union - -from ... import schema -from ... import util -from ...sql import coercions -from ...sql import elements -from ...sql import roles -from ...sql import sqltypes -from ...sql import type_api -from ...sql.base import _NoArg -from ...sql.ddl import InvokeCreateDDLBase -from ...sql.ddl import InvokeDropDDLBase - -if TYPE_CHECKING: - from ...sql._typing import _TypeEngineArgument - - -class NamedType(sqltypes.TypeEngine): - """Base for named types.""" - - __abstract__ = True - DDLGenerator: Type[NamedTypeGenerator] - DDLDropper: Type[NamedTypeDropper] - create_type: bool - - def create(self, bind, checkfirst=True, **kw): - """Emit ``CREATE`` DDL for this type. - - :param bind: a connectable :class:`_engine.Engine`, - :class:`_engine.Connection`, or similar object to emit - SQL. - :param checkfirst: if ``True``, a query against - the PG catalog will be first performed to see - if the type does not exist already before - creating. - - """ - bind._run_ddl_visitor(self.DDLGenerator, self, checkfirst=checkfirst) - - def drop(self, bind, checkfirst=True, **kw): - """Emit ``DROP`` DDL for this type. - - :param bind: a connectable :class:`_engine.Engine`, - :class:`_engine.Connection`, or similar object to emit - SQL. - :param checkfirst: if ``True``, a query against - the PG catalog will be first performed to see - if the type actually exists before dropping. - - """ - bind._run_ddl_visitor(self.DDLDropper, self, checkfirst=checkfirst) - - def _check_for_name_in_memos(self, checkfirst, kw): - """Look in the 'ddl runner' for 'memos', then - note our name in that collection. - - This to ensure a particular named type is operated - upon only once within any kind of create/drop - sequence without relying upon "checkfirst". - - """ - if not self.create_type: - return True - if "_ddl_runner" in kw: - ddl_runner = kw["_ddl_runner"] - type_name = f"pg_{self.__visit_name__}" - if type_name in ddl_runner.memo: - existing = ddl_runner.memo[type_name] - else: - existing = ddl_runner.memo[type_name] = set() - present = (self.schema, self.name) in existing - existing.add((self.schema, self.name)) - return present - else: - return False - - def _on_table_create(self, target, bind, checkfirst=False, **kw): - if ( - checkfirst - or ( - not self.metadata - and not kw.get("_is_metadata_operation", False) - ) - ) and not self._check_for_name_in_memos(checkfirst, kw): - self.create(bind=bind, checkfirst=checkfirst) - - def _on_table_drop(self, target, bind, checkfirst=False, **kw): - if ( - not self.metadata - and not kw.get("_is_metadata_operation", False) - and not self._check_for_name_in_memos(checkfirst, kw) - ): - self.drop(bind=bind, checkfirst=checkfirst) - - def _on_metadata_create(self, target, bind, checkfirst=False, **kw): - if not self._check_for_name_in_memos(checkfirst, kw): - self.create(bind=bind, checkfirst=checkfirst) - - def _on_metadata_drop(self, target, bind, checkfirst=False, **kw): - if not self._check_for_name_in_memos(checkfirst, kw): - self.drop(bind=bind, checkfirst=checkfirst) - - -class NamedTypeGenerator(InvokeCreateDDLBase): - def __init__(self, dialect, connection, checkfirst=False, **kwargs): - super().__init__(connection, **kwargs) - self.checkfirst = checkfirst - - def _can_create_type(self, type_): - if not self.checkfirst: - return True - - effective_schema = self.connection.schema_for_object(type_) - return not self.connection.dialect.has_type( - self.connection, type_.name, schema=effective_schema - ) - - -class NamedTypeDropper(InvokeDropDDLBase): - def __init__(self, dialect, connection, checkfirst=False, **kwargs): - super().__init__(connection, **kwargs) - self.checkfirst = checkfirst - - def _can_drop_type(self, type_): - if not self.checkfirst: - return True - - effective_schema = self.connection.schema_for_object(type_) - return self.connection.dialect.has_type( - self.connection, type_.name, schema=effective_schema - ) - - -class EnumGenerator(NamedTypeGenerator): - def visit_enum(self, enum): - if not self._can_create_type(enum): - return - - with self.with_ddl_events(enum): - self.connection.execute(CreateEnumType(enum)) - - -class EnumDropper(NamedTypeDropper): - def visit_enum(self, enum): - if not self._can_drop_type(enum): - return - - with self.with_ddl_events(enum): - self.connection.execute(DropEnumType(enum)) - - -class ENUM(NamedType, type_api.NativeForEmulated, sqltypes.Enum): - """PostgreSQL ENUM type. - - This is a subclass of :class:`_types.Enum` which includes - support for PG's ``CREATE TYPE`` and ``DROP TYPE``. - - When the builtin type :class:`_types.Enum` is used and the - :paramref:`.Enum.native_enum` flag is left at its default of - True, the PostgreSQL backend will use a :class:`_postgresql.ENUM` - type as the implementation, so the special create/drop rules - will be used. - - The create/drop behavior of ENUM is necessarily intricate, due to the - awkward relationship the ENUM type has in relationship to the - parent table, in that it may be "owned" by just a single table, or - may be shared among many tables. - - When using :class:`_types.Enum` or :class:`_postgresql.ENUM` - in an "inline" fashion, the ``CREATE TYPE`` and ``DROP TYPE`` is emitted - corresponding to when the :meth:`_schema.Table.create` and - :meth:`_schema.Table.drop` - methods are called:: - - table = Table('sometable', metadata, - Column('some_enum', ENUM('a', 'b', 'c', name='myenum')) - ) - - table.create(engine) # will emit CREATE ENUM and CREATE TABLE - table.drop(engine) # will emit DROP TABLE and DROP ENUM - - To use a common enumerated type between multiple tables, the best - practice is to declare the :class:`_types.Enum` or - :class:`_postgresql.ENUM` independently, and associate it with the - :class:`_schema.MetaData` object itself:: - - my_enum = ENUM('a', 'b', 'c', name='myenum', metadata=metadata) - - t1 = Table('sometable_one', metadata, - Column('some_enum', myenum) - ) - - t2 = Table('sometable_two', metadata, - Column('some_enum', myenum) - ) - - When this pattern is used, care must still be taken at the level - of individual table creates. Emitting CREATE TABLE without also - specifying ``checkfirst=True`` will still cause issues:: - - t1.create(engine) # will fail: no such type 'myenum' - - If we specify ``checkfirst=True``, the individual table-level create - operation will check for the ``ENUM`` and create if not exists:: - - # will check if enum exists, and emit CREATE TYPE if not - t1.create(engine, checkfirst=True) - - When using a metadata-level ENUM type, the type will always be created - and dropped if either the metadata-wide create/drop is called:: - - metadata.create_all(engine) # will emit CREATE TYPE - metadata.drop_all(engine) # will emit DROP TYPE - - The type can also be created and dropped directly:: - - my_enum.create(engine) - my_enum.drop(engine) - - """ - - native_enum = True - DDLGenerator = EnumGenerator - DDLDropper = EnumDropper - - def __init__( - self, - *enums, - name: Union[str, _NoArg, None] = _NoArg.NO_ARG, - create_type: bool = True, - **kw, - ): - """Construct an :class:`_postgresql.ENUM`. - - Arguments are the same as that of - :class:`_types.Enum`, but also including - the following parameters. - - :param create_type: Defaults to True. - Indicates that ``CREATE TYPE`` should be - emitted, after optionally checking for the - presence of the type, when the parent - table is being created; and additionally - that ``DROP TYPE`` is called when the table - is dropped. When ``False``, no check - will be performed and no ``CREATE TYPE`` - or ``DROP TYPE`` is emitted, unless - :meth:`~.postgresql.ENUM.create` - or :meth:`~.postgresql.ENUM.drop` - are called directly. - Setting to ``False`` is helpful - when invoking a creation scheme to a SQL file - without access to the actual database - - the :meth:`~.postgresql.ENUM.create` and - :meth:`~.postgresql.ENUM.drop` methods can - be used to emit SQL to a target bind. - - """ - native_enum = kw.pop("native_enum", None) - if native_enum is False: - util.warn( - "the native_enum flag does not apply to the " - "sqlalchemy.dialects.postgresql.ENUM datatype; this type " - "always refers to ENUM. Use sqlalchemy.types.Enum for " - "non-native enum." - ) - self.create_type = create_type - if name is not _NoArg.NO_ARG: - kw["name"] = name - super().__init__(*enums, **kw) - - def coerce_compared_value(self, op, value): - super_coerced_type = super().coerce_compared_value(op, value) - if ( - super_coerced_type._type_affinity - is type_api.STRINGTYPE._type_affinity - ): - return self - else: - return super_coerced_type - - @classmethod - def __test_init__(cls): - return cls(name="name") - - @classmethod - def adapt_emulated_to_native(cls, impl, **kw): - """Produce a PostgreSQL native :class:`_postgresql.ENUM` from plain - :class:`.Enum`. - - """ - kw.setdefault("validate_strings", impl.validate_strings) - kw.setdefault("name", impl.name) - kw.setdefault("schema", impl.schema) - kw.setdefault("inherit_schema", impl.inherit_schema) - kw.setdefault("metadata", impl.metadata) - kw.setdefault("_create_events", False) - kw.setdefault("values_callable", impl.values_callable) - kw.setdefault("omit_aliases", impl._omit_aliases) - kw.setdefault("_adapted_from", impl) - if type_api._is_native_for_emulated(impl.__class__): - kw.setdefault("create_type", impl.create_type) - - return cls(**kw) - - def create(self, bind=None, checkfirst=True): - """Emit ``CREATE TYPE`` for this - :class:`_postgresql.ENUM`. - - If the underlying dialect does not support - PostgreSQL CREATE TYPE, no action is taken. - - :param bind: a connectable :class:`_engine.Engine`, - :class:`_engine.Connection`, or similar object to emit - SQL. - :param checkfirst: if ``True``, a query against - the PG catalog will be first performed to see - if the type does not exist already before - creating. - - """ - if not bind.dialect.supports_native_enum: - return - - super().create(bind, checkfirst=checkfirst) - - def drop(self, bind=None, checkfirst=True): - """Emit ``DROP TYPE`` for this - :class:`_postgresql.ENUM`. - - If the underlying dialect does not support - PostgreSQL DROP TYPE, no action is taken. - - :param bind: a connectable :class:`_engine.Engine`, - :class:`_engine.Connection`, or similar object to emit - SQL. - :param checkfirst: if ``True``, a query against - the PG catalog will be first performed to see - if the type actually exists before dropping. - - """ - if not bind.dialect.supports_native_enum: - return - - super().drop(bind, checkfirst=checkfirst) - - def get_dbapi_type(self, dbapi): - """dont return dbapi.STRING for ENUM in PostgreSQL, since that's - a different type""" - - return None - - -class DomainGenerator(NamedTypeGenerator): - def visit_DOMAIN(self, domain): - if not self._can_create_type(domain): - return - with self.with_ddl_events(domain): - self.connection.execute(CreateDomainType(domain)) - - -class DomainDropper(NamedTypeDropper): - def visit_DOMAIN(self, domain): - if not self._can_drop_type(domain): - return - - with self.with_ddl_events(domain): - self.connection.execute(DropDomainType(domain)) - - -class DOMAIN(NamedType, sqltypes.SchemaType): - r"""Represent the DOMAIN PostgreSQL type. - - A domain is essentially a data type with optional constraints - that restrict the allowed set of values. E.g.:: - - PositiveInt = DOMAIN( - "pos_int", Integer, check="VALUE > 0", not_null=True - ) - - UsPostalCode = DOMAIN( - "us_postal_code", - Text, - check="VALUE ~ '^\d{5}$' OR VALUE ~ '^\d{5}-\d{4}$'" - ) - - See the `PostgreSQL documentation`__ for additional details - - __ https://www.postgresql.org/docs/current/sql-createdomain.html - - .. versionadded:: 2.0 - - """ - - DDLGenerator = DomainGenerator - DDLDropper = DomainDropper - - __visit_name__ = "DOMAIN" - - def __init__( - self, - name: str, - data_type: _TypeEngineArgument[Any], - *, - collation: Optional[str] = None, - default: Union[elements.TextClause, str, None] = None, - constraint_name: Optional[str] = None, - not_null: Optional[bool] = None, - check: Union[elements.TextClause, str, None] = None, - create_type: bool = True, - **kw: Any, - ): - """ - Construct a DOMAIN. - - :param name: the name of the domain - :param data_type: The underlying data type of the domain. - This can include array specifiers. - :param collation: An optional collation for the domain. - If no collation is specified, the underlying data type's default - collation is used. The underlying type must be collatable if - ``collation`` is specified. - :param default: The DEFAULT clause specifies a default value for - columns of the domain data type. The default should be a string - or a :func:`_expression.text` value. - If no default value is specified, then the default value is - the null value. - :param constraint_name: An optional name for a constraint. - If not specified, the backend generates a name. - :param not_null: Values of this domain are prevented from being null. - By default domain are allowed to be null. If not specified - no nullability clause will be emitted. - :param check: CHECK clause specify integrity constraint or test - which values of the domain must satisfy. A constraint must be - an expression producing a Boolean result that can use the key - word VALUE to refer to the value being tested. - Differently from PostgreSQL, only a single check clause is - currently allowed in SQLAlchemy. - :param schema: optional schema name - :param metadata: optional :class:`_schema.MetaData` object which - this :class:`_postgresql.DOMAIN` will be directly associated - :param create_type: Defaults to True. - Indicates that ``CREATE TYPE`` should be emitted, after optionally - checking for the presence of the type, when the parent table is - being created; and additionally that ``DROP TYPE`` is called - when the table is dropped. - - """ - self.data_type = type_api.to_instance(data_type) - self.default = default - self.collation = collation - self.constraint_name = constraint_name - self.not_null = bool(not_null) - if check is not None: - check = coercions.expect(roles.DDLExpressionRole, check) - self.check = check - self.create_type = create_type - super().__init__(name=name, **kw) - - @classmethod - def __test_init__(cls): - return cls("name", sqltypes.Integer) - - def adapt(self, impl, **kw): - if self.default: - kw["default"] = self.default - if self.constraint_name is not None: - kw["constraint_name"] = self.constraint_name - if self.not_null: - kw["not_null"] = self.not_null - if self.check is not None: - kw["check"] = str(self.check) - if self.create_type: - kw["create_type"] = self.create_type - - return super().adapt(impl, **kw) - - -class CreateEnumType(schema._CreateDropBase): - __visit_name__ = "create_enum_type" - - -class DropEnumType(schema._CreateDropBase): - __visit_name__ = "drop_enum_type" - - -class CreateDomainType(schema._CreateDropBase): - """Represent a CREATE DOMAIN statement.""" - - __visit_name__ = "create_domain_type" - - -class DropDomainType(schema._CreateDropBase): - """Represent a DROP DOMAIN statement.""" - - __visit_name__ = "drop_domain_type" diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/operators.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/operators.py deleted file mode 100644 index 53e175f..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/operators.py +++ /dev/null @@ -1,129 +0,0 @@ -# dialects/postgresql/operators.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors -from ...sql import operators - - -_getitem_precedence = operators._PRECEDENCE[operators.json_getitem_op] -_eq_precedence = operators._PRECEDENCE[operators.eq] - -# JSON + JSONB -ASTEXT = operators.custom_op( - "->>", - precedence=_getitem_precedence, - natural_self_precedent=True, - eager_grouping=True, -) - -JSONPATH_ASTEXT = operators.custom_op( - "#>>", - precedence=_getitem_precedence, - natural_self_precedent=True, - eager_grouping=True, -) - -# JSONB + HSTORE -HAS_KEY = operators.custom_op( - "?", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -HAS_ALL = operators.custom_op( - "?&", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -HAS_ANY = operators.custom_op( - "?|", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -# JSONB -DELETE_PATH = operators.custom_op( - "#-", - precedence=_getitem_precedence, - natural_self_precedent=True, - eager_grouping=True, -) - -PATH_EXISTS = operators.custom_op( - "@?", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -PATH_MATCH = operators.custom_op( - "@@", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -# JSONB + ARRAY + HSTORE + RANGE -CONTAINS = operators.custom_op( - "@>", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -CONTAINED_BY = operators.custom_op( - "<@", - precedence=_eq_precedence, - natural_self_precedent=True, - eager_grouping=True, - is_comparison=True, -) - -# ARRAY + RANGE -OVERLAP = operators.custom_op( - "&&", - precedence=_eq_precedence, - is_comparison=True, -) - -# RANGE -STRICTLY_LEFT_OF = operators.custom_op( - "<<", precedence=_eq_precedence, is_comparison=True -) - -STRICTLY_RIGHT_OF = operators.custom_op( - ">>", precedence=_eq_precedence, is_comparison=True -) - -NOT_EXTEND_RIGHT_OF = operators.custom_op( - "&<", precedence=_eq_precedence, is_comparison=True -) - -NOT_EXTEND_LEFT_OF = operators.custom_op( - "&>", precedence=_eq_precedence, is_comparison=True -) - -ADJACENT_TO = operators.custom_op( - "-|-", precedence=_eq_precedence, is_comparison=True -) - -# HSTORE -GETITEM = operators.custom_op( - "->", - precedence=_getitem_precedence, - natural_self_precedent=True, - eager_grouping=True, -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg8000.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg8000.py deleted file mode 100644 index 0151be0..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg8000.py +++ /dev/null @@ -1,662 +0,0 @@ -# dialects/postgresql/pg8000.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql+pg8000 - :name: pg8000 - :dbapi: pg8000 - :connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...] - :url: https://pypi.org/project/pg8000/ - -.. versionchanged:: 1.4 The pg8000 dialect has been updated for version - 1.16.6 and higher, and is again part of SQLAlchemy's continuous integration - with full feature support. - -.. _pg8000_unicode: - -Unicode -------- - -pg8000 will encode / decode string values between it and the server using the -PostgreSQL ``client_encoding`` parameter; by default this is the value in -the ``postgresql.conf`` file, which often defaults to ``SQL_ASCII``. -Typically, this can be changed to ``utf-8``, as a more useful default:: - - #client_encoding = sql_ascii # actually, defaults to database - # encoding - client_encoding = utf8 - -The ``client_encoding`` can be overridden for a session by executing the SQL: - -SET CLIENT_ENCODING TO 'utf8'; - -SQLAlchemy will execute this SQL on all new connections based on the value -passed to :func:`_sa.create_engine` using the ``client_encoding`` parameter:: - - engine = create_engine( - "postgresql+pg8000://user:pass@host/dbname", client_encoding='utf8') - -.. _pg8000_ssl: - -SSL Connections ---------------- - -pg8000 accepts a Python ``SSLContext`` object which may be specified using the -:paramref:`_sa.create_engine.connect_args` dictionary:: - - import ssl - ssl_context = ssl.create_default_context() - engine = sa.create_engine( - "postgresql+pg8000://scott:tiger@192.168.0.199/test", - connect_args={"ssl_context": ssl_context}, - ) - -If the server uses an automatically-generated certificate that is self-signed -or does not match the host name (as seen from the client), it may also be -necessary to disable hostname checking:: - - import ssl - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - engine = sa.create_engine( - "postgresql+pg8000://scott:tiger@192.168.0.199/test", - connect_args={"ssl_context": ssl_context}, - ) - -.. _pg8000_isolation_level: - -pg8000 Transaction Isolation Level -------------------------------------- - -The pg8000 dialect offers the same isolation level settings as that -of the :ref:`psycopg2 ` dialect: - -* ``READ COMMITTED`` -* ``READ UNCOMMITTED`` -* ``REPEATABLE READ`` -* ``SERIALIZABLE`` -* ``AUTOCOMMIT`` - -.. seealso:: - - :ref:`postgresql_isolation_level` - - :ref:`psycopg2_isolation_level` - - -""" # noqa -import decimal -import re - -from . import ranges -from .array import ARRAY as PGARRAY -from .base import _DECIMAL_TYPES -from .base import _FLOAT_TYPES -from .base import _INT_TYPES -from .base import ENUM -from .base import INTERVAL -from .base import PGCompiler -from .base import PGDialect -from .base import PGExecutionContext -from .base import PGIdentifierPreparer -from .json import JSON -from .json import JSONB -from .json import JSONPathType -from .pg_catalog import _SpaceVector -from .pg_catalog import OIDVECTOR -from .types import CITEXT -from ... import exc -from ... import util -from ...engine import processors -from ...sql import sqltypes -from ...sql.elements import quoted_name - - -class _PGString(sqltypes.String): - render_bind_cast = True - - -class _PGNumeric(sqltypes.Numeric): - render_bind_cast = True - - def result_processor(self, dialect, coltype): - if self.asdecimal: - if coltype in _FLOAT_TYPES: - return processors.to_decimal_processor_factory( - decimal.Decimal, self._effective_decimal_return_scale - ) - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - # pg8000 returns Decimal natively for 1700 - return None - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - else: - if coltype in _FLOAT_TYPES: - # pg8000 returns float natively for 701 - return None - elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES: - return processors.to_float - else: - raise exc.InvalidRequestError( - "Unknown PG numeric type: %d" % coltype - ) - - -class _PGFloat(_PGNumeric, sqltypes.Float): - __visit_name__ = "float" - render_bind_cast = True - - -class _PGNumericNoBind(_PGNumeric): - def bind_processor(self, dialect): - return None - - -class _PGJSON(JSON): - render_bind_cast = True - - def result_processor(self, dialect, coltype): - return None - - -class _PGJSONB(JSONB): - render_bind_cast = True - - def result_processor(self, dialect, coltype): - return None - - -class _PGJSONIndexType(sqltypes.JSON.JSONIndexType): - def get_dbapi_type(self, dbapi): - raise NotImplementedError("should not be here") - - -class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType): - __visit_name__ = "json_int_index" - - render_bind_cast = True - - -class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType): - __visit_name__ = "json_str_index" - - render_bind_cast = True - - -class _PGJSONPathType(JSONPathType): - pass - - # DBAPI type 1009 - - -class _PGEnum(ENUM): - def get_dbapi_type(self, dbapi): - return dbapi.UNKNOWN - - -class _PGInterval(INTERVAL): - render_bind_cast = True - - def get_dbapi_type(self, dbapi): - return dbapi.INTERVAL - - @classmethod - def adapt_emulated_to_native(cls, interval, **kw): - return _PGInterval(precision=interval.second_precision) - - -class _PGTimeStamp(sqltypes.DateTime): - render_bind_cast = True - - -class _PGDate(sqltypes.Date): - render_bind_cast = True - - -class _PGTime(sqltypes.Time): - render_bind_cast = True - - -class _PGInteger(sqltypes.Integer): - render_bind_cast = True - - -class _PGSmallInteger(sqltypes.SmallInteger): - render_bind_cast = True - - -class _PGNullType(sqltypes.NullType): - pass - - -class _PGBigInteger(sqltypes.BigInteger): - render_bind_cast = True - - -class _PGBoolean(sqltypes.Boolean): - render_bind_cast = True - - -class _PGARRAY(PGARRAY): - render_bind_cast = True - - -class _PGOIDVECTOR(_SpaceVector, OIDVECTOR): - pass - - -class _Pg8000Range(ranges.AbstractSingleRangeImpl): - def bind_processor(self, dialect): - pg8000_Range = dialect.dbapi.Range - - def to_range(value): - if isinstance(value, ranges.Range): - value = pg8000_Range( - value.lower, value.upper, value.bounds, value.empty - ) - return value - - return to_range - - def result_processor(self, dialect, coltype): - def to_range(value): - if value is not None: - value = ranges.Range( - value.lower, - value.upper, - bounds=value.bounds, - empty=value.is_empty, - ) - return value - - return to_range - - -class _Pg8000MultiRange(ranges.AbstractMultiRangeImpl): - def bind_processor(self, dialect): - pg8000_Range = dialect.dbapi.Range - - def to_multirange(value): - if isinstance(value, list): - mr = [] - for v in value: - if isinstance(v, ranges.Range): - mr.append( - pg8000_Range(v.lower, v.upper, v.bounds, v.empty) - ) - else: - mr.append(v) - return mr - else: - return value - - return to_multirange - - def result_processor(self, dialect, coltype): - def to_multirange(value): - if value is None: - return None - else: - return ranges.MultiRange( - ranges.Range( - v.lower, v.upper, bounds=v.bounds, empty=v.is_empty - ) - for v in value - ) - - return to_multirange - - -_server_side_id = util.counter() - - -class PGExecutionContext_pg8000(PGExecutionContext): - def create_server_side_cursor(self): - ident = "c_%s_%s" % (hex(id(self))[2:], hex(_server_side_id())[2:]) - return ServerSideCursor(self._dbapi_connection.cursor(), ident) - - def pre_exec(self): - if not self.compiled: - return - - -class ServerSideCursor: - server_side = True - - def __init__(self, cursor, ident): - self.ident = ident - self.cursor = cursor - - @property - def connection(self): - return self.cursor.connection - - @property - def rowcount(self): - return self.cursor.rowcount - - @property - def description(self): - return self.cursor.description - - def execute(self, operation, args=(), stream=None): - op = "DECLARE " + self.ident + " NO SCROLL CURSOR FOR " + operation - self.cursor.execute(op, args, stream=stream) - return self - - def executemany(self, operation, param_sets): - self.cursor.executemany(operation, param_sets) - return self - - def fetchone(self): - self.cursor.execute("FETCH FORWARD 1 FROM " + self.ident) - return self.cursor.fetchone() - - def fetchmany(self, num=None): - if num is None: - return self.fetchall() - else: - self.cursor.execute( - "FETCH FORWARD " + str(int(num)) + " FROM " + self.ident - ) - return self.cursor.fetchall() - - def fetchall(self): - self.cursor.execute("FETCH FORWARD ALL FROM " + self.ident) - return self.cursor.fetchall() - - def close(self): - self.cursor.execute("CLOSE " + self.ident) - self.cursor.close() - - def setinputsizes(self, *sizes): - self.cursor.setinputsizes(*sizes) - - def setoutputsize(self, size, column=None): - pass - - -class PGCompiler_pg8000(PGCompiler): - def visit_mod_binary(self, binary, operator, **kw): - return ( - self.process(binary.left, **kw) - + " %% " - + self.process(binary.right, **kw) - ) - - -class PGIdentifierPreparer_pg8000(PGIdentifierPreparer): - def __init__(self, *args, **kwargs): - PGIdentifierPreparer.__init__(self, *args, **kwargs) - self._double_percents = False - - -class PGDialect_pg8000(PGDialect): - driver = "pg8000" - supports_statement_cache = True - - supports_unicode_statements = True - - supports_unicode_binds = True - - default_paramstyle = "format" - supports_sane_multi_rowcount = True - execution_ctx_cls = PGExecutionContext_pg8000 - statement_compiler = PGCompiler_pg8000 - preparer = PGIdentifierPreparer_pg8000 - supports_server_side_cursors = True - - render_bind_cast = True - - # reversed as of pg8000 1.16.6. 1.16.5 and lower - # are no longer compatible - description_encoding = None - # description_encoding = "use_encoding" - - colspecs = util.update_copy( - PGDialect.colspecs, - { - sqltypes.String: _PGString, - sqltypes.Numeric: _PGNumericNoBind, - sqltypes.Float: _PGFloat, - sqltypes.JSON: _PGJSON, - sqltypes.Boolean: _PGBoolean, - sqltypes.NullType: _PGNullType, - JSONB: _PGJSONB, - CITEXT: CITEXT, - sqltypes.JSON.JSONPathType: _PGJSONPathType, - sqltypes.JSON.JSONIndexType: _PGJSONIndexType, - sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType, - sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType, - sqltypes.Interval: _PGInterval, - INTERVAL: _PGInterval, - sqltypes.DateTime: _PGTimeStamp, - sqltypes.DateTime: _PGTimeStamp, - sqltypes.Date: _PGDate, - sqltypes.Time: _PGTime, - sqltypes.Integer: _PGInteger, - sqltypes.SmallInteger: _PGSmallInteger, - sqltypes.BigInteger: _PGBigInteger, - sqltypes.Enum: _PGEnum, - sqltypes.ARRAY: _PGARRAY, - OIDVECTOR: _PGOIDVECTOR, - ranges.INT4RANGE: _Pg8000Range, - ranges.INT8RANGE: _Pg8000Range, - ranges.NUMRANGE: _Pg8000Range, - ranges.DATERANGE: _Pg8000Range, - ranges.TSRANGE: _Pg8000Range, - ranges.TSTZRANGE: _Pg8000Range, - ranges.INT4MULTIRANGE: _Pg8000MultiRange, - ranges.INT8MULTIRANGE: _Pg8000MultiRange, - ranges.NUMMULTIRANGE: _Pg8000MultiRange, - ranges.DATEMULTIRANGE: _Pg8000MultiRange, - ranges.TSMULTIRANGE: _Pg8000MultiRange, - ranges.TSTZMULTIRANGE: _Pg8000MultiRange, - }, - ) - - def __init__(self, client_encoding=None, **kwargs): - PGDialect.__init__(self, **kwargs) - self.client_encoding = client_encoding - - if self._dbapi_version < (1, 16, 6): - raise NotImplementedError("pg8000 1.16.6 or greater is required") - - if self._native_inet_types: - raise NotImplementedError( - "The pg8000 dialect does not fully implement " - "ipaddress type handling; INET is supported by default, " - "CIDR is not" - ) - - @util.memoized_property - def _dbapi_version(self): - if self.dbapi and hasattr(self.dbapi, "__version__"): - return tuple( - [ - int(x) - for x in re.findall( - r"(\d+)(?:[-\.]?|$)", self.dbapi.__version__ - ) - ] - ) - else: - return (99, 99, 99) - - @classmethod - def import_dbapi(cls): - return __import__("pg8000") - - def create_connect_args(self, url): - opts = url.translate_connect_args(username="user") - if "port" in opts: - opts["port"] = int(opts["port"]) - opts.update(url.query) - return ([], opts) - - def is_disconnect(self, e, connection, cursor): - if isinstance(e, self.dbapi.InterfaceError) and "network error" in str( - e - ): - # new as of pg8000 1.19.0 for broken connections - return True - - # connection was closed normally - return "connection is closed" in str(e) - - def get_isolation_level_values(self, dbapi_connection): - return ( - "AUTOCOMMIT", - "READ COMMITTED", - "READ UNCOMMITTED", - "REPEATABLE READ", - "SERIALIZABLE", - ) - - def set_isolation_level(self, dbapi_connection, level): - level = level.replace("_", " ") - - if level == "AUTOCOMMIT": - dbapi_connection.autocommit = True - else: - dbapi_connection.autocommit = False - cursor = dbapi_connection.cursor() - cursor.execute( - "SET SESSION CHARACTERISTICS AS TRANSACTION " - f"ISOLATION LEVEL {level}" - ) - cursor.execute("COMMIT") - cursor.close() - - def set_readonly(self, connection, value): - cursor = connection.cursor() - try: - cursor.execute( - "SET SESSION CHARACTERISTICS AS TRANSACTION %s" - % ("READ ONLY" if value else "READ WRITE") - ) - cursor.execute("COMMIT") - finally: - cursor.close() - - def get_readonly(self, connection): - cursor = connection.cursor() - try: - cursor.execute("show transaction_read_only") - val = cursor.fetchone()[0] - finally: - cursor.close() - - return val == "on" - - def set_deferrable(self, connection, value): - cursor = connection.cursor() - try: - cursor.execute( - "SET SESSION CHARACTERISTICS AS TRANSACTION %s" - % ("DEFERRABLE" if value else "NOT DEFERRABLE") - ) - cursor.execute("COMMIT") - finally: - cursor.close() - - def get_deferrable(self, connection): - cursor = connection.cursor() - try: - cursor.execute("show transaction_deferrable") - val = cursor.fetchone()[0] - finally: - cursor.close() - - return val == "on" - - def _set_client_encoding(self, dbapi_connection, client_encoding): - cursor = dbapi_connection.cursor() - cursor.execute( - f"""SET CLIENT_ENCODING TO '{ - client_encoding.replace("'", "''") - }'""" - ) - cursor.execute("COMMIT") - cursor.close() - - def do_begin_twophase(self, connection, xid): - connection.connection.tpc_begin((0, xid, "")) - - def do_prepare_twophase(self, connection, xid): - connection.connection.tpc_prepare() - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - connection.connection.tpc_rollback((0, xid, "")) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - connection.connection.tpc_commit((0, xid, "")) - - def do_recover_twophase(self, connection): - return [row[1] for row in connection.connection.tpc_recover()] - - def on_connect(self): - fns = [] - - def on_connect(conn): - conn.py_types[quoted_name] = conn.py_types[str] - - fns.append(on_connect) - - if self.client_encoding is not None: - - def on_connect(conn): - self._set_client_encoding(conn, self.client_encoding) - - fns.append(on_connect) - - if self._native_inet_types is False: - - def on_connect(conn): - # inet - conn.register_in_adapter(869, lambda s: s) - - # cidr - conn.register_in_adapter(650, lambda s: s) - - fns.append(on_connect) - - if self._json_deserializer: - - def on_connect(conn): - # json - conn.register_in_adapter(114, self._json_deserializer) - - # jsonb - conn.register_in_adapter(3802, self._json_deserializer) - - fns.append(on_connect) - - if len(fns) > 0: - - def on_connect(conn): - for fn in fns: - fn(conn) - - return on_connect - else: - return None - - @util.memoized_property - def _dialect_specific_select_one(self): - return ";" - - -dialect = PGDialect_pg8000 diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py deleted file mode 100644 index 9b5562c..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py +++ /dev/null @@ -1,300 +0,0 @@ -# dialects/postgresql/pg_catalog.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from .array import ARRAY -from .types import OID -from .types import REGCLASS -from ... import Column -from ... import func -from ... import MetaData -from ... import Table -from ...types import BigInteger -from ...types import Boolean -from ...types import CHAR -from ...types import Float -from ...types import Integer -from ...types import SmallInteger -from ...types import String -from ...types import Text -from ...types import TypeDecorator - - -# types -class NAME(TypeDecorator): - impl = String(64, collation="C") - cache_ok = True - - -class PG_NODE_TREE(TypeDecorator): - impl = Text(collation="C") - cache_ok = True - - -class INT2VECTOR(TypeDecorator): - impl = ARRAY(SmallInteger) - cache_ok = True - - -class OIDVECTOR(TypeDecorator): - impl = ARRAY(OID) - cache_ok = True - - -class _SpaceVector: - def result_processor(self, dialect, coltype): - def process(value): - if value is None: - return value - return [int(p) for p in value.split(" ")] - - return process - - -REGPROC = REGCLASS # seems an alias - -# functions -_pg_cat = func.pg_catalog -quote_ident = _pg_cat.quote_ident -pg_table_is_visible = _pg_cat.pg_table_is_visible -pg_type_is_visible = _pg_cat.pg_type_is_visible -pg_get_viewdef = _pg_cat.pg_get_viewdef -pg_get_serial_sequence = _pg_cat.pg_get_serial_sequence -format_type = _pg_cat.format_type -pg_get_expr = _pg_cat.pg_get_expr -pg_get_constraintdef = _pg_cat.pg_get_constraintdef -pg_get_indexdef = _pg_cat.pg_get_indexdef - -# constants -RELKINDS_TABLE_NO_FOREIGN = ("r", "p") -RELKINDS_TABLE = RELKINDS_TABLE_NO_FOREIGN + ("f",) -RELKINDS_VIEW = ("v",) -RELKINDS_MAT_VIEW = ("m",) -RELKINDS_ALL_TABLE_LIKE = RELKINDS_TABLE + RELKINDS_VIEW + RELKINDS_MAT_VIEW - -# tables -pg_catalog_meta = MetaData(schema="pg_catalog") - -pg_namespace = Table( - "pg_namespace", - pg_catalog_meta, - Column("oid", OID), - Column("nspname", NAME), - Column("nspowner", OID), -) - -pg_class = Table( - "pg_class", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("relname", NAME), - Column("relnamespace", OID), - Column("reltype", OID), - Column("reloftype", OID), - Column("relowner", OID), - Column("relam", OID), - Column("relfilenode", OID), - Column("reltablespace", OID), - Column("relpages", Integer), - Column("reltuples", Float), - Column("relallvisible", Integer, info={"server_version": (9, 2)}), - Column("reltoastrelid", OID), - Column("relhasindex", Boolean), - Column("relisshared", Boolean), - Column("relpersistence", CHAR, info={"server_version": (9, 1)}), - Column("relkind", CHAR), - Column("relnatts", SmallInteger), - Column("relchecks", SmallInteger), - Column("relhasrules", Boolean), - Column("relhastriggers", Boolean), - Column("relhassubclass", Boolean), - Column("relrowsecurity", Boolean), - Column("relforcerowsecurity", Boolean, info={"server_version": (9, 5)}), - Column("relispopulated", Boolean, info={"server_version": (9, 3)}), - Column("relreplident", CHAR, info={"server_version": (9, 4)}), - Column("relispartition", Boolean, info={"server_version": (10,)}), - Column("relrewrite", OID, info={"server_version": (11,)}), - Column("reloptions", ARRAY(Text)), -) - -pg_type = Table( - "pg_type", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("typname", NAME), - Column("typnamespace", OID), - Column("typowner", OID), - Column("typlen", SmallInteger), - Column("typbyval", Boolean), - Column("typtype", CHAR), - Column("typcategory", CHAR), - Column("typispreferred", Boolean), - Column("typisdefined", Boolean), - Column("typdelim", CHAR), - Column("typrelid", OID), - Column("typelem", OID), - Column("typarray", OID), - Column("typinput", REGPROC), - Column("typoutput", REGPROC), - Column("typreceive", REGPROC), - Column("typsend", REGPROC), - Column("typmodin", REGPROC), - Column("typmodout", REGPROC), - Column("typanalyze", REGPROC), - Column("typalign", CHAR), - Column("typstorage", CHAR), - Column("typnotnull", Boolean), - Column("typbasetype", OID), - Column("typtypmod", Integer), - Column("typndims", Integer), - Column("typcollation", OID, info={"server_version": (9, 1)}), - Column("typdefault", Text), -) - -pg_index = Table( - "pg_index", - pg_catalog_meta, - Column("indexrelid", OID), - Column("indrelid", OID), - Column("indnatts", SmallInteger), - Column("indnkeyatts", SmallInteger, info={"server_version": (11,)}), - Column("indisunique", Boolean), - Column("indnullsnotdistinct", Boolean, info={"server_version": (15,)}), - Column("indisprimary", Boolean), - Column("indisexclusion", Boolean, info={"server_version": (9, 1)}), - Column("indimmediate", Boolean), - Column("indisclustered", Boolean), - Column("indisvalid", Boolean), - Column("indcheckxmin", Boolean), - Column("indisready", Boolean), - Column("indislive", Boolean, info={"server_version": (9, 3)}), # 9.3 - Column("indisreplident", Boolean), - Column("indkey", INT2VECTOR), - Column("indcollation", OIDVECTOR, info={"server_version": (9, 1)}), # 9.1 - Column("indclass", OIDVECTOR), - Column("indoption", INT2VECTOR), - Column("indexprs", PG_NODE_TREE), - Column("indpred", PG_NODE_TREE), -) - -pg_attribute = Table( - "pg_attribute", - pg_catalog_meta, - Column("attrelid", OID), - Column("attname", NAME), - Column("atttypid", OID), - Column("attstattarget", Integer), - Column("attlen", SmallInteger), - Column("attnum", SmallInteger), - Column("attndims", Integer), - Column("attcacheoff", Integer), - Column("atttypmod", Integer), - Column("attbyval", Boolean), - Column("attstorage", CHAR), - Column("attalign", CHAR), - Column("attnotnull", Boolean), - Column("atthasdef", Boolean), - Column("atthasmissing", Boolean, info={"server_version": (11,)}), - Column("attidentity", CHAR, info={"server_version": (10,)}), - Column("attgenerated", CHAR, info={"server_version": (12,)}), - Column("attisdropped", Boolean), - Column("attislocal", Boolean), - Column("attinhcount", Integer), - Column("attcollation", OID, info={"server_version": (9, 1)}), -) - -pg_constraint = Table( - "pg_constraint", - pg_catalog_meta, - Column("oid", OID), # 9.3 - Column("conname", NAME), - Column("connamespace", OID), - Column("contype", CHAR), - Column("condeferrable", Boolean), - Column("condeferred", Boolean), - Column("convalidated", Boolean, info={"server_version": (9, 1)}), - Column("conrelid", OID), - Column("contypid", OID), - Column("conindid", OID), - Column("conparentid", OID, info={"server_version": (11,)}), - Column("confrelid", OID), - Column("confupdtype", CHAR), - Column("confdeltype", CHAR), - Column("confmatchtype", CHAR), - Column("conislocal", Boolean), - Column("coninhcount", Integer), - Column("connoinherit", Boolean, info={"server_version": (9, 2)}), - Column("conkey", ARRAY(SmallInteger)), - Column("confkey", ARRAY(SmallInteger)), -) - -pg_sequence = Table( - "pg_sequence", - pg_catalog_meta, - Column("seqrelid", OID), - Column("seqtypid", OID), - Column("seqstart", BigInteger), - Column("seqincrement", BigInteger), - Column("seqmax", BigInteger), - Column("seqmin", BigInteger), - Column("seqcache", BigInteger), - Column("seqcycle", Boolean), - info={"server_version": (10,)}, -) - -pg_attrdef = Table( - "pg_attrdef", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("adrelid", OID), - Column("adnum", SmallInteger), - Column("adbin", PG_NODE_TREE), -) - -pg_description = Table( - "pg_description", - pg_catalog_meta, - Column("objoid", OID), - Column("classoid", OID), - Column("objsubid", Integer), - Column("description", Text(collation="C")), -) - -pg_enum = Table( - "pg_enum", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("enumtypid", OID), - Column("enumsortorder", Float(), info={"server_version": (9, 1)}), - Column("enumlabel", NAME), -) - -pg_am = Table( - "pg_am", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("amname", NAME), - Column("amhandler", REGPROC, info={"server_version": (9, 6)}), - Column("amtype", CHAR, info={"server_version": (9, 6)}), -) - -pg_collation = Table( - "pg_collation", - pg_catalog_meta, - Column("oid", OID, info={"server_version": (9, 3)}), - Column("collname", NAME), - Column("collnamespace", OID), - Column("collowner", OID), - Column("collprovider", CHAR, info={"server_version": (10,)}), - Column("collisdeterministic", Boolean, info={"server_version": (12,)}), - Column("collencoding", Integer), - Column("collcollate", Text), - Column("collctype", Text), - Column("colliculocale", Text), - Column("collicurules", Text, info={"server_version": (16,)}), - Column("collversion", Text, info={"server_version": (10,)}), -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/provision.py deleted file mode 100644 index a87bb93..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/provision.py +++ /dev/null @@ -1,175 +0,0 @@ -# dialects/postgresql/provision.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -import time - -from ... import exc -from ... import inspect -from ... import text -from ...testing import warn_test_suite -from ...testing.provision import create_db -from ...testing.provision import drop_all_schema_objects_post_tables -from ...testing.provision import drop_all_schema_objects_pre_tables -from ...testing.provision import drop_db -from ...testing.provision import log -from ...testing.provision import post_configure_engine -from ...testing.provision import prepare_for_drop_tables -from ...testing.provision import set_default_schema_on_connection -from ...testing.provision import temp_table_keyword_args -from ...testing.provision import upsert - - -@create_db.for_db("postgresql") -def _pg_create_db(cfg, eng, ident): - template_db = cfg.options.postgresql_templatedb - - with eng.execution_options(isolation_level="AUTOCOMMIT").begin() as conn: - if not template_db: - template_db = conn.exec_driver_sql( - "select current_database()" - ).scalar() - - attempt = 0 - while True: - try: - conn.exec_driver_sql( - "CREATE DATABASE %s TEMPLATE %s" % (ident, template_db) - ) - except exc.OperationalError as err: - attempt += 1 - if attempt >= 3: - raise - if "accessed by other users" in str(err): - log.info( - "Waiting to create %s, URI %r, " - "template DB %s is in use sleeping for .5", - ident, - eng.url, - template_db, - ) - time.sleep(0.5) - except: - raise - else: - break - - -@drop_db.for_db("postgresql") -def _pg_drop_db(cfg, eng, ident): - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - with conn.begin(): - conn.execute( - text( - "select pg_terminate_backend(pid) from pg_stat_activity " - "where usename=current_user and pid != pg_backend_pid() " - "and datname=:dname" - ), - dict(dname=ident), - ) - conn.exec_driver_sql("DROP DATABASE %s" % ident) - - -@temp_table_keyword_args.for_db("postgresql") -def _postgresql_temp_table_keyword_args(cfg, eng): - return {"prefixes": ["TEMPORARY"]} - - -@set_default_schema_on_connection.for_db("postgresql") -def _postgresql_set_default_schema_on_connection( - cfg, dbapi_connection, schema_name -): - existing_autocommit = dbapi_connection.autocommit - dbapi_connection.autocommit = True - cursor = dbapi_connection.cursor() - cursor.execute("SET SESSION search_path='%s'" % schema_name) - cursor.close() - dbapi_connection.autocommit = existing_autocommit - - -@drop_all_schema_objects_pre_tables.for_db("postgresql") -def drop_all_schema_objects_pre_tables(cfg, eng): - with eng.connect().execution_options(isolation_level="AUTOCOMMIT") as conn: - for xid in conn.exec_driver_sql( - "select gid from pg_prepared_xacts" - ).scalars(): - conn.execute("ROLLBACK PREPARED '%s'" % xid) - - -@drop_all_schema_objects_post_tables.for_db("postgresql") -def drop_all_schema_objects_post_tables(cfg, eng): - from sqlalchemy.dialects import postgresql - - inspector = inspect(eng) - with eng.begin() as conn: - for enum in inspector.get_enums("*"): - conn.execute( - postgresql.DropEnumType( - postgresql.ENUM(name=enum["name"], schema=enum["schema"]) - ) - ) - - -@prepare_for_drop_tables.for_db("postgresql") -def prepare_for_drop_tables(config, connection): - """Ensure there are no locks on the current username/database.""" - - result = connection.exec_driver_sql( - "select pid, state, wait_event_type, query " - # "select pg_terminate_backend(pid), state, wait_event_type " - "from pg_stat_activity where " - "usename=current_user " - "and datname=current_database() and state='idle in transaction' " - "and pid != pg_backend_pid()" - ) - rows = result.all() # noqa - if rows: - warn_test_suite( - "PostgreSQL may not be able to DROP tables due to " - "idle in transaction: %s" - % ("; ".join(row._mapping["query"] for row in rows)) - ) - - -@upsert.for_db("postgresql") -def _upsert( - cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False -): - from sqlalchemy.dialects.postgresql import insert - - stmt = insert(table) - - table_pk = inspect(table).selectable - - if set_lambda: - stmt = stmt.on_conflict_do_update( - index_elements=table_pk.primary_key, set_=set_lambda(stmt.excluded) - ) - else: - stmt = stmt.on_conflict_do_nothing() - - stmt = stmt.returning( - *returning, sort_by_parameter_order=sort_by_parameter_order - ) - return stmt - - -_extensions = [ - ("citext", (13,)), - ("hstore", (13,)), -] - - -@post_configure_engine.for_db("postgresql") -def _create_citext_extension(url, engine, follower_ident): - with engine.connect() as conn: - for extension, min_version in _extensions: - if conn.dialect.server_version_info >= min_version: - conn.execute( - text(f"CREATE EXTENSION IF NOT EXISTS {extension}") - ) - conn.commit() diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg.py deleted file mode 100644 index 90177a4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg.py +++ /dev/null @@ -1,749 +0,0 @@ -# dialects/postgresql/psycopg.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql+psycopg - :name: psycopg (a.k.a. psycopg 3) - :dbapi: psycopg - :connectstring: postgresql+psycopg://user:password@host:port/dbname[?key=value&key=value...] - :url: https://pypi.org/project/psycopg/ - -``psycopg`` is the package and module name for version 3 of the ``psycopg`` -database driver, formerly known as ``psycopg2``. This driver is different -enough from its ``psycopg2`` predecessor that SQLAlchemy supports it -via a totally separate dialect; support for ``psycopg2`` is expected to remain -for as long as that package continues to function for modern Python versions, -and also remains the default dialect for the ``postgresql://`` dialect -series. - -The SQLAlchemy ``psycopg`` dialect provides both a sync and an async -implementation under the same dialect name. The proper version is -selected depending on how the engine is created: - -* calling :func:`_sa.create_engine` with ``postgresql+psycopg://...`` will - automatically select the sync version, e.g.:: - - from sqlalchemy import create_engine - sync_engine = create_engine("postgresql+psycopg://scott:tiger@localhost/test") - -* calling :func:`_asyncio.create_async_engine` with - ``postgresql+psycopg://...`` will automatically select the async version, - e.g.:: - - from sqlalchemy.ext.asyncio import create_async_engine - asyncio_engine = create_async_engine("postgresql+psycopg://scott:tiger@localhost/test") - -The asyncio version of the dialect may also be specified explicitly using the -``psycopg_async`` suffix, as:: - - from sqlalchemy.ext.asyncio import create_async_engine - asyncio_engine = create_async_engine("postgresql+psycopg_async://scott:tiger@localhost/test") - -.. seealso:: - - :ref:`postgresql_psycopg2` - The SQLAlchemy ``psycopg`` - dialect shares most of its behavior with the ``psycopg2`` dialect. - Further documentation is available there. - -""" # noqa -from __future__ import annotations - -import logging -import re -from typing import cast -from typing import TYPE_CHECKING - -from . import ranges -from ._psycopg_common import _PGDialect_common_psycopg -from ._psycopg_common import _PGExecutionContext_common_psycopg -from .base import INTERVAL -from .base import PGCompiler -from .base import PGIdentifierPreparer -from .base import REGCONFIG -from .json import JSON -from .json import JSONB -from .json import JSONPathType -from .types import CITEXT -from ... import pool -from ... import util -from ...engine import AdaptedConnection -from ...sql import sqltypes -from ...util.concurrency import await_fallback -from ...util.concurrency import await_only - -if TYPE_CHECKING: - from typing import Iterable - - from psycopg import AsyncConnection - -logger = logging.getLogger("sqlalchemy.dialects.postgresql") - - -class _PGString(sqltypes.String): - render_bind_cast = True - - -class _PGREGCONFIG(REGCONFIG): - render_bind_cast = True - - -class _PGJSON(JSON): - render_bind_cast = True - - def bind_processor(self, dialect): - return self._make_bind_processor(None, dialect._psycopg_Json) - - def result_processor(self, dialect, coltype): - return None - - -class _PGJSONB(JSONB): - render_bind_cast = True - - def bind_processor(self, dialect): - return self._make_bind_processor(None, dialect._psycopg_Jsonb) - - def result_processor(self, dialect, coltype): - return None - - -class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType): - __visit_name__ = "json_int_index" - - render_bind_cast = True - - -class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType): - __visit_name__ = "json_str_index" - - render_bind_cast = True - - -class _PGJSONPathType(JSONPathType): - pass - - -class _PGInterval(INTERVAL): - render_bind_cast = True - - -class _PGTimeStamp(sqltypes.DateTime): - render_bind_cast = True - - -class _PGDate(sqltypes.Date): - render_bind_cast = True - - -class _PGTime(sqltypes.Time): - render_bind_cast = True - - -class _PGInteger(sqltypes.Integer): - render_bind_cast = True - - -class _PGSmallInteger(sqltypes.SmallInteger): - render_bind_cast = True - - -class _PGNullType(sqltypes.NullType): - render_bind_cast = True - - -class _PGBigInteger(sqltypes.BigInteger): - render_bind_cast = True - - -class _PGBoolean(sqltypes.Boolean): - render_bind_cast = True - - -class _PsycopgRange(ranges.AbstractSingleRangeImpl): - def bind_processor(self, dialect): - psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range - - def to_range(value): - if isinstance(value, ranges.Range): - value = psycopg_Range( - value.lower, value.upper, value.bounds, value.empty - ) - return value - - return to_range - - def result_processor(self, dialect, coltype): - def to_range(value): - if value is not None: - value = ranges.Range( - value._lower, - value._upper, - bounds=value._bounds if value._bounds else "[)", - empty=not value._bounds, - ) - return value - - return to_range - - -class _PsycopgMultiRange(ranges.AbstractMultiRangeImpl): - def bind_processor(self, dialect): - psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range - psycopg_Multirange = cast( - PGDialect_psycopg, dialect - )._psycopg_Multirange - - NoneType = type(None) - - def to_range(value): - if isinstance(value, (str, NoneType, psycopg_Multirange)): - return value - - return psycopg_Multirange( - [ - psycopg_Range( - element.lower, - element.upper, - element.bounds, - element.empty, - ) - for element in cast("Iterable[ranges.Range]", value) - ] - ) - - return to_range - - def result_processor(self, dialect, coltype): - def to_range(value): - if value is None: - return None - else: - return ranges.MultiRange( - ranges.Range( - elem._lower, - elem._upper, - bounds=elem._bounds if elem._bounds else "[)", - empty=not elem._bounds, - ) - for elem in value - ) - - return to_range - - -class PGExecutionContext_psycopg(_PGExecutionContext_common_psycopg): - pass - - -class PGCompiler_psycopg(PGCompiler): - pass - - -class PGIdentifierPreparer_psycopg(PGIdentifierPreparer): - pass - - -def _log_notices(diagnostic): - logger.info("%s: %s", diagnostic.severity, diagnostic.message_primary) - - -class PGDialect_psycopg(_PGDialect_common_psycopg): - driver = "psycopg" - - supports_statement_cache = True - supports_server_side_cursors = True - default_paramstyle = "pyformat" - supports_sane_multi_rowcount = True - - execution_ctx_cls = PGExecutionContext_psycopg - statement_compiler = PGCompiler_psycopg - preparer = PGIdentifierPreparer_psycopg - psycopg_version = (0, 0) - - _has_native_hstore = True - _psycopg_adapters_map = None - - colspecs = util.update_copy( - _PGDialect_common_psycopg.colspecs, - { - sqltypes.String: _PGString, - REGCONFIG: _PGREGCONFIG, - JSON: _PGJSON, - CITEXT: CITEXT, - sqltypes.JSON: _PGJSON, - JSONB: _PGJSONB, - sqltypes.JSON.JSONPathType: _PGJSONPathType, - sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType, - sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType, - sqltypes.Interval: _PGInterval, - INTERVAL: _PGInterval, - sqltypes.Date: _PGDate, - sqltypes.DateTime: _PGTimeStamp, - sqltypes.Time: _PGTime, - sqltypes.Integer: _PGInteger, - sqltypes.SmallInteger: _PGSmallInteger, - sqltypes.BigInteger: _PGBigInteger, - ranges.AbstractSingleRange: _PsycopgRange, - ranges.AbstractMultiRange: _PsycopgMultiRange, - }, - ) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - if self.dbapi: - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__) - if m: - self.psycopg_version = tuple( - int(x) for x in m.group(1, 2, 3) if x is not None - ) - - if self.psycopg_version < (3, 0, 2): - raise ImportError( - "psycopg version 3.0.2 or higher is required." - ) - - from psycopg.adapt import AdaptersMap - - self._psycopg_adapters_map = adapters_map = AdaptersMap( - self.dbapi.adapters - ) - - if self._native_inet_types is False: - import psycopg.types.string - - adapters_map.register_loader( - "inet", psycopg.types.string.TextLoader - ) - adapters_map.register_loader( - "cidr", psycopg.types.string.TextLoader - ) - - if self._json_deserializer: - from psycopg.types.json import set_json_loads - - set_json_loads(self._json_deserializer, adapters_map) - - if self._json_serializer: - from psycopg.types.json import set_json_dumps - - set_json_dumps(self._json_serializer, adapters_map) - - def create_connect_args(self, url): - # see https://github.com/psycopg/psycopg/issues/83 - cargs, cparams = super().create_connect_args(url) - - if self._psycopg_adapters_map: - cparams["context"] = self._psycopg_adapters_map - if self.client_encoding is not None: - cparams["client_encoding"] = self.client_encoding - return cargs, cparams - - def _type_info_fetch(self, connection, name): - from psycopg.types import TypeInfo - - return TypeInfo.fetch(connection.connection.driver_connection, name) - - def initialize(self, connection): - super().initialize(connection) - - # PGDialect.initialize() checks server version for <= 8.2 and sets - # this flag to False if so - if not self.insert_returning: - self.insert_executemany_returning = False - - # HSTORE can't be registered until we have a connection so that - # we can look up its OID, so we set up this adapter in - # initialize() - if self.use_native_hstore: - info = self._type_info_fetch(connection, "hstore") - self._has_native_hstore = info is not None - if self._has_native_hstore: - from psycopg.types.hstore import register_hstore - - # register the adapter for connections made subsequent to - # this one - register_hstore(info, self._psycopg_adapters_map) - - # register the adapter for this connection - register_hstore(info, connection.connection) - - @classmethod - def import_dbapi(cls): - import psycopg - - return psycopg - - @classmethod - def get_async_dialect_cls(cls, url): - return PGDialectAsync_psycopg - - @util.memoized_property - def _isolation_lookup(self): - return { - "READ COMMITTED": self.dbapi.IsolationLevel.READ_COMMITTED, - "READ UNCOMMITTED": self.dbapi.IsolationLevel.READ_UNCOMMITTED, - "REPEATABLE READ": self.dbapi.IsolationLevel.REPEATABLE_READ, - "SERIALIZABLE": self.dbapi.IsolationLevel.SERIALIZABLE, - } - - @util.memoized_property - def _psycopg_Json(self): - from psycopg.types import json - - return json.Json - - @util.memoized_property - def _psycopg_Jsonb(self): - from psycopg.types import json - - return json.Jsonb - - @util.memoized_property - def _psycopg_TransactionStatus(self): - from psycopg.pq import TransactionStatus - - return TransactionStatus - - @util.memoized_property - def _psycopg_Range(self): - from psycopg.types.range import Range - - return Range - - @util.memoized_property - def _psycopg_Multirange(self): - from psycopg.types.multirange import Multirange - - return Multirange - - def _do_isolation_level(self, connection, autocommit, isolation_level): - connection.autocommit = autocommit - connection.isolation_level = isolation_level - - def get_isolation_level(self, dbapi_connection): - status_before = dbapi_connection.info.transaction_status - value = super().get_isolation_level(dbapi_connection) - - # don't rely on psycopg providing enum symbols, compare with - # eq/ne - if status_before == self._psycopg_TransactionStatus.IDLE: - dbapi_connection.rollback() - return value - - def set_isolation_level(self, dbapi_connection, level): - if level == "AUTOCOMMIT": - self._do_isolation_level( - dbapi_connection, autocommit=True, isolation_level=None - ) - else: - self._do_isolation_level( - dbapi_connection, - autocommit=False, - isolation_level=self._isolation_lookup[level], - ) - - def set_readonly(self, connection, value): - connection.read_only = value - - def get_readonly(self, connection): - return connection.read_only - - def on_connect(self): - def notices(conn): - conn.add_notice_handler(_log_notices) - - fns = [notices] - - if self.isolation_level is not None: - - def on_connect(conn): - self.set_isolation_level(conn, self.isolation_level) - - fns.append(on_connect) - - # fns always has the notices function - def on_connect(conn): - for fn in fns: - fn(conn) - - return on_connect - - def is_disconnect(self, e, connection, cursor): - if isinstance(e, self.dbapi.Error) and connection is not None: - if connection.closed or connection.broken: - return True - return False - - def _do_prepared_twophase(self, connection, command, recover=False): - dbapi_conn = connection.connection.dbapi_connection - if ( - recover - # don't rely on psycopg providing enum symbols, compare with - # eq/ne - or dbapi_conn.info.transaction_status - != self._psycopg_TransactionStatus.IDLE - ): - dbapi_conn.rollback() - before_autocommit = dbapi_conn.autocommit - try: - if not before_autocommit: - self._do_autocommit(dbapi_conn, True) - dbapi_conn.execute(command) - finally: - if not before_autocommit: - self._do_autocommit(dbapi_conn, before_autocommit) - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if is_prepared: - self._do_prepared_twophase( - connection, f"ROLLBACK PREPARED '{xid}'", recover=recover - ) - else: - self.do_rollback(connection.connection) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - if is_prepared: - self._do_prepared_twophase( - connection, f"COMMIT PREPARED '{xid}'", recover=recover - ) - else: - self.do_commit(connection.connection) - - @util.memoized_property - def _dialect_specific_select_one(self): - return ";" - - -class AsyncAdapt_psycopg_cursor: - __slots__ = ("_cursor", "await_", "_rows") - - _psycopg_ExecStatus = None - - def __init__(self, cursor, await_) -> None: - self._cursor = cursor - self.await_ = await_ - self._rows = [] - - def __getattr__(self, name): - return getattr(self._cursor, name) - - @property - def arraysize(self): - return self._cursor.arraysize - - @arraysize.setter - def arraysize(self, value): - self._cursor.arraysize = value - - def close(self): - self._rows.clear() - # Normal cursor just call _close() in a non-sync way. - self._cursor._close() - - def execute(self, query, params=None, **kw): - result = self.await_(self._cursor.execute(query, params, **kw)) - # sqlalchemy result is not async, so need to pull all rows here - res = self._cursor.pgresult - - # don't rely on psycopg providing enum symbols, compare with - # eq/ne - if res and res.status == self._psycopg_ExecStatus.TUPLES_OK: - rows = self.await_(self._cursor.fetchall()) - if not isinstance(rows, list): - self._rows = list(rows) - else: - self._rows = rows - return result - - def executemany(self, query, params_seq): - return self.await_(self._cursor.executemany(query, params_seq)) - - def __iter__(self): - # TODO: try to avoid pop(0) on a list - while self._rows: - yield self._rows.pop(0) - - def fetchone(self): - if self._rows: - # TODO: try to avoid pop(0) on a list - return self._rows.pop(0) - else: - return None - - def fetchmany(self, size=None): - if size is None: - size = self._cursor.arraysize - - retval = self._rows[0:size] - self._rows = self._rows[size:] - return retval - - def fetchall(self): - retval = self._rows - self._rows = [] - return retval - - -class AsyncAdapt_psycopg_ss_cursor(AsyncAdapt_psycopg_cursor): - def execute(self, query, params=None, **kw): - self.await_(self._cursor.execute(query, params, **kw)) - return self - - def close(self): - self.await_(self._cursor.close()) - - def fetchone(self): - return self.await_(self._cursor.fetchone()) - - def fetchmany(self, size=0): - return self.await_(self._cursor.fetchmany(size)) - - def fetchall(self): - return self.await_(self._cursor.fetchall()) - - def __iter__(self): - iterator = self._cursor.__aiter__() - while True: - try: - yield self.await_(iterator.__anext__()) - except StopAsyncIteration: - break - - -class AsyncAdapt_psycopg_connection(AdaptedConnection): - _connection: AsyncConnection - __slots__ = () - await_ = staticmethod(await_only) - - def __init__(self, connection) -> None: - self._connection = connection - - def __getattr__(self, name): - return getattr(self._connection, name) - - def execute(self, query, params=None, **kw): - cursor = self.await_(self._connection.execute(query, params, **kw)) - return AsyncAdapt_psycopg_cursor(cursor, self.await_) - - def cursor(self, *args, **kw): - cursor = self._connection.cursor(*args, **kw) - if hasattr(cursor, "name"): - return AsyncAdapt_psycopg_ss_cursor(cursor, self.await_) - else: - return AsyncAdapt_psycopg_cursor(cursor, self.await_) - - def commit(self): - self.await_(self._connection.commit()) - - def rollback(self): - self.await_(self._connection.rollback()) - - def close(self): - self.await_(self._connection.close()) - - @property - def autocommit(self): - return self._connection.autocommit - - @autocommit.setter - def autocommit(self, value): - self.set_autocommit(value) - - def set_autocommit(self, value): - self.await_(self._connection.set_autocommit(value)) - - def set_isolation_level(self, value): - self.await_(self._connection.set_isolation_level(value)) - - def set_read_only(self, value): - self.await_(self._connection.set_read_only(value)) - - def set_deferrable(self, value): - self.await_(self._connection.set_deferrable(value)) - - -class AsyncAdaptFallback_psycopg_connection(AsyncAdapt_psycopg_connection): - __slots__ = () - await_ = staticmethod(await_fallback) - - -class PsycopgAdaptDBAPI: - def __init__(self, psycopg) -> None: - self.psycopg = psycopg - - for k, v in self.psycopg.__dict__.items(): - if k != "connect": - self.__dict__[k] = v - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - creator_fn = kw.pop( - "async_creator_fn", self.psycopg.AsyncConnection.connect - ) - if util.asbool(async_fallback): - return AsyncAdaptFallback_psycopg_connection( - await_fallback(creator_fn(*arg, **kw)) - ) - else: - return AsyncAdapt_psycopg_connection( - await_only(creator_fn(*arg, **kw)) - ) - - -class PGDialectAsync_psycopg(PGDialect_psycopg): - is_async = True - supports_statement_cache = True - - @classmethod - def import_dbapi(cls): - import psycopg - from psycopg.pq import ExecStatus - - AsyncAdapt_psycopg_cursor._psycopg_ExecStatus = ExecStatus - - return PsycopgAdaptDBAPI(psycopg) - - @classmethod - def get_pool_class(cls, url): - async_fallback = url.query.get("async_fallback", False) - - if util.asbool(async_fallback): - return pool.FallbackAsyncAdaptedQueuePool - else: - return pool.AsyncAdaptedQueuePool - - def _type_info_fetch(self, connection, name): - from psycopg.types import TypeInfo - - adapted = connection.connection - return adapted.await_(TypeInfo.fetch(adapted.driver_connection, name)) - - def _do_isolation_level(self, connection, autocommit, isolation_level): - connection.set_autocommit(autocommit) - connection.set_isolation_level(isolation_level) - - def _do_autocommit(self, connection, value): - connection.set_autocommit(value) - - def set_readonly(self, connection, value): - connection.set_read_only(value) - - def set_deferrable(self, connection, value): - connection.set_deferrable(value) - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = PGDialect_psycopg -dialect_async = PGDialectAsync_psycopg diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py deleted file mode 100644 index 9bf2e49..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py +++ /dev/null @@ -1,876 +0,0 @@ -# dialects/postgresql/psycopg2.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql+psycopg2 - :name: psycopg2 - :dbapi: psycopg2 - :connectstring: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...] - :url: https://pypi.org/project/psycopg2/ - -.. _psycopg2_toplevel: - -psycopg2 Connect Arguments --------------------------- - -Keyword arguments that are specific to the SQLAlchemy psycopg2 dialect -may be passed to :func:`_sa.create_engine()`, and include the following: - - -* ``isolation_level``: This option, available for all PostgreSQL dialects, - includes the ``AUTOCOMMIT`` isolation level when using the psycopg2 - dialect. This option sets the **default** isolation level for the - connection that is set immediately upon connection to the database before - the connection is pooled. This option is generally superseded by the more - modern :paramref:`_engine.Connection.execution_options.isolation_level` - execution option, detailed at :ref:`dbapi_autocommit`. - - .. seealso:: - - :ref:`psycopg2_isolation_level` - - :ref:`dbapi_autocommit` - - -* ``client_encoding``: sets the client encoding in a libpq-agnostic way, - using psycopg2's ``set_client_encoding()`` method. - - .. seealso:: - - :ref:`psycopg2_unicode` - - -* ``executemany_mode``, ``executemany_batch_page_size``, - ``executemany_values_page_size``: Allows use of psycopg2 - extensions for optimizing "executemany"-style queries. See the referenced - section below for details. - - .. seealso:: - - :ref:`psycopg2_executemany_mode` - -.. tip:: - - The above keyword arguments are **dialect** keyword arguments, meaning - that they are passed as explicit keyword arguments to :func:`_sa.create_engine()`:: - - engine = create_engine( - "postgresql+psycopg2://scott:tiger@localhost/test", - isolation_level="SERIALIZABLE", - ) - - These should not be confused with **DBAPI** connect arguments, which - are passed as part of the :paramref:`_sa.create_engine.connect_args` - dictionary and/or are passed in the URL query string, as detailed in - the section :ref:`custom_dbapi_args`. - -.. _psycopg2_ssl: - -SSL Connections ---------------- - -The psycopg2 module has a connection argument named ``sslmode`` for -controlling its behavior regarding secure (SSL) connections. The default is -``sslmode=prefer``; it will attempt an SSL connection and if that fails it -will fall back to an unencrypted connection. ``sslmode=require`` may be used -to ensure that only secure connections are established. Consult the -psycopg2 / libpq documentation for further options that are available. - -Note that ``sslmode`` is specific to psycopg2 so it is included in the -connection URI:: - - engine = sa.create_engine( - "postgresql+psycopg2://scott:tiger@192.168.0.199:5432/test?sslmode=require" - ) - - -Unix Domain Connections ------------------------- - -psycopg2 supports connecting via Unix domain connections. When the ``host`` -portion of the URL is omitted, SQLAlchemy passes ``None`` to psycopg2, -which specifies Unix-domain communication rather than TCP/IP communication:: - - create_engine("postgresql+psycopg2://user:password@/dbname") - -By default, the socket file used is to connect to a Unix-domain socket -in ``/tmp``, or whatever socket directory was specified when PostgreSQL -was built. This value can be overridden by passing a pathname to psycopg2, -using ``host`` as an additional keyword argument:: - - create_engine("postgresql+psycopg2://user:password@/dbname?host=/var/lib/postgresql") - -.. warning:: The format accepted here allows for a hostname in the main URL - in addition to the "host" query string argument. **When using this URL - format, the initial host is silently ignored**. That is, this URL:: - - engine = create_engine("postgresql+psycopg2://user:password@myhost1/dbname?host=myhost2") - - Above, the hostname ``myhost1`` is **silently ignored and discarded.** The - host which is connected is the ``myhost2`` host. - - This is to maintain some degree of compatibility with PostgreSQL's own URL - format which has been tested to behave the same way and for which tools like - PifPaf hardcode two hostnames. - -.. seealso:: - - `PQconnectdbParams \ - `_ - -.. _psycopg2_multi_host: - -Specifying multiple fallback hosts ------------------------------------ - -psycopg2 supports multiple connection points in the connection string. -When the ``host`` parameter is used multiple times in the query section of -the URL, SQLAlchemy will create a single string of the host and port -information provided to make the connections. Tokens may consist of -``host::port`` or just ``host``; in the latter case, the default port -is selected by libpq. In the example below, three host connections -are specified, for ``HostA::PortA``, ``HostB`` connecting to the default port, -and ``HostC::PortC``:: - - create_engine( - "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC" - ) - -As an alternative, libpq query string format also may be used; this specifies -``host`` and ``port`` as single query string arguments with comma-separated -lists - the default port can be chosen by indicating an empty value -in the comma separated list:: - - create_engine( - "postgresql+psycopg2://user:password@/dbname?host=HostA,HostB,HostC&port=PortA,,PortC" - ) - -With either URL style, connections to each host is attempted based on a -configurable strategy, which may be configured using the libpq -``target_session_attrs`` parameter. Per libpq this defaults to ``any`` -which indicates a connection to each host is then attempted until a connection is successful. -Other strategies include ``primary``, ``prefer-standby``, etc. The complete -list is documented by PostgreSQL at -`libpq connection strings `_. - -For example, to indicate two hosts using the ``primary`` strategy:: - - create_engine( - "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC&target_session_attrs=primary" - ) - -.. versionchanged:: 1.4.40 Port specification in psycopg2 multiple host format - is repaired, previously ports were not correctly interpreted in this context. - libpq comma-separated format is also now supported. - -.. versionadded:: 1.3.20 Support for multiple hosts in PostgreSQL connection - string. - -.. seealso:: - - `libpq connection strings `_ - please refer - to this section in the libpq documentation for complete background on multiple host support. - - -Empty DSN Connections / Environment Variable Connections ---------------------------------------------------------- - -The psycopg2 DBAPI can connect to PostgreSQL by passing an empty DSN to the -libpq client library, which by default indicates to connect to a localhost -PostgreSQL database that is open for "trust" connections. This behavior can be -further tailored using a particular set of environment variables which are -prefixed with ``PG_...``, which are consumed by ``libpq`` to take the place of -any or all elements of the connection string. - -For this form, the URL can be passed without any elements other than the -initial scheme:: - - engine = create_engine('postgresql+psycopg2://') - -In the above form, a blank "dsn" string is passed to the ``psycopg2.connect()`` -function which in turn represents an empty DSN passed to libpq. - -.. versionadded:: 1.3.2 support for parameter-less connections with psycopg2. - -.. seealso:: - - `Environment Variables\ - `_ - - PostgreSQL documentation on how to use ``PG_...`` - environment variables for connections. - -.. _psycopg2_execution_options: - -Per-Statement/Connection Execution Options -------------------------------------------- - -The following DBAPI-specific options are respected when used with -:meth:`_engine.Connection.execution_options`, -:meth:`.Executable.execution_options`, -:meth:`_query.Query.execution_options`, -in addition to those not specific to DBAPIs: - -* ``isolation_level`` - Set the transaction isolation level for the lifespan - of a :class:`_engine.Connection` (can only be set on a connection, - not a statement - or query). See :ref:`psycopg2_isolation_level`. - -* ``stream_results`` - Enable or disable usage of psycopg2 server side - cursors - this feature makes use of "named" cursors in combination with - special result handling methods so that result rows are not fully buffered. - Defaults to False, meaning cursors are buffered by default. - -* ``max_row_buffer`` - when using ``stream_results``, an integer value that - specifies the maximum number of rows to buffer at a time. This is - interpreted by the :class:`.BufferedRowCursorResult`, and if omitted the - buffer will grow to ultimately store 1000 rows at a time. - - .. versionchanged:: 1.4 The ``max_row_buffer`` size can now be greater than - 1000, and the buffer will grow to that size. - -.. _psycopg2_batch_mode: - -.. _psycopg2_executemany_mode: - -Psycopg2 Fast Execution Helpers -------------------------------- - -Modern versions of psycopg2 include a feature known as -`Fast Execution Helpers \ -`_, which -have been shown in benchmarking to improve psycopg2's executemany() -performance, primarily with INSERT statements, by at least -an order of magnitude. - -SQLAlchemy implements a native form of the "insert many values" -handler that will rewrite a single-row INSERT statement to accommodate for -many values at once within an extended VALUES clause; this handler is -equivalent to psycopg2's ``execute_values()`` handler; an overview of this -feature and its configuration are at :ref:`engine_insertmanyvalues`. - -.. versionadded:: 2.0 Replaced psycopg2's ``execute_values()`` fast execution - helper with a native SQLAlchemy mechanism known as - :ref:`insertmanyvalues `. - -The psycopg2 dialect retains the ability to use the psycopg2-specific -``execute_batch()`` feature, although it is not expected that this is a widely -used feature. The use of this extension may be enabled using the -``executemany_mode`` flag which may be passed to :func:`_sa.create_engine`:: - - engine = create_engine( - "postgresql+psycopg2://scott:tiger@host/dbname", - executemany_mode='values_plus_batch') - - -Possible options for ``executemany_mode`` include: - -* ``values_only`` - this is the default value. SQLAlchemy's native - :ref:`insertmanyvalues ` handler is used for qualifying - INSERT statements, assuming - :paramref:`_sa.create_engine.use_insertmanyvalues` is left at - its default value of ``True``. This handler rewrites simple - INSERT statements to include multiple VALUES clauses so that many - parameter sets can be inserted with one statement. - -* ``'values_plus_batch'``- SQLAlchemy's native - :ref:`insertmanyvalues ` handler is used for qualifying - INSERT statements, assuming - :paramref:`_sa.create_engine.use_insertmanyvalues` is left at its default - value of ``True``. Then, psycopg2's ``execute_batch()`` handler is used for - qualifying UPDATE and DELETE statements when executed with multiple parameter - sets. When using this mode, the :attr:`_engine.CursorResult.rowcount` - attribute will not contain a value for executemany-style executions against - UPDATE and DELETE statements. - -.. versionchanged:: 2.0 Removed the ``'batch'`` and ``'None'`` options - from psycopg2 ``executemany_mode``. Control over batching for INSERT - statements is now configured via the - :paramref:`_sa.create_engine.use_insertmanyvalues` engine-level parameter. - -The term "qualifying statements" refers to the statement being executed -being a Core :func:`_expression.insert`, :func:`_expression.update` -or :func:`_expression.delete` construct, and **not** a plain textual SQL -string or one constructed using :func:`_expression.text`. It also may **not** be -a special "extension" statement such as an "ON CONFLICT" "upsert" statement. -When using the ORM, all insert/update/delete statements used by the ORM flush process -are qualifying. - -The "page size" for the psycopg2 "batch" strategy can be affected -by using the ``executemany_batch_page_size`` parameter, which defaults to -100. - -For the "insertmanyvalues" feature, the page size can be controlled using the -:paramref:`_sa.create_engine.insertmanyvalues_page_size` parameter, -which defaults to 1000. An example of modifying both parameters -is below:: - - engine = create_engine( - "postgresql+psycopg2://scott:tiger@host/dbname", - executemany_mode='values_plus_batch', - insertmanyvalues_page_size=5000, executemany_batch_page_size=500) - -.. seealso:: - - :ref:`engine_insertmanyvalues` - background on "insertmanyvalues" - - :ref:`tutorial_multiple_parameters` - General information on using the - :class:`_engine.Connection` - object to execute statements in such a way as to make - use of the DBAPI ``.executemany()`` method. - - -.. _psycopg2_unicode: - -Unicode with Psycopg2 ----------------------- - -The psycopg2 DBAPI driver supports Unicode data transparently. - -The client character encoding can be controlled for the psycopg2 dialect -in the following ways: - -* For PostgreSQL 9.1 and above, the ``client_encoding`` parameter may be - passed in the database URL; this parameter is consumed by the underlying - ``libpq`` PostgreSQL client library:: - - engine = create_engine("postgresql+psycopg2://user:pass@host/dbname?client_encoding=utf8") - - Alternatively, the above ``client_encoding`` value may be passed using - :paramref:`_sa.create_engine.connect_args` for programmatic establishment with - ``libpq``:: - - engine = create_engine( - "postgresql+psycopg2://user:pass@host/dbname", - connect_args={'client_encoding': 'utf8'} - ) - -* For all PostgreSQL versions, psycopg2 supports a client-side encoding - value that will be passed to database connections when they are first - established. The SQLAlchemy psycopg2 dialect supports this using the - ``client_encoding`` parameter passed to :func:`_sa.create_engine`:: - - engine = create_engine( - "postgresql+psycopg2://user:pass@host/dbname", - client_encoding="utf8" - ) - - .. tip:: The above ``client_encoding`` parameter admittedly is very similar - in appearance to usage of the parameter within the - :paramref:`_sa.create_engine.connect_args` dictionary; the difference - above is that the parameter is consumed by psycopg2 and is - passed to the database connection using ``SET client_encoding TO - 'utf8'``; in the previously mentioned style, the parameter is instead - passed through psycopg2 and consumed by the ``libpq`` library. - -* A common way to set up client encoding with PostgreSQL databases is to - ensure it is configured within the server-side postgresql.conf file; - this is the recommended way to set encoding for a server that is - consistently of one encoding in all databases:: - - # postgresql.conf file - - # client_encoding = sql_ascii # actually, defaults to database - # encoding - client_encoding = utf8 - - - -Transactions ------------- - -The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations. - -.. _psycopg2_isolation_level: - -Psycopg2 Transaction Isolation Level -------------------------------------- - -As discussed in :ref:`postgresql_isolation_level`, -all PostgreSQL dialects support setting of transaction isolation level -both via the ``isolation_level`` parameter passed to :func:`_sa.create_engine` -, -as well as the ``isolation_level`` argument used by -:meth:`_engine.Connection.execution_options`. When using the psycopg2 dialect -, these -options make use of psycopg2's ``set_isolation_level()`` connection method, -rather than emitting a PostgreSQL directive; this is because psycopg2's -API-level setting is always emitted at the start of each transaction in any -case. - -The psycopg2 dialect supports these constants for isolation level: - -* ``READ COMMITTED`` -* ``READ UNCOMMITTED`` -* ``REPEATABLE READ`` -* ``SERIALIZABLE`` -* ``AUTOCOMMIT`` - -.. seealso:: - - :ref:`postgresql_isolation_level` - - :ref:`pg8000_isolation_level` - - -NOTICE logging ---------------- - -The psycopg2 dialect will log PostgreSQL NOTICE messages -via the ``sqlalchemy.dialects.postgresql`` logger. When this logger -is set to the ``logging.INFO`` level, notice messages will be logged:: - - import logging - - logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO) - -Above, it is assumed that logging is configured externally. If this is not -the case, configuration such as ``logging.basicConfig()`` must be utilized:: - - import logging - - logging.basicConfig() # log messages to stdout - logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO) - -.. seealso:: - - `Logging HOWTO `_ - on the python.org website - -.. _psycopg2_hstore: - -HSTORE type ------------- - -The ``psycopg2`` DBAPI includes an extension to natively handle marshalling of -the HSTORE type. The SQLAlchemy psycopg2 dialect will enable this extension -by default when psycopg2 version 2.4 or greater is used, and -it is detected that the target database has the HSTORE type set up for use. -In other words, when the dialect makes the first -connection, a sequence like the following is performed: - -1. Request the available HSTORE oids using - ``psycopg2.extras.HstoreAdapter.get_oids()``. - If this function returns a list of HSTORE identifiers, we then determine - that the ``HSTORE`` extension is present. - This function is **skipped** if the version of psycopg2 installed is - less than version 2.4. - -2. If the ``use_native_hstore`` flag is at its default of ``True``, and - we've detected that ``HSTORE`` oids are available, the - ``psycopg2.extensions.register_hstore()`` extension is invoked for all - connections. - -The ``register_hstore()`` extension has the effect of **all Python -dictionaries being accepted as parameters regardless of the type of target -column in SQL**. The dictionaries are converted by this extension into a -textual HSTORE expression. If this behavior is not desired, disable the -use of the hstore extension by setting ``use_native_hstore`` to ``False`` as -follows:: - - engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/test", - use_native_hstore=False) - -The ``HSTORE`` type is **still supported** when the -``psycopg2.extensions.register_hstore()`` extension is not used. It merely -means that the coercion between Python dictionaries and the HSTORE -string format, on both the parameter side and the result side, will take -place within SQLAlchemy's own marshalling logic, and not that of ``psycopg2`` -which may be more performant. - -""" # noqa -from __future__ import annotations - -import collections.abc as collections_abc -import logging -import re -from typing import cast - -from . import ranges -from ._psycopg_common import _PGDialect_common_psycopg -from ._psycopg_common import _PGExecutionContext_common_psycopg -from .base import PGIdentifierPreparer -from .json import JSON -from .json import JSONB -from ... import types as sqltypes -from ... import util -from ...util import FastIntFlag -from ...util import parse_user_argument_for_enum - -logger = logging.getLogger("sqlalchemy.dialects.postgresql") - - -class _PGJSON(JSON): - def result_processor(self, dialect, coltype): - return None - - -class _PGJSONB(JSONB): - def result_processor(self, dialect, coltype): - return None - - -class _Psycopg2Range(ranges.AbstractSingleRangeImpl): - _psycopg2_range_cls = "none" - - def bind_processor(self, dialect): - psycopg2_Range = getattr( - cast(PGDialect_psycopg2, dialect)._psycopg2_extras, - self._psycopg2_range_cls, - ) - - def to_range(value): - if isinstance(value, ranges.Range): - value = psycopg2_Range( - value.lower, value.upper, value.bounds, value.empty - ) - return value - - return to_range - - def result_processor(self, dialect, coltype): - def to_range(value): - if value is not None: - value = ranges.Range( - value._lower, - value._upper, - bounds=value._bounds if value._bounds else "[)", - empty=not value._bounds, - ) - return value - - return to_range - - -class _Psycopg2NumericRange(_Psycopg2Range): - _psycopg2_range_cls = "NumericRange" - - -class _Psycopg2DateRange(_Psycopg2Range): - _psycopg2_range_cls = "DateRange" - - -class _Psycopg2DateTimeRange(_Psycopg2Range): - _psycopg2_range_cls = "DateTimeRange" - - -class _Psycopg2DateTimeTZRange(_Psycopg2Range): - _psycopg2_range_cls = "DateTimeTZRange" - - -class PGExecutionContext_psycopg2(_PGExecutionContext_common_psycopg): - _psycopg2_fetched_rows = None - - def post_exec(self): - self._log_notices(self.cursor) - - def _log_notices(self, cursor): - # check also that notices is an iterable, after it's already - # established that we will be iterating through it. This is to get - # around test suites such as SQLAlchemy's using a Mock object for - # cursor - if not cursor.connection.notices or not isinstance( - cursor.connection.notices, collections_abc.Iterable - ): - return - - for notice in cursor.connection.notices: - # NOTICE messages have a - # newline character at the end - logger.info(notice.rstrip()) - - cursor.connection.notices[:] = [] - - -class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer): - pass - - -class ExecutemanyMode(FastIntFlag): - EXECUTEMANY_VALUES = 0 - EXECUTEMANY_VALUES_PLUS_BATCH = 1 - - -( - EXECUTEMANY_VALUES, - EXECUTEMANY_VALUES_PLUS_BATCH, -) = ExecutemanyMode.__members__.values() - - -class PGDialect_psycopg2(_PGDialect_common_psycopg): - driver = "psycopg2" - - supports_statement_cache = True - supports_server_side_cursors = True - - default_paramstyle = "pyformat" - # set to true based on psycopg2 version - supports_sane_multi_rowcount = False - execution_ctx_cls = PGExecutionContext_psycopg2 - preparer = PGIdentifierPreparer_psycopg2 - psycopg2_version = (0, 0) - use_insertmanyvalues_wo_returning = True - - returns_native_bytes = False - - _has_native_hstore = True - - colspecs = util.update_copy( - _PGDialect_common_psycopg.colspecs, - { - JSON: _PGJSON, - sqltypes.JSON: _PGJSON, - JSONB: _PGJSONB, - ranges.INT4RANGE: _Psycopg2NumericRange, - ranges.INT8RANGE: _Psycopg2NumericRange, - ranges.NUMRANGE: _Psycopg2NumericRange, - ranges.DATERANGE: _Psycopg2DateRange, - ranges.TSRANGE: _Psycopg2DateTimeRange, - ranges.TSTZRANGE: _Psycopg2DateTimeTZRange, - }, - ) - - def __init__( - self, - executemany_mode="values_only", - executemany_batch_page_size=100, - **kwargs, - ): - _PGDialect_common_psycopg.__init__(self, **kwargs) - - if self._native_inet_types: - raise NotImplementedError( - "The psycopg2 dialect does not implement " - "ipaddress type handling; native_inet_types cannot be set " - "to ``True`` when using this dialect." - ) - - # Parse executemany_mode argument, allowing it to be only one of the - # symbol names - self.executemany_mode = parse_user_argument_for_enum( - executemany_mode, - { - EXECUTEMANY_VALUES: ["values_only"], - EXECUTEMANY_VALUES_PLUS_BATCH: ["values_plus_batch"], - }, - "executemany_mode", - ) - - self.executemany_batch_page_size = executemany_batch_page_size - - if self.dbapi and hasattr(self.dbapi, "__version__"): - m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__) - if m: - self.psycopg2_version = tuple( - int(x) for x in m.group(1, 2, 3) if x is not None - ) - - if self.psycopg2_version < (2, 7): - raise ImportError( - "psycopg2 version 2.7 or higher is required." - ) - - def initialize(self, connection): - super().initialize(connection) - self._has_native_hstore = ( - self.use_native_hstore - and self._hstore_oids(connection.connection.dbapi_connection) - is not None - ) - - self.supports_sane_multi_rowcount = ( - self.executemany_mode is not EXECUTEMANY_VALUES_PLUS_BATCH - ) - - @classmethod - def import_dbapi(cls): - import psycopg2 - - return psycopg2 - - @util.memoized_property - def _psycopg2_extensions(cls): - from psycopg2 import extensions - - return extensions - - @util.memoized_property - def _psycopg2_extras(cls): - from psycopg2 import extras - - return extras - - @util.memoized_property - def _isolation_lookup(self): - extensions = self._psycopg2_extensions - return { - "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT, - "READ COMMITTED": extensions.ISOLATION_LEVEL_READ_COMMITTED, - "READ UNCOMMITTED": extensions.ISOLATION_LEVEL_READ_UNCOMMITTED, - "REPEATABLE READ": extensions.ISOLATION_LEVEL_REPEATABLE_READ, - "SERIALIZABLE": extensions.ISOLATION_LEVEL_SERIALIZABLE, - } - - def set_isolation_level(self, dbapi_connection, level): - dbapi_connection.set_isolation_level(self._isolation_lookup[level]) - - def set_readonly(self, connection, value): - connection.readonly = value - - def get_readonly(self, connection): - return connection.readonly - - def set_deferrable(self, connection, value): - connection.deferrable = value - - def get_deferrable(self, connection): - return connection.deferrable - - def on_connect(self): - extras = self._psycopg2_extras - - fns = [] - if self.client_encoding is not None: - - def on_connect(dbapi_conn): - dbapi_conn.set_client_encoding(self.client_encoding) - - fns.append(on_connect) - - if self.dbapi: - - def on_connect(dbapi_conn): - extras.register_uuid(None, dbapi_conn) - - fns.append(on_connect) - - if self.dbapi and self.use_native_hstore: - - def on_connect(dbapi_conn): - hstore_oids = self._hstore_oids(dbapi_conn) - if hstore_oids is not None: - oid, array_oid = hstore_oids - kw = {"oid": oid} - kw["array_oid"] = array_oid - extras.register_hstore(dbapi_conn, **kw) - - fns.append(on_connect) - - if self.dbapi and self._json_deserializer: - - def on_connect(dbapi_conn): - extras.register_default_json( - dbapi_conn, loads=self._json_deserializer - ) - extras.register_default_jsonb( - dbapi_conn, loads=self._json_deserializer - ) - - fns.append(on_connect) - - if fns: - - def on_connect(dbapi_conn): - for fn in fns: - fn(dbapi_conn) - - return on_connect - else: - return None - - def do_executemany(self, cursor, statement, parameters, context=None): - if self.executemany_mode is EXECUTEMANY_VALUES_PLUS_BATCH: - if self.executemany_batch_page_size: - kwargs = {"page_size": self.executemany_batch_page_size} - else: - kwargs = {} - self._psycopg2_extras.execute_batch( - cursor, statement, parameters, **kwargs - ) - else: - cursor.executemany(statement, parameters) - - def do_begin_twophase(self, connection, xid): - connection.connection.tpc_begin(xid) - - def do_prepare_twophase(self, connection, xid): - connection.connection.tpc_prepare() - - def _do_twophase(self, dbapi_conn, operation, xid, recover=False): - if recover: - if dbapi_conn.status != self._psycopg2_extensions.STATUS_READY: - dbapi_conn.rollback() - operation(xid) - else: - operation() - - def do_rollback_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - dbapi_conn = connection.connection.dbapi_connection - self._do_twophase( - dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover - ) - - def do_commit_twophase( - self, connection, xid, is_prepared=True, recover=False - ): - dbapi_conn = connection.connection.dbapi_connection - self._do_twophase( - dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover - ) - - @util.memoized_instancemethod - def _hstore_oids(self, dbapi_connection): - extras = self._psycopg2_extras - oids = extras.HstoreAdapter.get_oids(dbapi_connection) - if oids is not None and oids[0]: - return oids[0:2] - else: - return None - - def is_disconnect(self, e, connection, cursor): - if isinstance(e, self.dbapi.Error): - # check the "closed" flag. this might not be - # present on old psycopg2 versions. Also, - # this flag doesn't actually help in a lot of disconnect - # situations, so don't rely on it. - if getattr(connection, "closed", False): - return True - - # checks based on strings. in the case that .closed - # didn't cut it, fall back onto these. - str_e = str(e).partition("\n")[0] - for msg in [ - # these error messages from libpq: interfaces/libpq/fe-misc.c - # and interfaces/libpq/fe-secure.c. - "terminating connection", - "closed the connection", - "connection not open", - "could not receive data from server", - "could not send data to server", - # psycopg2 client errors, psycopg2/connection.h, - # psycopg2/cursor.h - "connection already closed", - "cursor already closed", - # not sure where this path is originally from, it may - # be obsolete. It really says "losed", not "closed". - "losed the connection unexpectedly", - # these can occur in newer SSL - "connection has been closed unexpectedly", - "SSL error: decryption failed or bad record mac", - "SSL SYSCALL error: Bad file descriptor", - "SSL SYSCALL error: EOF detected", - "SSL SYSCALL error: Operation timed out", - "SSL SYSCALL error: Bad address", - ]: - idx = str_e.find(msg) - if idx >= 0 and '"' not in str_e[:idx]: - return True - return False - - -dialect = PGDialect_psycopg2 diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py deleted file mode 100644 index 3cc3b69..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py +++ /dev/null @@ -1,61 +0,0 @@ -# dialects/postgresql/psycopg2cffi.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -r""" -.. dialect:: postgresql+psycopg2cffi - :name: psycopg2cffi - :dbapi: psycopg2cffi - :connectstring: postgresql+psycopg2cffi://user:password@host:port/dbname[?key=value&key=value...] - :url: https://pypi.org/project/psycopg2cffi/ - -``psycopg2cffi`` is an adaptation of ``psycopg2``, using CFFI for the C -layer. This makes it suitable for use in e.g. PyPy. Documentation -is as per ``psycopg2``. - -.. seealso:: - - :mod:`sqlalchemy.dialects.postgresql.psycopg2` - -""" # noqa -from .psycopg2 import PGDialect_psycopg2 -from ... import util - - -class PGDialect_psycopg2cffi(PGDialect_psycopg2): - driver = "psycopg2cffi" - supports_unicode_statements = True - supports_statement_cache = True - - # psycopg2cffi's first release is 2.5.0, but reports - # __version__ as 2.4.4. Subsequent releases seem to have - # fixed this. - - FEATURE_VERSION_MAP = dict( - native_json=(2, 4, 4), - native_jsonb=(2, 7, 1), - sane_multi_rowcount=(2, 4, 4), - array_oid=(2, 4, 4), - hstore_adapter=(2, 4, 4), - ) - - @classmethod - def import_dbapi(cls): - return __import__("psycopg2cffi") - - @util.memoized_property - def _psycopg2_extensions(cls): - root = __import__("psycopg2cffi", fromlist=["extensions"]) - return root.extensions - - @util.memoized_property - def _psycopg2_extras(cls): - root = __import__("psycopg2cffi", fromlist=["extras"]) - return root.extras - - -dialect = PGDialect_psycopg2cffi diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ranges.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ranges.py deleted file mode 100644 index b793ca4..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/ranges.py +++ /dev/null @@ -1,1029 +0,0 @@ -# dialects/postgresql/ranges.py -# Copyright (C) 2013-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php - -from __future__ import annotations - -import dataclasses -from datetime import date -from datetime import datetime -from datetime import timedelta -from decimal import Decimal -from typing import Any -from typing import cast -from typing import Generic -from typing import List -from typing import Optional -from typing import overload -from typing import Sequence -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union - -from .operators import ADJACENT_TO -from .operators import CONTAINED_BY -from .operators import CONTAINS -from .operators import NOT_EXTEND_LEFT_OF -from .operators import NOT_EXTEND_RIGHT_OF -from .operators import OVERLAP -from .operators import STRICTLY_LEFT_OF -from .operators import STRICTLY_RIGHT_OF -from ... import types as sqltypes -from ...sql import operators -from ...sql.type_api import TypeEngine -from ...util import py310 -from ...util.typing import Literal - -if TYPE_CHECKING: - from ...sql.elements import ColumnElement - from ...sql.type_api import _TE - from ...sql.type_api import TypeEngineMixin - -_T = TypeVar("_T", bound=Any) - -_BoundsType = Literal["()", "[)", "(]", "[]"] - -if py310: - dc_slots = {"slots": True} - dc_kwonly = {"kw_only": True} -else: - dc_slots = {} - dc_kwonly = {} - - -@dataclasses.dataclass(frozen=True, **dc_slots) -class Range(Generic[_T]): - """Represent a PostgreSQL range. - - E.g.:: - - r = Range(10, 50, bounds="()") - - The calling style is similar to that of psycopg and psycopg2, in part - to allow easier migration from previous SQLAlchemy versions that used - these objects directly. - - :param lower: Lower bound value, or None - :param upper: Upper bound value, or None - :param bounds: keyword-only, optional string value that is one of - ``"()"``, ``"[)"``, ``"(]"``, ``"[]"``. Defaults to ``"[)"``. - :param empty: keyword-only, optional bool indicating this is an "empty" - range - - .. versionadded:: 2.0 - - """ - - lower: Optional[_T] = None - """the lower bound""" - - upper: Optional[_T] = None - """the upper bound""" - - if TYPE_CHECKING: - bounds: _BoundsType = dataclasses.field(default="[)") - empty: bool = dataclasses.field(default=False) - else: - bounds: _BoundsType = dataclasses.field(default="[)", **dc_kwonly) - empty: bool = dataclasses.field(default=False, **dc_kwonly) - - if not py310: - - def __init__( - self, - lower: Optional[_T] = None, - upper: Optional[_T] = None, - *, - bounds: _BoundsType = "[)", - empty: bool = False, - ): - # no __slots__ either so we can update dict - self.__dict__.update( - { - "lower": lower, - "upper": upper, - "bounds": bounds, - "empty": empty, - } - ) - - def __bool__(self) -> bool: - return not self.empty - - @property - def isempty(self) -> bool: - "A synonym for the 'empty' attribute." - - return self.empty - - @property - def is_empty(self) -> bool: - "A synonym for the 'empty' attribute." - - return self.empty - - @property - def lower_inc(self) -> bool: - """Return True if the lower bound is inclusive.""" - - return self.bounds[0] == "[" - - @property - def lower_inf(self) -> bool: - """Return True if this range is non-empty and lower bound is - infinite.""" - - return not self.empty and self.lower is None - - @property - def upper_inc(self) -> bool: - """Return True if the upper bound is inclusive.""" - - return self.bounds[1] == "]" - - @property - def upper_inf(self) -> bool: - """Return True if this range is non-empty and the upper bound is - infinite.""" - - return not self.empty and self.upper is None - - @property - def __sa_type_engine__(self) -> AbstractSingleRange[_T]: - return AbstractSingleRange() - - def _contains_value(self, value: _T) -> bool: - """Return True if this range contains the given value.""" - - if self.empty: - return False - - if self.lower is None: - return self.upper is None or ( - value < self.upper - if self.bounds[1] == ")" - else value <= self.upper - ) - - if self.upper is None: - return ( # type: ignore - value > self.lower - if self.bounds[0] == "(" - else value >= self.lower - ) - - return ( # type: ignore - value > self.lower - if self.bounds[0] == "(" - else value >= self.lower - ) and ( - value < self.upper - if self.bounds[1] == ")" - else value <= self.upper - ) - - def _get_discrete_step(self) -> Any: - "Determine the “step” for this range, if it is a discrete one." - - # See - # https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DISCRETE - # for the rationale - - if isinstance(self.lower, int) or isinstance(self.upper, int): - return 1 - elif isinstance(self.lower, datetime) or isinstance( - self.upper, datetime - ): - # This is required, because a `isinstance(datetime.now(), date)` - # is True - return None - elif isinstance(self.lower, date) or isinstance(self.upper, date): - return timedelta(days=1) - else: - return None - - def _compare_edges( - self, - value1: Optional[_T], - bound1: str, - value2: Optional[_T], - bound2: str, - only_values: bool = False, - ) -> int: - """Compare two range bounds. - - Return -1, 0 or 1 respectively when `value1` is less than, - equal to or greater than `value2`. - - When `only_value` is ``True``, do not consider the *inclusivity* - of the edges, just their values. - """ - - value1_is_lower_bound = bound1 in {"[", "("} - value2_is_lower_bound = bound2 in {"[", "("} - - # Infinite edges are equal when they are on the same side, - # otherwise a lower edge is considered less than the upper end - if value1 is value2 is None: - if value1_is_lower_bound == value2_is_lower_bound: - return 0 - else: - return -1 if value1_is_lower_bound else 1 - elif value1 is None: - return -1 if value1_is_lower_bound else 1 - elif value2 is None: - return 1 if value2_is_lower_bound else -1 - - # Short path for trivial case - if bound1 == bound2 and value1 == value2: - return 0 - - value1_inc = bound1 in {"[", "]"} - value2_inc = bound2 in {"[", "]"} - step = self._get_discrete_step() - - if step is not None: - # "Normalize" the two edges as '[)', to simplify successive - # logic when the range is discrete: otherwise we would need - # to handle the comparison between ``(0`` and ``[1`` that - # are equal when dealing with integers while for floats the - # former is lesser than the latter - - if value1_is_lower_bound: - if not value1_inc: - value1 += step - value1_inc = True - else: - if value1_inc: - value1 += step - value1_inc = False - if value2_is_lower_bound: - if not value2_inc: - value2 += step - value2_inc = True - else: - if value2_inc: - value2 += step - value2_inc = False - - if value1 < value2: # type: ignore - return -1 - elif value1 > value2: # type: ignore - return 1 - elif only_values: - return 0 - else: - # Neither one is infinite but are equal, so we - # need to consider the respective inclusive/exclusive - # flag - - if value1_inc and value2_inc: - return 0 - elif not value1_inc and not value2_inc: - if value1_is_lower_bound == value2_is_lower_bound: - return 0 - else: - return 1 if value1_is_lower_bound else -1 - elif not value1_inc: - return 1 if value1_is_lower_bound else -1 - elif not value2_inc: - return -1 if value2_is_lower_bound else 1 - else: - return 0 - - def __eq__(self, other: Any) -> bool: - """Compare this range to the `other` taking into account - bounds inclusivity, returning ``True`` if they are equal. - """ - - if not isinstance(other, Range): - return NotImplemented - - if self.empty and other.empty: - return True - elif self.empty != other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - olower = other.lower - olower_b = other.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - oupper = other.upper - oupper_b = other.bounds[1] - - return ( - self._compare_edges(slower, slower_b, olower, olower_b) == 0 - and self._compare_edges(supper, supper_b, oupper, oupper_b) == 0 - ) - - def contained_by(self, other: Range[_T]) -> bool: - "Determine whether this range is a contained by `other`." - - # Any range contains the empty one - if self.empty: - return True - - # An empty range does not contain any range except the empty one - if other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - olower = other.lower - olower_b = other.bounds[0] - - if self._compare_edges(slower, slower_b, olower, olower_b) < 0: - return False - - supper = self.upper - supper_b = self.bounds[1] - oupper = other.upper - oupper_b = other.bounds[1] - - if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0: - return False - - return True - - def contains(self, value: Union[_T, Range[_T]]) -> bool: - "Determine whether this range contains `value`." - - if isinstance(value, Range): - return value.contained_by(self) - else: - return self._contains_value(value) - - def overlaps(self, other: Range[_T]) -> bool: - "Determine whether this range overlaps with `other`." - - # Empty ranges never overlap with any other range - if self.empty or other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - # Check whether this lower bound is contained in the other range - if ( - self._compare_edges(slower, slower_b, olower, olower_b) >= 0 - and self._compare_edges(slower, slower_b, oupper, oupper_b) <= 0 - ): - return True - - # Check whether other lower bound is contained in this range - if ( - self._compare_edges(olower, olower_b, slower, slower_b) >= 0 - and self._compare_edges(olower, olower_b, supper, supper_b) <= 0 - ): - return True - - return False - - def strictly_left_of(self, other: Range[_T]) -> bool: - "Determine whether this range is completely to the left of `other`." - - # Empty ranges are neither to left nor to the right of any other range - if self.empty or other.empty: - return False - - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - - # Check whether this upper edge is less than other's lower end - return self._compare_edges(supper, supper_b, olower, olower_b) < 0 - - __lshift__ = strictly_left_of - - def strictly_right_of(self, other: Range[_T]) -> bool: - "Determine whether this range is completely to the right of `other`." - - # Empty ranges are neither to left nor to the right of any other range - if self.empty or other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - # Check whether this lower edge is greater than other's upper end - return self._compare_edges(slower, slower_b, oupper, oupper_b) > 0 - - __rshift__ = strictly_right_of - - def not_extend_left_of(self, other: Range[_T]) -> bool: - "Determine whether this does not extend to the left of `other`." - - # Empty ranges are neither to left nor to the right of any other range - if self.empty or other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - olower = other.lower - olower_b = other.bounds[0] - - # Check whether this lower edge is not less than other's lower end - return self._compare_edges(slower, slower_b, olower, olower_b) >= 0 - - def not_extend_right_of(self, other: Range[_T]) -> bool: - "Determine whether this does not extend to the right of `other`." - - # Empty ranges are neither to left nor to the right of any other range - if self.empty or other.empty: - return False - - supper = self.upper - supper_b = self.bounds[1] - oupper = other.upper - oupper_b = other.bounds[1] - - # Check whether this upper edge is not greater than other's upper end - return self._compare_edges(supper, supper_b, oupper, oupper_b) <= 0 - - def _upper_edge_adjacent_to_lower( - self, - value1: Optional[_T], - bound1: str, - value2: Optional[_T], - bound2: str, - ) -> bool: - """Determine whether an upper bound is immediately successive to a - lower bound.""" - - # Since we need a peculiar way to handle the bounds inclusivity, - # just do a comparison by value here - res = self._compare_edges(value1, bound1, value2, bound2, True) - if res == -1: - step = self._get_discrete_step() - if step is None: - return False - if bound1 == "]": - if bound2 == "[": - return value1 == value2 - step # type: ignore - else: - return value1 == value2 - else: - if bound2 == "[": - return value1 == value2 - else: - return value1 == value2 - step # type: ignore - elif res == 0: - # Cover cases like [0,0] -|- [1,] and [0,2) -|- (1,3] - if ( - bound1 == "]" - and bound2 == "[" - or bound1 == ")" - and bound2 == "(" - ): - step = self._get_discrete_step() - if step is not None: - return True - return ( - bound1 == ")" - and bound2 == "[" - or bound1 == "]" - and bound2 == "(" - ) - else: - return False - - def adjacent_to(self, other: Range[_T]) -> bool: - "Determine whether this range is adjacent to the `other`." - - # Empty ranges are not adjacent to any other range - if self.empty or other.empty: - return False - - slower = self.lower - slower_b = self.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - return self._upper_edge_adjacent_to_lower( - supper, supper_b, olower, olower_b - ) or self._upper_edge_adjacent_to_lower( - oupper, oupper_b, slower, slower_b - ) - - def union(self, other: Range[_T]) -> Range[_T]: - """Compute the union of this range with the `other`. - - This raises a ``ValueError`` exception if the two ranges are - "disjunct", that is neither adjacent nor overlapping. - """ - - # Empty ranges are "additive identities" - if self.empty: - return other - if other.empty: - return self - - if not self.overlaps(other) and not self.adjacent_to(other): - raise ValueError( - "Adding non-overlapping and non-adjacent" - " ranges is not implemented" - ) - - slower = self.lower - slower_b = self.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - if self._compare_edges(slower, slower_b, olower, olower_b) < 0: - rlower = slower - rlower_b = slower_b - else: - rlower = olower - rlower_b = olower_b - - if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0: - rupper = supper - rupper_b = supper_b - else: - rupper = oupper - rupper_b = oupper_b - - return Range( - rlower, rupper, bounds=cast(_BoundsType, rlower_b + rupper_b) - ) - - def __add__(self, other: Range[_T]) -> Range[_T]: - return self.union(other) - - def difference(self, other: Range[_T]) -> Range[_T]: - """Compute the difference between this range and the `other`. - - This raises a ``ValueError`` exception if the two ranges are - "disjunct", that is neither adjacent nor overlapping. - """ - - # Subtracting an empty range is a no-op - if self.empty or other.empty: - return self - - slower = self.lower - slower_b = self.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - sl_vs_ol = self._compare_edges(slower, slower_b, olower, olower_b) - su_vs_ou = self._compare_edges(supper, supper_b, oupper, oupper_b) - if sl_vs_ol < 0 and su_vs_ou > 0: - raise ValueError( - "Subtracting a strictly inner range is not implemented" - ) - - sl_vs_ou = self._compare_edges(slower, slower_b, oupper, oupper_b) - su_vs_ol = self._compare_edges(supper, supper_b, olower, olower_b) - - # If the ranges do not overlap, result is simply the first - if sl_vs_ou > 0 or su_vs_ol < 0: - return self - - # If this range is completely contained by the other, result is empty - if sl_vs_ol >= 0 and su_vs_ou <= 0: - return Range(None, None, empty=True) - - # If this range extends to the left of the other and ends in its - # middle - if sl_vs_ol <= 0 and su_vs_ol >= 0 and su_vs_ou <= 0: - rupper_b = ")" if olower_b == "[" else "]" - if ( - slower_b != "[" - and rupper_b != "]" - and self._compare_edges(slower, slower_b, olower, rupper_b) - == 0 - ): - return Range(None, None, empty=True) - else: - return Range( - slower, - olower, - bounds=cast(_BoundsType, slower_b + rupper_b), - ) - - # If this range starts in the middle of the other and extends to its - # right - if sl_vs_ol >= 0 and su_vs_ou >= 0 and sl_vs_ou <= 0: - rlower_b = "(" if oupper_b == "]" else "[" - if ( - rlower_b != "[" - and supper_b != "]" - and self._compare_edges(oupper, rlower_b, supper, supper_b) - == 0 - ): - return Range(None, None, empty=True) - else: - return Range( - oupper, - supper, - bounds=cast(_BoundsType, rlower_b + supper_b), - ) - - assert False, f"Unhandled case computing {self} - {other}" - - def __sub__(self, other: Range[_T]) -> Range[_T]: - return self.difference(other) - - def intersection(self, other: Range[_T]) -> Range[_T]: - """Compute the intersection of this range with the `other`. - - .. versionadded:: 2.0.10 - - """ - if self.empty or other.empty or not self.overlaps(other): - return Range(None, None, empty=True) - - slower = self.lower - slower_b = self.bounds[0] - supper = self.upper - supper_b = self.bounds[1] - olower = other.lower - olower_b = other.bounds[0] - oupper = other.upper - oupper_b = other.bounds[1] - - if self._compare_edges(slower, slower_b, olower, olower_b) < 0: - rlower = olower - rlower_b = olower_b - else: - rlower = slower - rlower_b = slower_b - - if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0: - rupper = oupper - rupper_b = oupper_b - else: - rupper = supper - rupper_b = supper_b - - return Range( - rlower, - rupper, - bounds=cast(_BoundsType, rlower_b + rupper_b), - ) - - def __mul__(self, other: Range[_T]) -> Range[_T]: - return self.intersection(other) - - def __str__(self) -> str: - return self._stringify() - - def _stringify(self) -> str: - if self.empty: - return "empty" - - l, r = self.lower, self.upper - l = "" if l is None else l # type: ignore - r = "" if r is None else r # type: ignore - - b0, b1 = cast("Tuple[str, str]", self.bounds) - - return f"{b0}{l},{r}{b1}" - - -class MultiRange(List[Range[_T]]): - """Represents a multirange sequence. - - This list subclass is an utility to allow automatic type inference of - the proper multi-range SQL type depending on the single range values. - This is useful when operating on literal multi-ranges:: - - import sqlalchemy as sa - from sqlalchemy.dialects.postgresql import MultiRange, Range - - value = literal(MultiRange([Range(2, 4)])) - - select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)]))) - - .. versionadded:: 2.0.26 - - .. seealso:: - - - :ref:`postgresql_multirange_list_use`. - """ - - @property - def __sa_type_engine__(self) -> AbstractMultiRange[_T]: - return AbstractMultiRange() - - -class AbstractRange(sqltypes.TypeEngine[_T]): - """Base class for single and multi Range SQL types.""" - - render_bind_cast = True - - __abstract__ = True - - @overload - def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ... - - @overload - def adapt( - self, cls: Type[TypeEngineMixin], **kw: Any - ) -> TypeEngine[Any]: ... - - def adapt( - self, - cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], - **kw: Any, - ) -> TypeEngine[Any]: - """Dynamically adapt a range type to an abstract impl. - - For example ``INT4RANGE().adapt(_Psycopg2NumericRange)`` should - produce a type that will have ``_Psycopg2NumericRange`` behaviors - and also render as ``INT4RANGE`` in SQL and DDL. - - """ - if ( - issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl)) - and cls is not self.__class__ - ): - # two ways to do this are: 1. create a new type on the fly - # or 2. have AbstractRangeImpl(visit_name) constructor and a - # visit_abstract_range_impl() method in the PG compiler. - # I'm choosing #1 as the resulting type object - # will then make use of the same mechanics - # as if we had made all these sub-types explicitly, and will - # also look more obvious under pdb etc. - # The adapt() operation here is cached per type-class-per-dialect, - # so is not much of a performance concern - visit_name = self.__visit_name__ - return type( # type: ignore - f"{visit_name}RangeImpl", - (cls, self.__class__), - {"__visit_name__": visit_name}, - )() - else: - return super().adapt(cls) - - class comparator_factory(TypeEngine.Comparator[Range[Any]]): - """Define comparison operations for range types.""" - - def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the right hand operand, - which can be an element or a range, is contained within the - column. - - kwargs may be ignored by this operator but are required for API - conformance. - """ - return self.expr.operate(CONTAINS, other) - - def contained_by(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the column is contained - within the right hand operand. - """ - return self.expr.operate(CONTAINED_BY, other) - - def overlaps(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the column overlaps - (has points in common with) the right hand operand. - """ - return self.expr.operate(OVERLAP, other) - - def strictly_left_of(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the column is strictly - left of the right hand operand. - """ - return self.expr.operate(STRICTLY_LEFT_OF, other) - - __lshift__ = strictly_left_of - - def strictly_right_of(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the column is strictly - right of the right hand operand. - """ - return self.expr.operate(STRICTLY_RIGHT_OF, other) - - __rshift__ = strictly_right_of - - def not_extend_right_of(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the range in the column - does not extend right of the range in the operand. - """ - return self.expr.operate(NOT_EXTEND_RIGHT_OF, other) - - def not_extend_left_of(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the range in the column - does not extend left of the range in the operand. - """ - return self.expr.operate(NOT_EXTEND_LEFT_OF, other) - - def adjacent_to(self, other: Any) -> ColumnElement[bool]: - """Boolean expression. Returns true if the range in the column - is adjacent to the range in the operand. - """ - return self.expr.operate(ADJACENT_TO, other) - - def union(self, other: Any) -> ColumnElement[bool]: - """Range expression. Returns the union of the two ranges. - Will raise an exception if the resulting range is not - contiguous. - """ - return self.expr.operate(operators.add, other) - - def difference(self, other: Any) -> ColumnElement[bool]: - """Range expression. Returns the union of the two ranges. - Will raise an exception if the resulting range is not - contiguous. - """ - return self.expr.operate(operators.sub, other) - - def intersection(self, other: Any) -> ColumnElement[Range[_T]]: - """Range expression. Returns the intersection of the two ranges. - Will raise an exception if the resulting range is not - contiguous. - """ - return self.expr.operate(operators.mul, other) - - -class AbstractSingleRange(AbstractRange[Range[_T]]): - """Base for PostgreSQL RANGE types. - - These are types that return a single :class:`_postgresql.Range` object. - - .. seealso:: - - `PostgreSQL range functions `_ - - """ # noqa: E501 - - __abstract__ = True - - def _resolve_for_literal(self, value: Range[Any]) -> Any: - spec = value.lower if value.lower is not None else value.upper - - if isinstance(spec, int): - # pg is unreasonably picky here: the query - # "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises - # "operator does not exist: integer <@ int8range" as of pg 16 - if _is_int32(value): - return INT4RANGE() - else: - return INT8RANGE() - elif isinstance(spec, (Decimal, float)): - return NUMRANGE() - elif isinstance(spec, datetime): - return TSRANGE() if not spec.tzinfo else TSTZRANGE() - elif isinstance(spec, date): - return DATERANGE() - else: - # empty Range, SQL datatype can't be determined here - return sqltypes.NULLTYPE - - -class AbstractSingleRangeImpl(AbstractSingleRange[_T]): - """Marker for AbstractSingleRange that will apply a subclass-specific - adaptation""" - - -class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]): - """Base for PostgreSQL MULTIRANGE types. - - these are types that return a sequence of :class:`_postgresql.Range` - objects. - - """ - - __abstract__ = True - - def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any: - if not value: - # empty MultiRange, SQL datatype can't be determined here - return sqltypes.NULLTYPE - first = value[0] - spec = first.lower if first.lower is not None else first.upper - - if isinstance(spec, int): - # pg is unreasonably picky here: the query - # "select 1::INTEGER <@ '{[1, 4),[6,19)}'::INT8MULTIRANGE" raises - # "operator does not exist: integer <@ int8multirange" as of pg 16 - if all(_is_int32(r) for r in value): - return INT4MULTIRANGE() - else: - return INT8MULTIRANGE() - elif isinstance(spec, (Decimal, float)): - return NUMMULTIRANGE() - elif isinstance(spec, datetime): - return TSMULTIRANGE() if not spec.tzinfo else TSTZMULTIRANGE() - elif isinstance(spec, date): - return DATEMULTIRANGE() - else: - # empty Range, SQL datatype can't be determined here - return sqltypes.NULLTYPE - - -class AbstractMultiRangeImpl(AbstractMultiRange[_T]): - """Marker for AbstractMultiRange that will apply a subclass-specific - adaptation""" - - -class INT4RANGE(AbstractSingleRange[int]): - """Represent the PostgreSQL INT4RANGE type.""" - - __visit_name__ = "INT4RANGE" - - -class INT8RANGE(AbstractSingleRange[int]): - """Represent the PostgreSQL INT8RANGE type.""" - - __visit_name__ = "INT8RANGE" - - -class NUMRANGE(AbstractSingleRange[Decimal]): - """Represent the PostgreSQL NUMRANGE type.""" - - __visit_name__ = "NUMRANGE" - - -class DATERANGE(AbstractSingleRange[date]): - """Represent the PostgreSQL DATERANGE type.""" - - __visit_name__ = "DATERANGE" - - -class TSRANGE(AbstractSingleRange[datetime]): - """Represent the PostgreSQL TSRANGE type.""" - - __visit_name__ = "TSRANGE" - - -class TSTZRANGE(AbstractSingleRange[datetime]): - """Represent the PostgreSQL TSTZRANGE type.""" - - __visit_name__ = "TSTZRANGE" - - -class INT4MULTIRANGE(AbstractMultiRange[int]): - """Represent the PostgreSQL INT4MULTIRANGE type.""" - - __visit_name__ = "INT4MULTIRANGE" - - -class INT8MULTIRANGE(AbstractMultiRange[int]): - """Represent the PostgreSQL INT8MULTIRANGE type.""" - - __visit_name__ = "INT8MULTIRANGE" - - -class NUMMULTIRANGE(AbstractMultiRange[Decimal]): - """Represent the PostgreSQL NUMMULTIRANGE type.""" - - __visit_name__ = "NUMMULTIRANGE" - - -class DATEMULTIRANGE(AbstractMultiRange[date]): - """Represent the PostgreSQL DATEMULTIRANGE type.""" - - __visit_name__ = "DATEMULTIRANGE" - - -class TSMULTIRANGE(AbstractMultiRange[datetime]): - """Represent the PostgreSQL TSRANGE type.""" - - __visit_name__ = "TSMULTIRANGE" - - -class TSTZMULTIRANGE(AbstractMultiRange[datetime]): - """Represent the PostgreSQL TSTZRANGE type.""" - - __visit_name__ = "TSTZMULTIRANGE" - - -_max_int_32 = 2**31 - 1 -_min_int_32 = -(2**31) - - -def _is_int32(r: Range[int]) -> bool: - return (r.lower is None or _min_int_32 <= r.lower <= _max_int_32) and ( - r.upper is None or _min_int_32 <= r.upper <= _max_int_32 - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/types.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/types.py deleted file mode 100644 index 2acf63b..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/types.py +++ /dev/null @@ -1,303 +0,0 @@ -# dialects/postgresql/types.py -# Copyright (C) 2013-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -import datetime as dt -from typing import Any -from typing import Optional -from typing import overload -from typing import Type -from typing import TYPE_CHECKING -from uuid import UUID as _python_UUID - -from ...sql import sqltypes -from ...sql import type_api -from ...util.typing import Literal - -if TYPE_CHECKING: - from ...engine.interfaces import Dialect - from ...sql.operators import OperatorType - from ...sql.type_api import _LiteralProcessorType - from ...sql.type_api import TypeEngine - -_DECIMAL_TYPES = (1231, 1700) -_FLOAT_TYPES = (700, 701, 1021, 1022) -_INT_TYPES = (20, 21, 23, 26, 1005, 1007, 1016) - - -class PGUuid(sqltypes.UUID[sqltypes._UUID_RETURN]): - render_bind_cast = True - render_literal_cast = True - - if TYPE_CHECKING: - - @overload - def __init__( - self: PGUuid[_python_UUID], as_uuid: Literal[True] = ... - ) -> None: ... - - @overload - def __init__( - self: PGUuid[str], as_uuid: Literal[False] = ... - ) -> None: ... - - def __init__(self, as_uuid: bool = True) -> None: ... - - -class BYTEA(sqltypes.LargeBinary): - __visit_name__ = "BYTEA" - - -class INET(sqltypes.TypeEngine[str]): - __visit_name__ = "INET" - - -PGInet = INET - - -class CIDR(sqltypes.TypeEngine[str]): - __visit_name__ = "CIDR" - - -PGCidr = CIDR - - -class MACADDR(sqltypes.TypeEngine[str]): - __visit_name__ = "MACADDR" - - -PGMacAddr = MACADDR - - -class MACADDR8(sqltypes.TypeEngine[str]): - __visit_name__ = "MACADDR8" - - -PGMacAddr8 = MACADDR8 - - -class MONEY(sqltypes.TypeEngine[str]): - r"""Provide the PostgreSQL MONEY type. - - Depending on driver, result rows using this type may return a - string value which includes currency symbols. - - For this reason, it may be preferable to provide conversion to a - numerically-based currency datatype using :class:`_types.TypeDecorator`:: - - import re - import decimal - from sqlalchemy import Dialect - from sqlalchemy import TypeDecorator - - class NumericMoney(TypeDecorator): - impl = MONEY - - def process_result_value( - self, value: Any, dialect: Dialect - ) -> None: - if value is not None: - # adjust this for the currency and numeric - m = re.match(r"\$([\d.]+)", value) - if m: - value = decimal.Decimal(m.group(1)) - return value - - Alternatively, the conversion may be applied as a CAST using - the :meth:`_types.TypeDecorator.column_expression` method as follows:: - - import decimal - from sqlalchemy import cast - from sqlalchemy import TypeDecorator - - class NumericMoney(TypeDecorator): - impl = MONEY - - def column_expression(self, column: Any): - return cast(column, Numeric()) - - .. versionadded:: 1.2 - - """ - - __visit_name__ = "MONEY" - - -class OID(sqltypes.TypeEngine[int]): - """Provide the PostgreSQL OID type.""" - - __visit_name__ = "OID" - - -class REGCONFIG(sqltypes.TypeEngine[str]): - """Provide the PostgreSQL REGCONFIG type. - - .. versionadded:: 2.0.0rc1 - - """ - - __visit_name__ = "REGCONFIG" - - -class TSQUERY(sqltypes.TypeEngine[str]): - """Provide the PostgreSQL TSQUERY type. - - .. versionadded:: 2.0.0rc1 - - """ - - __visit_name__ = "TSQUERY" - - -class REGCLASS(sqltypes.TypeEngine[str]): - """Provide the PostgreSQL REGCLASS type. - - .. versionadded:: 1.2.7 - - """ - - __visit_name__ = "REGCLASS" - - -class TIMESTAMP(sqltypes.TIMESTAMP): - """Provide the PostgreSQL TIMESTAMP type.""" - - __visit_name__ = "TIMESTAMP" - - def __init__( - self, timezone: bool = False, precision: Optional[int] = None - ) -> None: - """Construct a TIMESTAMP. - - :param timezone: boolean value if timezone present, default False - :param precision: optional integer precision value - - .. versionadded:: 1.4 - - """ - super().__init__(timezone=timezone) - self.precision = precision - - -class TIME(sqltypes.TIME): - """PostgreSQL TIME type.""" - - __visit_name__ = "TIME" - - def __init__( - self, timezone: bool = False, precision: Optional[int] = None - ) -> None: - """Construct a TIME. - - :param timezone: boolean value if timezone present, default False - :param precision: optional integer precision value - - .. versionadded:: 1.4 - - """ - super().__init__(timezone=timezone) - self.precision = precision - - -class INTERVAL(type_api.NativeForEmulated, sqltypes._AbstractInterval): - """PostgreSQL INTERVAL type.""" - - __visit_name__ = "INTERVAL" - native = True - - def __init__( - self, precision: Optional[int] = None, fields: Optional[str] = None - ) -> None: - """Construct an INTERVAL. - - :param precision: optional integer precision value - :param fields: string fields specifier. allows storage of fields - to be limited, such as ``"YEAR"``, ``"MONTH"``, ``"DAY TO HOUR"``, - etc. - - .. versionadded:: 1.2 - - """ - self.precision = precision - self.fields = fields - - @classmethod - def adapt_emulated_to_native( - cls, interval: sqltypes.Interval, **kw: Any # type: ignore[override] - ) -> INTERVAL: - return INTERVAL(precision=interval.second_precision) - - @property - def _type_affinity(self) -> Type[sqltypes.Interval]: - return sqltypes.Interval - - def as_generic(self, allow_nulltype: bool = False) -> sqltypes.Interval: - return sqltypes.Interval(native=True, second_precision=self.precision) - - @property - def python_type(self) -> Type[dt.timedelta]: - return dt.timedelta - - def literal_processor( - self, dialect: Dialect - ) -> Optional[_LiteralProcessorType[dt.timedelta]]: - def process(value: dt.timedelta) -> str: - return f"make_interval(secs=>{value.total_seconds()})" - - return process - - -PGInterval = INTERVAL - - -class BIT(sqltypes.TypeEngine[int]): - __visit_name__ = "BIT" - - def __init__( - self, length: Optional[int] = None, varying: bool = False - ) -> None: - if varying: - # BIT VARYING can be unlimited-length, so no default - self.length = length - else: - # BIT without VARYING defaults to length 1 - self.length = length or 1 - self.varying = varying - - -PGBit = BIT - - -class TSVECTOR(sqltypes.TypeEngine[str]): - """The :class:`_postgresql.TSVECTOR` type implements the PostgreSQL - text search type TSVECTOR. - - It can be used to do full text queries on natural language - documents. - - .. seealso:: - - :ref:`postgresql_match` - - """ - - __visit_name__ = "TSVECTOR" - - -class CITEXT(sqltypes.TEXT): - """Provide the PostgreSQL CITEXT type. - - .. versionadded:: 2.0.7 - - """ - - __visit_name__ = "CITEXT" - - def coerce_compared_value( - self, op: Optional[OperatorType], value: Any - ) -> TypeEngine[Any]: - return self diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__init__.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__init__.py deleted file mode 100644 index 45f088e..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -# dialects/sqlite/__init__.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -from . import aiosqlite # noqa -from . import base # noqa -from . import pysqlcipher # noqa -from . import pysqlite # noqa -from .base import BLOB -from .base import BOOLEAN -from .base import CHAR -from .base import DATE -from .base import DATETIME -from .base import DECIMAL -from .base import FLOAT -from .base import INTEGER -from .base import JSON -from .base import NUMERIC -from .base import REAL -from .base import SMALLINT -from .base import TEXT -from .base import TIME -from .base import TIMESTAMP -from .base import VARCHAR -from .dml import Insert -from .dml import insert - -# default dialect -base.dialect = dialect = pysqlite.dialect - - -__all__ = ( - "BLOB", - "BOOLEAN", - "CHAR", - "DATE", - "DATETIME", - "DECIMAL", - "FLOAT", - "INTEGER", - "JSON", - "NUMERIC", - "SMALLINT", - "TEXT", - "TIME", - "TIMESTAMP", - "VARCHAR", - "REAL", - "Insert", - "insert", - "dialect", -) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e4a9b51..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc deleted file mode 100644 index 41466a4..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc deleted file mode 100644 index e7f5c22..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc deleted file mode 100644 index eb0f448..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc deleted file mode 100644 index ad4323c..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc deleted file mode 100644 index d139ba3..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc deleted file mode 100644 index d26e7b3..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc deleted file mode 100644 index df08288..0000000 Binary files a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc and /dev/null differ diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py deleted file mode 100644 index 6c91563..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py +++ /dev/null @@ -1,396 +0,0 @@ -# dialects/sqlite/aiosqlite.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" - -.. dialect:: sqlite+aiosqlite - :name: aiosqlite - :dbapi: aiosqlite - :connectstring: sqlite+aiosqlite:///file_path - :url: https://pypi.org/project/aiosqlite/ - -The aiosqlite dialect provides support for the SQLAlchemy asyncio interface -running on top of pysqlite. - -aiosqlite is a wrapper around pysqlite that uses a background thread for -each connection. It does not actually use non-blocking IO, as SQLite -databases are not socket-based. However it does provide a working asyncio -interface that's useful for testing and prototyping purposes. - -Using a special asyncio mediation layer, the aiosqlite dialect is usable -as the backend for the :ref:`SQLAlchemy asyncio ` -extension package. - -This dialect should normally be used only with the -:func:`_asyncio.create_async_engine` engine creation function:: - - from sqlalchemy.ext.asyncio import create_async_engine - engine = create_async_engine("sqlite+aiosqlite:///filename") - -The URL passes through all arguments to the ``pysqlite`` driver, so all -connection arguments are the same as they are for that of :ref:`pysqlite`. - -.. _aiosqlite_udfs: - -User-Defined Functions ----------------------- - -aiosqlite extends pysqlite to support async, so we can create our own user-defined functions (UDFs) -in Python and use them directly in SQLite queries as described here: :ref:`pysqlite_udfs`. - -.. _aiosqlite_serializable: - -Serializable isolation / Savepoints / Transactional DDL (asyncio version) -------------------------------------------------------------------------- - -Similarly to pysqlite, aiosqlite does not support SAVEPOINT feature. - -The solution is similar to :ref:`pysqlite_serializable`. This is achieved by the event listeners in async:: - - from sqlalchemy import create_engine, event - from sqlalchemy.ext.asyncio import create_async_engine - - engine = create_async_engine("sqlite+aiosqlite:///myfile.db") - - @event.listens_for(engine.sync_engine, "connect") - def do_connect(dbapi_connection, connection_record): - # disable aiosqlite's emitting of the BEGIN statement entirely. - # also stops it from emitting COMMIT before any DDL. - dbapi_connection.isolation_level = None - - @event.listens_for(engine.sync_engine, "begin") - def do_begin(conn): - # emit our own BEGIN - conn.exec_driver_sql("BEGIN") - -.. warning:: When using the above recipe, it is advised to not use the - :paramref:`.Connection.execution_options.isolation_level` setting on - :class:`_engine.Connection` and :func:`_sa.create_engine` - with the SQLite driver, - as this function necessarily will also alter the ".isolation_level" setting. - -""" # noqa - -import asyncio -from functools import partial - -from .base import SQLiteExecutionContext -from .pysqlite import SQLiteDialect_pysqlite -from ... import pool -from ... import util -from ...engine import AdaptedConnection -from ...util.concurrency import await_fallback -from ...util.concurrency import await_only - - -class AsyncAdapt_aiosqlite_cursor: - # TODO: base on connectors/asyncio.py - # see #10415 - - __slots__ = ( - "_adapt_connection", - "_connection", - "description", - "await_", - "_rows", - "arraysize", - "rowcount", - "lastrowid", - ) - - server_side = False - - def __init__(self, adapt_connection): - self._adapt_connection = adapt_connection - self._connection = adapt_connection._connection - self.await_ = adapt_connection.await_ - self.arraysize = 1 - self.rowcount = -1 - self.description = None - self._rows = [] - - def close(self): - self._rows[:] = [] - - def execute(self, operation, parameters=None): - try: - _cursor = self.await_(self._connection.cursor()) - - if parameters is None: - self.await_(_cursor.execute(operation)) - else: - self.await_(_cursor.execute(operation, parameters)) - - if _cursor.description: - self.description = _cursor.description - self.lastrowid = self.rowcount = -1 - - if not self.server_side: - self._rows = self.await_(_cursor.fetchall()) - else: - self.description = None - self.lastrowid = _cursor.lastrowid - self.rowcount = _cursor.rowcount - - if not self.server_side: - self.await_(_cursor.close()) - else: - self._cursor = _cursor - except Exception as error: - self._adapt_connection._handle_exception(error) - - def executemany(self, operation, seq_of_parameters): - try: - _cursor = self.await_(self._connection.cursor()) - self.await_(_cursor.executemany(operation, seq_of_parameters)) - self.description = None - self.lastrowid = _cursor.lastrowid - self.rowcount = _cursor.rowcount - self.await_(_cursor.close()) - except Exception as error: - self._adapt_connection._handle_exception(error) - - def setinputsizes(self, *inputsizes): - pass - - def __iter__(self): - while self._rows: - yield self._rows.pop(0) - - def fetchone(self): - if self._rows: - return self._rows.pop(0) - else: - return None - - def fetchmany(self, size=None): - if size is None: - size = self.arraysize - - retval = self._rows[0:size] - self._rows[:] = self._rows[size:] - return retval - - def fetchall(self): - retval = self._rows[:] - self._rows[:] = [] - return retval - - -class AsyncAdapt_aiosqlite_ss_cursor(AsyncAdapt_aiosqlite_cursor): - # TODO: base on connectors/asyncio.py - # see #10415 - __slots__ = "_cursor" - - server_side = True - - def __init__(self, *arg, **kw): - super().__init__(*arg, **kw) - self._cursor = None - - def close(self): - if self._cursor is not None: - self.await_(self._cursor.close()) - self._cursor = None - - def fetchone(self): - return self.await_(self._cursor.fetchone()) - - def fetchmany(self, size=None): - if size is None: - size = self.arraysize - return self.await_(self._cursor.fetchmany(size=size)) - - def fetchall(self): - return self.await_(self._cursor.fetchall()) - - -class AsyncAdapt_aiosqlite_connection(AdaptedConnection): - await_ = staticmethod(await_only) - __slots__ = ("dbapi",) - - def __init__(self, dbapi, connection): - self.dbapi = dbapi - self._connection = connection - - @property - def isolation_level(self): - return self._connection.isolation_level - - @isolation_level.setter - def isolation_level(self, value): - # aiosqlite's isolation_level setter works outside the Thread - # that it's supposed to, necessitating setting check_same_thread=False. - # for improved stability, we instead invent our own awaitable version - # using aiosqlite's async queue directly. - - def set_iso(connection, value): - connection.isolation_level = value - - function = partial(set_iso, self._connection._conn, value) - future = asyncio.get_event_loop().create_future() - - self._connection._tx.put_nowait((future, function)) - - try: - return self.await_(future) - except Exception as error: - self._handle_exception(error) - - def create_function(self, *args, **kw): - try: - self.await_(self._connection.create_function(*args, **kw)) - except Exception as error: - self._handle_exception(error) - - def cursor(self, server_side=False): - if server_side: - return AsyncAdapt_aiosqlite_ss_cursor(self) - else: - return AsyncAdapt_aiosqlite_cursor(self) - - def execute(self, *args, **kw): - return self.await_(self._connection.execute(*args, **kw)) - - def rollback(self): - try: - self.await_(self._connection.rollback()) - except Exception as error: - self._handle_exception(error) - - def commit(self): - try: - self.await_(self._connection.commit()) - except Exception as error: - self._handle_exception(error) - - def close(self): - try: - self.await_(self._connection.close()) - except ValueError: - # this is undocumented for aiosqlite, that ValueError - # was raised if .close() was called more than once, which is - # both not customary for DBAPI and is also not a DBAPI.Error - # exception. This is now fixed in aiosqlite via my PR - # https://github.com/omnilib/aiosqlite/pull/238, so we can be - # assured this will not become some other kind of exception, - # since it doesn't raise anymore. - - pass - except Exception as error: - self._handle_exception(error) - - def _handle_exception(self, error): - if ( - isinstance(error, ValueError) - and error.args[0] == "no active connection" - ): - raise self.dbapi.sqlite.OperationalError( - "no active connection" - ) from error - else: - raise error - - -class AsyncAdaptFallback_aiosqlite_connection(AsyncAdapt_aiosqlite_connection): - __slots__ = () - - await_ = staticmethod(await_fallback) - - -class AsyncAdapt_aiosqlite_dbapi: - def __init__(self, aiosqlite, sqlite): - self.aiosqlite = aiosqlite - self.sqlite = sqlite - self.paramstyle = "qmark" - self._init_dbapi_attributes() - - def _init_dbapi_attributes(self): - for name in ( - "DatabaseError", - "Error", - "IntegrityError", - "NotSupportedError", - "OperationalError", - "ProgrammingError", - "sqlite_version", - "sqlite_version_info", - ): - setattr(self, name, getattr(self.aiosqlite, name)) - - for name in ("PARSE_COLNAMES", "PARSE_DECLTYPES"): - setattr(self, name, getattr(self.sqlite, name)) - - for name in ("Binary",): - setattr(self, name, getattr(self.sqlite, name)) - - def connect(self, *arg, **kw): - async_fallback = kw.pop("async_fallback", False) - - creator_fn = kw.pop("async_creator_fn", None) - if creator_fn: - connection = creator_fn(*arg, **kw) - else: - connection = self.aiosqlite.connect(*arg, **kw) - # it's a Thread. you'll thank us later - connection.daemon = True - - if util.asbool(async_fallback): - return AsyncAdaptFallback_aiosqlite_connection( - self, - await_fallback(connection), - ) - else: - return AsyncAdapt_aiosqlite_connection( - self, - await_only(connection), - ) - - -class SQLiteExecutionContext_aiosqlite(SQLiteExecutionContext): - def create_server_side_cursor(self): - return self._dbapi_connection.cursor(server_side=True) - - -class SQLiteDialect_aiosqlite(SQLiteDialect_pysqlite): - driver = "aiosqlite" - supports_statement_cache = True - - is_async = True - - supports_server_side_cursors = True - - execution_ctx_cls = SQLiteExecutionContext_aiosqlite - - @classmethod - def import_dbapi(cls): - return AsyncAdapt_aiosqlite_dbapi( - __import__("aiosqlite"), __import__("sqlite3") - ) - - @classmethod - def get_pool_class(cls, url): - if cls._is_url_file_db(url): - return pool.NullPool - else: - return pool.StaticPool - - def is_disconnect(self, e, connection, cursor): - if isinstance( - e, self.dbapi.OperationalError - ) and "no active connection" in str(e): - return True - - return super().is_disconnect(e, connection, cursor) - - def get_driver_connection(self, connection): - return connection._connection - - -dialect = SQLiteDialect_aiosqlite diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/base.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/base.py deleted file mode 100644 index 6db8214..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/base.py +++ /dev/null @@ -1,2782 +0,0 @@ -# dialects/sqlite/base.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" -.. dialect:: sqlite - :name: SQLite - :full_support: 3.36.0 - :normal_support: 3.12+ - :best_effort: 3.7.16+ - -.. _sqlite_datetime: - -Date and Time Types -------------------- - -SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does -not provide out of the box functionality for translating values between Python -`datetime` objects and a SQLite-supported format. SQLAlchemy's own -:class:`~sqlalchemy.types.DateTime` and related types provide date formatting -and parsing functionality when SQLite is used. The implementation classes are -:class:`_sqlite.DATETIME`, :class:`_sqlite.DATE` and :class:`_sqlite.TIME`. -These types represent dates and times as ISO formatted strings, which also -nicely support ordering. There's no reliance on typical "libc" internals for -these functions so historical dates are fully supported. - -Ensuring Text affinity -^^^^^^^^^^^^^^^^^^^^^^ - -The DDL rendered for these types is the standard ``DATE``, ``TIME`` -and ``DATETIME`` indicators. However, custom storage formats can also be -applied to these types. When the -storage format is detected as containing no alpha characters, the DDL for -these types is rendered as ``DATE_CHAR``, ``TIME_CHAR``, and ``DATETIME_CHAR``, -so that the column continues to have textual affinity. - -.. seealso:: - - `Type Affinity `_ - - in the SQLite documentation - -.. _sqlite_autoincrement: - -SQLite Auto Incrementing Behavior ----------------------------------- - -Background on SQLite's autoincrement is at: https://sqlite.org/autoinc.html - -Key concepts: - -* SQLite has an implicit "auto increment" feature that takes place for any - non-composite primary-key column that is specifically created using - "INTEGER PRIMARY KEY" for the type + primary key. - -* SQLite also has an explicit "AUTOINCREMENT" keyword, that is **not** - equivalent to the implicit autoincrement feature; this keyword is not - recommended for general use. SQLAlchemy does not render this keyword - unless a special SQLite-specific directive is used (see below). However, - it still requires that the column's type is named "INTEGER". - -Using the AUTOINCREMENT Keyword -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To specifically render the AUTOINCREMENT keyword on the primary key column -when rendering DDL, add the flag ``sqlite_autoincrement=True`` to the Table -construct:: - - Table('sometable', metadata, - Column('id', Integer, primary_key=True), - sqlite_autoincrement=True) - -Allowing autoincrement behavior SQLAlchemy types other than Integer/INTEGER -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -SQLite's typing model is based on naming conventions. Among other things, this -means that any type name which contains the substring ``"INT"`` will be -determined to be of "integer affinity". A type named ``"BIGINT"``, -``"SPECIAL_INT"`` or even ``"XYZINTQPR"``, will be considered by SQLite to be -of "integer" affinity. However, **the SQLite autoincrement feature, whether -implicitly or explicitly enabled, requires that the name of the column's type -is exactly the string "INTEGER"**. Therefore, if an application uses a type -like :class:`.BigInteger` for a primary key, on SQLite this type will need to -be rendered as the name ``"INTEGER"`` when emitting the initial ``CREATE -TABLE`` statement in order for the autoincrement behavior to be available. - -One approach to achieve this is to use :class:`.Integer` on SQLite -only using :meth:`.TypeEngine.with_variant`:: - - table = Table( - "my_table", metadata, - Column("id", BigInteger().with_variant(Integer, "sqlite"), primary_key=True) - ) - -Another is to use a subclass of :class:`.BigInteger` that overrides its DDL -name to be ``INTEGER`` when compiled against SQLite:: - - from sqlalchemy import BigInteger - from sqlalchemy.ext.compiler import compiles - - class SLBigInteger(BigInteger): - pass - - @compiles(SLBigInteger, 'sqlite') - def bi_c(element, compiler, **kw): - return "INTEGER" - - @compiles(SLBigInteger) - def bi_c(element, compiler, **kw): - return compiler.visit_BIGINT(element, **kw) - - - table = Table( - "my_table", metadata, - Column("id", SLBigInteger(), primary_key=True) - ) - -.. seealso:: - - :meth:`.TypeEngine.with_variant` - - :ref:`sqlalchemy.ext.compiler_toplevel` - - `Datatypes In SQLite Version 3 `_ - -.. _sqlite_concurrency: - -Database Locking Behavior / Concurrency ---------------------------------------- - -SQLite is not designed for a high level of write concurrency. The database -itself, being a file, is locked completely during write operations within -transactions, meaning exactly one "connection" (in reality a file handle) -has exclusive access to the database during this period - all other -"connections" will be blocked during this time. - -The Python DBAPI specification also calls for a connection model that is -always in a transaction; there is no ``connection.begin()`` method, -only ``connection.commit()`` and ``connection.rollback()``, upon which a -new transaction is to be begun immediately. This may seem to imply -that the SQLite driver would in theory allow only a single filehandle on a -particular database file at any time; however, there are several -factors both within SQLite itself as well as within the pysqlite driver -which loosen this restriction significantly. - -However, no matter what locking modes are used, SQLite will still always -lock the database file once a transaction is started and DML (e.g. INSERT, -UPDATE, DELETE) has at least been emitted, and this will block -other transactions at least at the point that they also attempt to emit DML. -By default, the length of time on this block is very short before it times out -with an error. - -This behavior becomes more critical when used in conjunction with the -SQLAlchemy ORM. SQLAlchemy's :class:`.Session` object by default runs -within a transaction, and with its autoflush model, may emit DML preceding -any SELECT statement. This may lead to a SQLite database that locks -more quickly than is expected. The locking mode of SQLite and the pysqlite -driver can be manipulated to some degree, however it should be noted that -achieving a high degree of write-concurrency with SQLite is a losing battle. - -For more information on SQLite's lack of write concurrency by design, please -see -`Situations Where Another RDBMS May Work Better - High Concurrency -`_ near the bottom of the page. - -The following subsections introduce areas that are impacted by SQLite's -file-based architecture and additionally will usually require workarounds to -work when using the pysqlite driver. - -.. _sqlite_isolation_level: - -Transaction Isolation Level / Autocommit ----------------------------------------- - -SQLite supports "transaction isolation" in a non-standard way, along two -axes. One is that of the -`PRAGMA read_uncommitted `_ -instruction. This setting can essentially switch SQLite between its -default mode of ``SERIALIZABLE`` isolation, and a "dirty read" isolation -mode normally referred to as ``READ UNCOMMITTED``. - -SQLAlchemy ties into this PRAGMA statement using the -:paramref:`_sa.create_engine.isolation_level` parameter of -:func:`_sa.create_engine`. -Valid values for this parameter when used with SQLite are ``"SERIALIZABLE"`` -and ``"READ UNCOMMITTED"`` corresponding to a value of 0 and 1, respectively. -SQLite defaults to ``SERIALIZABLE``, however its behavior is impacted by -the pysqlite driver's default behavior. - -When using the pysqlite driver, the ``"AUTOCOMMIT"`` isolation level is also -available, which will alter the pysqlite connection using the ``.isolation_level`` -attribute on the DBAPI connection and set it to None for the duration -of the setting. - -.. versionadded:: 1.3.16 added support for SQLite AUTOCOMMIT isolation level - when using the pysqlite / sqlite3 SQLite driver. - - -The other axis along which SQLite's transactional locking is impacted is -via the nature of the ``BEGIN`` statement used. The three varieties -are "deferred", "immediate", and "exclusive", as described at -`BEGIN TRANSACTION `_. A straight -``BEGIN`` statement uses the "deferred" mode, where the database file is -not locked until the first read or write operation, and read access remains -open to other transactions until the first write operation. But again, -it is critical to note that the pysqlite driver interferes with this behavior -by *not even emitting BEGIN* until the first write operation. - -.. warning:: - - SQLite's transactional scope is impacted by unresolved - issues in the pysqlite driver, which defers BEGIN statements to a greater - degree than is often feasible. See the section :ref:`pysqlite_serializable` - or :ref:`aiosqlite_serializable` for techniques to work around this behavior. - -.. seealso:: - - :ref:`dbapi_autocommit` - -INSERT/UPDATE/DELETE...RETURNING ---------------------------------- - -The SQLite dialect supports SQLite 3.35's ``INSERT|UPDATE|DELETE..RETURNING`` -syntax. ``INSERT..RETURNING`` may be used -automatically in some cases in order to fetch newly generated identifiers in -place of the traditional approach of using ``cursor.lastrowid``, however -``cursor.lastrowid`` is currently still preferred for simple single-statement -cases for its better performance. - -To specify an explicit ``RETURNING`` clause, use the -:meth:`._UpdateBase.returning` method on a per-statement basis:: - - # INSERT..RETURNING - result = connection.execute( - table.insert(). - values(name='foo'). - returning(table.c.col1, table.c.col2) - ) - print(result.all()) - - # UPDATE..RETURNING - result = connection.execute( - table.update(). - where(table.c.name=='foo'). - values(name='bar'). - returning(table.c.col1, table.c.col2) - ) - print(result.all()) - - # DELETE..RETURNING - result = connection.execute( - table.delete(). - where(table.c.name=='foo'). - returning(table.c.col1, table.c.col2) - ) - print(result.all()) - -.. versionadded:: 2.0 Added support for SQLite RETURNING - -SAVEPOINT Support ----------------------------- - -SQLite supports SAVEPOINTs, which only function once a transaction is -begun. SQLAlchemy's SAVEPOINT support is available using the -:meth:`_engine.Connection.begin_nested` method at the Core level, and -:meth:`.Session.begin_nested` at the ORM level. However, SAVEPOINTs -won't work at all with pysqlite unless workarounds are taken. - -.. warning:: - - SQLite's SAVEPOINT feature is impacted by unresolved - issues in the pysqlite and aiosqlite drivers, which defer BEGIN statements - to a greater degree than is often feasible. See the sections - :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable` - for techniques to work around this behavior. - -Transactional DDL ----------------------------- - -The SQLite database supports transactional :term:`DDL` as well. -In this case, the pysqlite driver is not only failing to start transactions, -it also is ending any existing transaction when DDL is detected, so again, -workarounds are required. - -.. warning:: - - SQLite's transactional DDL is impacted by unresolved issues - in the pysqlite driver, which fails to emit BEGIN and additionally - forces a COMMIT to cancel any transaction when DDL is encountered. - See the section :ref:`pysqlite_serializable` - for techniques to work around this behavior. - -.. _sqlite_foreign_keys: - -Foreign Key Support -------------------- - -SQLite supports FOREIGN KEY syntax when emitting CREATE statements for tables, -however by default these constraints have no effect on the operation of the -table. - -Constraint checking on SQLite has three prerequisites: - -* At least version 3.6.19 of SQLite must be in use -* The SQLite library must be compiled *without* the SQLITE_OMIT_FOREIGN_KEY - or SQLITE_OMIT_TRIGGER symbols enabled. -* The ``PRAGMA foreign_keys = ON`` statement must be emitted on all - connections before use -- including the initial call to - :meth:`sqlalchemy.schema.MetaData.create_all`. - -SQLAlchemy allows for the ``PRAGMA`` statement to be emitted automatically for -new connections through the usage of events:: - - from sqlalchemy.engine import Engine - from sqlalchemy import event - - @event.listens_for(Engine, "connect") - def set_sqlite_pragma(dbapi_connection, connection_record): - cursor = dbapi_connection.cursor() - cursor.execute("PRAGMA foreign_keys=ON") - cursor.close() - -.. warning:: - - When SQLite foreign keys are enabled, it is **not possible** - to emit CREATE or DROP statements for tables that contain - mutually-dependent foreign key constraints; - to emit the DDL for these tables requires that ALTER TABLE be used to - create or drop these constraints separately, for which SQLite has - no support. - -.. seealso:: - - `SQLite Foreign Key Support `_ - - on the SQLite web site. - - :ref:`event_toplevel` - SQLAlchemy event API. - - :ref:`use_alter` - more information on SQLAlchemy's facilities for handling - mutually-dependent foreign key constraints. - -.. _sqlite_on_conflict_ddl: - -ON CONFLICT support for constraints ------------------------------------ - -.. seealso:: This section describes the :term:`DDL` version of "ON CONFLICT" for - SQLite, which occurs within a CREATE TABLE statement. For "ON CONFLICT" as - applied to an INSERT statement, see :ref:`sqlite_on_conflict_insert`. - -SQLite supports a non-standard DDL clause known as ON CONFLICT which can be applied -to primary key, unique, check, and not null constraints. In DDL, it is -rendered either within the "CONSTRAINT" clause or within the column definition -itself depending on the location of the target constraint. To render this -clause within DDL, the extension parameter ``sqlite_on_conflict`` can be -specified with a string conflict resolution algorithm within the -:class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`, -:class:`.CheckConstraint` objects. Within the :class:`_schema.Column` object, -there -are individual parameters ``sqlite_on_conflict_not_null``, -``sqlite_on_conflict_primary_key``, ``sqlite_on_conflict_unique`` which each -correspond to the three types of relevant constraint types that can be -indicated from a :class:`_schema.Column` object. - -.. seealso:: - - `ON CONFLICT `_ - in the SQLite - documentation - -.. versionadded:: 1.3 - - -The ``sqlite_on_conflict`` parameters accept a string argument which is just -the resolution name to be chosen, which on SQLite can be one of ROLLBACK, -ABORT, FAIL, IGNORE, and REPLACE. For example, to add a UNIQUE constraint -that specifies the IGNORE algorithm:: - - some_table = Table( - 'some_table', metadata, - Column('id', Integer, primary_key=True), - Column('data', Integer), - UniqueConstraint('id', 'data', sqlite_on_conflict='IGNORE') - ) - -The above renders CREATE TABLE DDL as:: - - CREATE TABLE some_table ( - id INTEGER NOT NULL, - data INTEGER, - PRIMARY KEY (id), - UNIQUE (id, data) ON CONFLICT IGNORE - ) - - -When using the :paramref:`_schema.Column.unique` -flag to add a UNIQUE constraint -to a single column, the ``sqlite_on_conflict_unique`` parameter can -be added to the :class:`_schema.Column` as well, which will be added to the -UNIQUE constraint in the DDL:: - - some_table = Table( - 'some_table', metadata, - Column('id', Integer, primary_key=True), - Column('data', Integer, unique=True, - sqlite_on_conflict_unique='IGNORE') - ) - -rendering:: - - CREATE TABLE some_table ( - id INTEGER NOT NULL, - data INTEGER, - PRIMARY KEY (id), - UNIQUE (data) ON CONFLICT IGNORE - ) - -To apply the FAIL algorithm for a NOT NULL constraint, -``sqlite_on_conflict_not_null`` is used:: - - some_table = Table( - 'some_table', metadata, - Column('id', Integer, primary_key=True), - Column('data', Integer, nullable=False, - sqlite_on_conflict_not_null='FAIL') - ) - -this renders the column inline ON CONFLICT phrase:: - - CREATE TABLE some_table ( - id INTEGER NOT NULL, - data INTEGER NOT NULL ON CONFLICT FAIL, - PRIMARY KEY (id) - ) - - -Similarly, for an inline primary key, use ``sqlite_on_conflict_primary_key``:: - - some_table = Table( - 'some_table', metadata, - Column('id', Integer, primary_key=True, - sqlite_on_conflict_primary_key='FAIL') - ) - -SQLAlchemy renders the PRIMARY KEY constraint separately, so the conflict -resolution algorithm is applied to the constraint itself:: - - CREATE TABLE some_table ( - id INTEGER NOT NULL, - PRIMARY KEY (id) ON CONFLICT FAIL - ) - -.. _sqlite_on_conflict_insert: - -INSERT...ON CONFLICT (Upsert) ------------------------------------ - -.. seealso:: This section describes the :term:`DML` version of "ON CONFLICT" for - SQLite, which occurs within an INSERT statement. For "ON CONFLICT" as - applied to a CREATE TABLE statement, see :ref:`sqlite_on_conflict_ddl`. - -From version 3.24.0 onwards, SQLite supports "upserts" (update or insert) -of rows into a table via the ``ON CONFLICT`` clause of the ``INSERT`` -statement. A candidate row will only be inserted if that row does not violate -any unique or primary key constraints. In the case of a unique constraint violation, a -secondary action can occur which can be either "DO UPDATE", indicating that -the data in the target row should be updated, or "DO NOTHING", which indicates -to silently skip this row. - -Conflicts are determined using columns that are part of existing unique -constraints and indexes. These constraints are identified by stating the -columns and conditions that comprise the indexes. - -SQLAlchemy provides ``ON CONFLICT`` support via the SQLite-specific -:func:`_sqlite.insert()` function, which provides -the generative methods :meth:`_sqlite.Insert.on_conflict_do_update` -and :meth:`_sqlite.Insert.on_conflict_do_nothing`: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy.dialects.sqlite import insert - - >>> insert_stmt = insert(my_table).values( - ... id='some_existing_id', - ... data='inserted value') - - >>> do_update_stmt = insert_stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value') - ... ) - - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET data = ?{stop} - - >>> do_nothing_stmt = insert_stmt.on_conflict_do_nothing( - ... index_elements=['id'] - ... ) - - >>> print(do_nothing_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) - ON CONFLICT (id) DO NOTHING - -.. versionadded:: 1.4 - -.. seealso:: - - `Upsert - `_ - - in the SQLite documentation. - - -Specifying the Target -^^^^^^^^^^^^^^^^^^^^^ - -Both methods supply the "target" of the conflict using column inference: - -* The :paramref:`_sqlite.Insert.on_conflict_do_update.index_elements` argument - specifies a sequence containing string column names, :class:`_schema.Column` - objects, and/or SQL expression elements, which would identify a unique index - or unique constraint. - -* When using :paramref:`_sqlite.Insert.on_conflict_do_update.index_elements` - to infer an index, a partial index can be inferred by also specifying the - :paramref:`_sqlite.Insert.on_conflict_do_update.index_where` parameter: - - .. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(user_email='a@b.com', data='inserted data') - - >>> do_update_stmt = stmt.on_conflict_do_update( - ... index_elements=[my_table.c.user_email], - ... index_where=my_table.c.user_email.like('%@gmail.com'), - ... set_=dict(data=stmt.excluded.data) - ... ) - - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (data, user_email) VALUES (?, ?) - ON CONFLICT (user_email) - WHERE user_email LIKE '%@gmail.com' - DO UPDATE SET data = excluded.data - -The SET Clause -^^^^^^^^^^^^^^^ - -``ON CONFLICT...DO UPDATE`` is used to perform an update of the already -existing row, using any combination of new values as well as values -from the proposed insertion. These values are specified using the -:paramref:`_sqlite.Insert.on_conflict_do_update.set_` parameter. This -parameter accepts a dictionary which consists of direct values -for UPDATE: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - - >>> do_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value') - ... ) - - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET data = ? - -.. warning:: - - The :meth:`_sqlite.Insert.on_conflict_do_update` method does **not** take - into account Python-side default UPDATE values or generation functions, - e.g. those specified using :paramref:`_schema.Column.onupdate`. These - values will not be exercised for an ON CONFLICT style of UPDATE, unless - they are manually specified in the - :paramref:`_sqlite.Insert.on_conflict_do_update.set_` dictionary. - -Updating using the Excluded INSERT Values -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to refer to the proposed insertion row, the special alias -:attr:`~.sqlite.Insert.excluded` is available as an attribute on -the :class:`_sqlite.Insert` object; this object creates an "excluded." prefix -on a column, that informs the DO UPDATE to update the row with the value that -would have been inserted had the constraint not failed: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values( - ... id='some_id', - ... data='inserted value', - ... author='jlh' - ... ) - - >>> do_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value', author=stmt.excluded.author) - ... ) - - >>> print(do_update_stmt) - {printsql}INSERT INTO my_table (id, data, author) VALUES (?, ?, ?) - ON CONFLICT (id) DO UPDATE SET data = ?, author = excluded.author - -Additional WHERE Criteria -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The :meth:`_sqlite.Insert.on_conflict_do_update` method also accepts -a WHERE clause using the :paramref:`_sqlite.Insert.on_conflict_do_update.where` -parameter, which will limit those rows which receive an UPDATE: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values( - ... id='some_id', - ... data='inserted value', - ... author='jlh' - ... ) - - >>> on_update_stmt = stmt.on_conflict_do_update( - ... index_elements=['id'], - ... set_=dict(data='updated value', author=stmt.excluded.author), - ... where=(my_table.c.status == 2) - ... ) - >>> print(on_update_stmt) - {printsql}INSERT INTO my_table (id, data, author) VALUES (?, ?, ?) - ON CONFLICT (id) DO UPDATE SET data = ?, author = excluded.author - WHERE my_table.status = ? - - -Skipping Rows with DO NOTHING -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``ON CONFLICT`` may be used to skip inserting a row entirely -if any conflict with a unique constraint occurs; below this is illustrated -using the :meth:`_sqlite.Insert.on_conflict_do_nothing` method: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - >>> stmt = stmt.on_conflict_do_nothing(index_elements=['id']) - >>> print(stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT (id) DO NOTHING - - -If ``DO NOTHING`` is used without specifying any columns or constraint, -it has the effect of skipping the INSERT for any unique violation which -occurs: - -.. sourcecode:: pycon+sql - - >>> stmt = insert(my_table).values(id='some_id', data='inserted value') - >>> stmt = stmt.on_conflict_do_nothing() - >>> print(stmt) - {printsql}INSERT INTO my_table (id, data) VALUES (?, ?) ON CONFLICT DO NOTHING - -.. _sqlite_type_reflection: - -Type Reflection ---------------- - -SQLite types are unlike those of most other database backends, in that -the string name of the type usually does not correspond to a "type" in a -one-to-one fashion. Instead, SQLite links per-column typing behavior -to one of five so-called "type affinities" based on a string matching -pattern for the type. - -SQLAlchemy's reflection process, when inspecting types, uses a simple -lookup table to link the keywords returned to provided SQLAlchemy types. -This lookup table is present within the SQLite dialect as it is for all -other dialects. However, the SQLite dialect has a different "fallback" -routine for when a particular type name is not located in the lookup map; -it instead implements the SQLite "type affinity" scheme located at -https://www.sqlite.org/datatype3.html section 2.1. - -The provided typemap will make direct associations from an exact string -name match for the following types: - -:class:`_types.BIGINT`, :class:`_types.BLOB`, -:class:`_types.BOOLEAN`, :class:`_types.BOOLEAN`, -:class:`_types.CHAR`, :class:`_types.DATE`, -:class:`_types.DATETIME`, :class:`_types.FLOAT`, -:class:`_types.DECIMAL`, :class:`_types.FLOAT`, -:class:`_types.INTEGER`, :class:`_types.INTEGER`, -:class:`_types.NUMERIC`, :class:`_types.REAL`, -:class:`_types.SMALLINT`, :class:`_types.TEXT`, -:class:`_types.TIME`, :class:`_types.TIMESTAMP`, -:class:`_types.VARCHAR`, :class:`_types.NVARCHAR`, -:class:`_types.NCHAR` - -When a type name does not match one of the above types, the "type affinity" -lookup is used instead: - -* :class:`_types.INTEGER` is returned if the type name includes the - string ``INT`` -* :class:`_types.TEXT` is returned if the type name includes the - string ``CHAR``, ``CLOB`` or ``TEXT`` -* :class:`_types.NullType` is returned if the type name includes the - string ``BLOB`` -* :class:`_types.REAL` is returned if the type name includes the string - ``REAL``, ``FLOA`` or ``DOUB``. -* Otherwise, the :class:`_types.NUMERIC` type is used. - -.. _sqlite_partial_index: - -Partial Indexes ---------------- - -A partial index, e.g. one which uses a WHERE clause, can be specified -with the DDL system using the argument ``sqlite_where``:: - - tbl = Table('testtbl', m, Column('data', Integer)) - idx = Index('test_idx1', tbl.c.data, - sqlite_where=and_(tbl.c.data > 5, tbl.c.data < 10)) - -The index will be rendered at create time as:: - - CREATE INDEX test_idx1 ON testtbl (data) - WHERE data > 5 AND data < 10 - -.. _sqlite_dotted_column_names: - -Dotted Column Names -------------------- - -Using table or column names that explicitly have periods in them is -**not recommended**. While this is generally a bad idea for relational -databases in general, as the dot is a syntactically significant character, -the SQLite driver up until version **3.10.0** of SQLite has a bug which -requires that SQLAlchemy filter out these dots in result sets. - -The bug, entirely outside of SQLAlchemy, can be illustrated thusly:: - - import sqlite3 - - assert sqlite3.sqlite_version_info < (3, 10, 0), "bug is fixed in this version" - - conn = sqlite3.connect(":memory:") - cursor = conn.cursor() - - cursor.execute("create table x (a integer, b integer)") - cursor.execute("insert into x (a, b) values (1, 1)") - cursor.execute("insert into x (a, b) values (2, 2)") - - cursor.execute("select x.a, x.b from x") - assert [c[0] for c in cursor.description] == ['a', 'b'] - - cursor.execute(''' - select x.a, x.b from x where a=1 - union - select x.a, x.b from x where a=2 - ''') - assert [c[0] for c in cursor.description] == ['a', 'b'], \ - [c[0] for c in cursor.description] - -The second assertion fails:: - - Traceback (most recent call last): - File "test.py", line 19, in - [c[0] for c in cursor.description] - AssertionError: ['x.a', 'x.b'] - -Where above, the driver incorrectly reports the names of the columns -including the name of the table, which is entirely inconsistent vs. -when the UNION is not present. - -SQLAlchemy relies upon column names being predictable in how they match -to the original statement, so the SQLAlchemy dialect has no choice but -to filter these out:: - - - from sqlalchemy import create_engine - - eng = create_engine("sqlite://") - conn = eng.connect() - - conn.exec_driver_sql("create table x (a integer, b integer)") - conn.exec_driver_sql("insert into x (a, b) values (1, 1)") - conn.exec_driver_sql("insert into x (a, b) values (2, 2)") - - result = conn.exec_driver_sql("select x.a, x.b from x") - assert result.keys() == ["a", "b"] - - result = conn.exec_driver_sql(''' - select x.a, x.b from x where a=1 - union - select x.a, x.b from x where a=2 - ''') - assert result.keys() == ["a", "b"] - -Note that above, even though SQLAlchemy filters out the dots, *both -names are still addressable*:: - - >>> row = result.first() - >>> row["a"] - 1 - >>> row["x.a"] - 1 - >>> row["b"] - 1 - >>> row["x.b"] - 1 - -Therefore, the workaround applied by SQLAlchemy only impacts -:meth:`_engine.CursorResult.keys` and :meth:`.Row.keys()` in the public API. In -the very specific case where an application is forced to use column names that -contain dots, and the functionality of :meth:`_engine.CursorResult.keys` and -:meth:`.Row.keys()` is required to return these dotted names unmodified, -the ``sqlite_raw_colnames`` execution option may be provided, either on a -per-:class:`_engine.Connection` basis:: - - result = conn.execution_options(sqlite_raw_colnames=True).exec_driver_sql(''' - select x.a, x.b from x where a=1 - union - select x.a, x.b from x where a=2 - ''') - assert result.keys() == ["x.a", "x.b"] - -or on a per-:class:`_engine.Engine` basis:: - - engine = create_engine("sqlite://", execution_options={"sqlite_raw_colnames": True}) - -When using the per-:class:`_engine.Engine` execution option, note that -**Core and ORM queries that use UNION may not function properly**. - -SQLite-specific table options ------------------------------ - -One option for CREATE TABLE is supported directly by the SQLite -dialect in conjunction with the :class:`_schema.Table` construct: - -* ``WITHOUT ROWID``:: - - Table("some_table", metadata, ..., sqlite_with_rowid=False) - -.. seealso:: - - `SQLite CREATE TABLE options - `_ - - -.. _sqlite_include_internal: - -Reflecting internal schema tables ----------------------------------- - -Reflection methods that return lists of tables will omit so-called -"SQLite internal schema object" names, which are considered by SQLite -as any object name that is prefixed with ``sqlite_``. An example of -such an object is the ``sqlite_sequence`` table that's generated when -the ``AUTOINCREMENT`` column parameter is used. In order to return -these objects, the parameter ``sqlite_include_internal=True`` may be -passed to methods such as :meth:`_schema.MetaData.reflect` or -:meth:`.Inspector.get_table_names`. - -.. versionadded:: 2.0 Added the ``sqlite_include_internal=True`` parameter. - Previously, these tables were not ignored by SQLAlchemy reflection - methods. - -.. note:: - - The ``sqlite_include_internal`` parameter does not refer to the - "system" tables that are present in schemas such as ``sqlite_master``. - -.. seealso:: - - `SQLite Internal Schema Objects `_ - in the SQLite - documentation. - -""" # noqa -from __future__ import annotations - -import datetime -import numbers -import re -from typing import Optional - -from .json import JSON -from .json import JSONIndexType -from .json import JSONPathType -from ... import exc -from ... import schema as sa_schema -from ... import sql -from ... import text -from ... import types as sqltypes -from ... import util -from ...engine import default -from ...engine import processors -from ...engine import reflection -from ...engine.reflection import ReflectionDefaults -from ...sql import coercions -from ...sql import ColumnElement -from ...sql import compiler -from ...sql import elements -from ...sql import roles -from ...sql import schema -from ...types import BLOB # noqa -from ...types import BOOLEAN # noqa -from ...types import CHAR # noqa -from ...types import DECIMAL # noqa -from ...types import FLOAT # noqa -from ...types import INTEGER # noqa -from ...types import NUMERIC # noqa -from ...types import REAL # noqa -from ...types import SMALLINT # noqa -from ...types import TEXT # noqa -from ...types import TIMESTAMP # noqa -from ...types import VARCHAR # noqa - - -class _SQliteJson(JSON): - def result_processor(self, dialect, coltype): - default_processor = super().result_processor(dialect, coltype) - - def process(value): - try: - return default_processor(value) - except TypeError: - if isinstance(value, numbers.Number): - return value - else: - raise - - return process - - -class _DateTimeMixin: - _reg = None - _storage_format = None - - def __init__(self, storage_format=None, regexp=None, **kw): - super().__init__(**kw) - if regexp is not None: - self._reg = re.compile(regexp) - if storage_format is not None: - self._storage_format = storage_format - - @property - def format_is_text_affinity(self): - """return True if the storage format will automatically imply - a TEXT affinity. - - If the storage format contains no non-numeric characters, - it will imply a NUMERIC storage format on SQLite; in this case, - the type will generate its DDL as DATE_CHAR, DATETIME_CHAR, - TIME_CHAR. - - """ - spec = self._storage_format % { - "year": 0, - "month": 0, - "day": 0, - "hour": 0, - "minute": 0, - "second": 0, - "microsecond": 0, - } - return bool(re.search(r"[^0-9]", spec)) - - def adapt(self, cls, **kw): - if issubclass(cls, _DateTimeMixin): - if self._storage_format: - kw["storage_format"] = self._storage_format - if self._reg: - kw["regexp"] = self._reg - return super().adapt(cls, **kw) - - def literal_processor(self, dialect): - bp = self.bind_processor(dialect) - - def process(value): - return "'%s'" % bp(value) - - return process - - -class DATETIME(_DateTimeMixin, sqltypes.DateTime): - r"""Represent a Python datetime object in SQLite using a string. - - The default string storage format is:: - - "%(year)04d-%(month)02d-%(day)02d %(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d" - - e.g.:: - - 2021-03-15 12:05:57.105542 - - The incoming storage format is by default parsed using the - Python ``datetime.fromisoformat()`` function. - - .. versionchanged:: 2.0 ``datetime.fromisoformat()`` is used for default - datetime string parsing. - - The storage format can be customized to some degree using the - ``storage_format`` and ``regexp`` parameters, such as:: - - import re - from sqlalchemy.dialects.sqlite import DATETIME - - dt = DATETIME(storage_format="%(year)04d/%(month)02d/%(day)02d " - "%(hour)02d:%(minute)02d:%(second)02d", - regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)" - ) - - :param storage_format: format string which will be applied to the dict - with keys year, month, day, hour, minute, second, and microsecond. - - :param regexp: regular expression which will be applied to incoming result - rows, replacing the use of ``datetime.fromisoformat()`` to parse incoming - strings. If the regexp contains named groups, the resulting match dict is - applied to the Python datetime() constructor as keyword arguments. - Otherwise, if positional groups are used, the datetime() constructor - is called with positional arguments via - ``*map(int, match_obj.groups(0))``. - - """ # noqa - - _storage_format = ( - "%(year)04d-%(month)02d-%(day)02d " - "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d" - ) - - def __init__(self, *args, **kwargs): - truncate_microseconds = kwargs.pop("truncate_microseconds", False) - super().__init__(*args, **kwargs) - if truncate_microseconds: - assert "storage_format" not in kwargs, ( - "You can specify only " - "one of truncate_microseconds or storage_format." - ) - assert "regexp" not in kwargs, ( - "You can specify only one of " - "truncate_microseconds or regexp." - ) - self._storage_format = ( - "%(year)04d-%(month)02d-%(day)02d " - "%(hour)02d:%(minute)02d:%(second)02d" - ) - - def bind_processor(self, dialect): - datetime_datetime = datetime.datetime - datetime_date = datetime.date - format_ = self._storage_format - - def process(value): - if value is None: - return None - elif isinstance(value, datetime_datetime): - return format_ % { - "year": value.year, - "month": value.month, - "day": value.day, - "hour": value.hour, - "minute": value.minute, - "second": value.second, - "microsecond": value.microsecond, - } - elif isinstance(value, datetime_date): - return format_ % { - "year": value.year, - "month": value.month, - "day": value.day, - "hour": 0, - "minute": 0, - "second": 0, - "microsecond": 0, - } - else: - raise TypeError( - "SQLite DateTime type only accepts Python " - "datetime and date objects as input." - ) - - return process - - def result_processor(self, dialect, coltype): - if self._reg: - return processors.str_to_datetime_processor_factory( - self._reg, datetime.datetime - ) - else: - return processors.str_to_datetime - - -class DATE(_DateTimeMixin, sqltypes.Date): - r"""Represent a Python date object in SQLite using a string. - - The default string storage format is:: - - "%(year)04d-%(month)02d-%(day)02d" - - e.g.:: - - 2011-03-15 - - The incoming storage format is by default parsed using the - Python ``date.fromisoformat()`` function. - - .. versionchanged:: 2.0 ``date.fromisoformat()`` is used for default - date string parsing. - - - The storage format can be customized to some degree using the - ``storage_format`` and ``regexp`` parameters, such as:: - - import re - from sqlalchemy.dialects.sqlite import DATE - - d = DATE( - storage_format="%(month)02d/%(day)02d/%(year)04d", - regexp=re.compile("(?P\d+)/(?P\d+)/(?P\d+)") - ) - - :param storage_format: format string which will be applied to the - dict with keys year, month, and day. - - :param regexp: regular expression which will be applied to - incoming result rows, replacing the use of ``date.fromisoformat()`` to - parse incoming strings. If the regexp contains named groups, the resulting - match dict is applied to the Python date() constructor as keyword - arguments. Otherwise, if positional groups are used, the date() - constructor is called with positional arguments via - ``*map(int, match_obj.groups(0))``. - - """ - - _storage_format = "%(year)04d-%(month)02d-%(day)02d" - - def bind_processor(self, dialect): - datetime_date = datetime.date - format_ = self._storage_format - - def process(value): - if value is None: - return None - elif isinstance(value, datetime_date): - return format_ % { - "year": value.year, - "month": value.month, - "day": value.day, - } - else: - raise TypeError( - "SQLite Date type only accepts Python " - "date objects as input." - ) - - return process - - def result_processor(self, dialect, coltype): - if self._reg: - return processors.str_to_datetime_processor_factory( - self._reg, datetime.date - ) - else: - return processors.str_to_date - - -class TIME(_DateTimeMixin, sqltypes.Time): - r"""Represent a Python time object in SQLite using a string. - - The default string storage format is:: - - "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d" - - e.g.:: - - 12:05:57.10558 - - The incoming storage format is by default parsed using the - Python ``time.fromisoformat()`` function. - - .. versionchanged:: 2.0 ``time.fromisoformat()`` is used for default - time string parsing. - - The storage format can be customized to some degree using the - ``storage_format`` and ``regexp`` parameters, such as:: - - import re - from sqlalchemy.dialects.sqlite import TIME - - t = TIME(storage_format="%(hour)02d-%(minute)02d-" - "%(second)02d-%(microsecond)06d", - regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?") - ) - - :param storage_format: format string which will be applied to the dict - with keys hour, minute, second, and microsecond. - - :param regexp: regular expression which will be applied to incoming result - rows, replacing the use of ``datetime.fromisoformat()`` to parse incoming - strings. If the regexp contains named groups, the resulting match dict is - applied to the Python time() constructor as keyword arguments. Otherwise, - if positional groups are used, the time() constructor is called with - positional arguments via ``*map(int, match_obj.groups(0))``. - - """ - - _storage_format = "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d" - - def __init__(self, *args, **kwargs): - truncate_microseconds = kwargs.pop("truncate_microseconds", False) - super().__init__(*args, **kwargs) - if truncate_microseconds: - assert "storage_format" not in kwargs, ( - "You can specify only " - "one of truncate_microseconds or storage_format." - ) - assert "regexp" not in kwargs, ( - "You can specify only one of " - "truncate_microseconds or regexp." - ) - self._storage_format = "%(hour)02d:%(minute)02d:%(second)02d" - - def bind_processor(self, dialect): - datetime_time = datetime.time - format_ = self._storage_format - - def process(value): - if value is None: - return None - elif isinstance(value, datetime_time): - return format_ % { - "hour": value.hour, - "minute": value.minute, - "second": value.second, - "microsecond": value.microsecond, - } - else: - raise TypeError( - "SQLite Time type only accepts Python " - "time objects as input." - ) - - return process - - def result_processor(self, dialect, coltype): - if self._reg: - return processors.str_to_datetime_processor_factory( - self._reg, datetime.time - ) - else: - return processors.str_to_time - - -colspecs = { - sqltypes.Date: DATE, - sqltypes.DateTime: DATETIME, - sqltypes.JSON: _SQliteJson, - sqltypes.JSON.JSONIndexType: JSONIndexType, - sqltypes.JSON.JSONPathType: JSONPathType, - sqltypes.Time: TIME, -} - -ischema_names = { - "BIGINT": sqltypes.BIGINT, - "BLOB": sqltypes.BLOB, - "BOOL": sqltypes.BOOLEAN, - "BOOLEAN": sqltypes.BOOLEAN, - "CHAR": sqltypes.CHAR, - "DATE": sqltypes.DATE, - "DATE_CHAR": sqltypes.DATE, - "DATETIME": sqltypes.DATETIME, - "DATETIME_CHAR": sqltypes.DATETIME, - "DOUBLE": sqltypes.DOUBLE, - "DECIMAL": sqltypes.DECIMAL, - "FLOAT": sqltypes.FLOAT, - "INT": sqltypes.INTEGER, - "INTEGER": sqltypes.INTEGER, - "JSON": JSON, - "NUMERIC": sqltypes.NUMERIC, - "REAL": sqltypes.REAL, - "SMALLINT": sqltypes.SMALLINT, - "TEXT": sqltypes.TEXT, - "TIME": sqltypes.TIME, - "TIME_CHAR": sqltypes.TIME, - "TIMESTAMP": sqltypes.TIMESTAMP, - "VARCHAR": sqltypes.VARCHAR, - "NVARCHAR": sqltypes.NVARCHAR, - "NCHAR": sqltypes.NCHAR, -} - - -class SQLiteCompiler(compiler.SQLCompiler): - extract_map = util.update_copy( - compiler.SQLCompiler.extract_map, - { - "month": "%m", - "day": "%d", - "year": "%Y", - "second": "%S", - "hour": "%H", - "doy": "%j", - "minute": "%M", - "epoch": "%s", - "dow": "%w", - "week": "%W", - }, - ) - - def visit_truediv_binary(self, binary, operator, **kw): - return ( - self.process(binary.left, **kw) - + " / " - + "(%s + 0.0)" % self.process(binary.right, **kw) - ) - - def visit_now_func(self, fn, **kw): - return "CURRENT_TIMESTAMP" - - def visit_localtimestamp_func(self, func, **kw): - return 'DATETIME(CURRENT_TIMESTAMP, "localtime")' - - def visit_true(self, expr, **kw): - return "1" - - def visit_false(self, expr, **kw): - return "0" - - def visit_char_length_func(self, fn, **kw): - return "length%s" % self.function_argspec(fn) - - def visit_aggregate_strings_func(self, fn, **kw): - return "group_concat%s" % self.function_argspec(fn) - - def visit_cast(self, cast, **kwargs): - if self.dialect.supports_cast: - return super().visit_cast(cast, **kwargs) - else: - return self.process(cast.clause, **kwargs) - - def visit_extract(self, extract, **kw): - try: - return "CAST(STRFTIME('%s', %s) AS INTEGER)" % ( - self.extract_map[extract.field], - self.process(extract.expr, **kw), - ) - except KeyError as err: - raise exc.CompileError( - "%s is not a valid extract argument." % extract.field - ) from err - - def returning_clause( - self, - stmt, - returning_cols, - *, - populate_result_map, - **kw, - ): - kw["include_table"] = False - return super().returning_clause( - stmt, returning_cols, populate_result_map=populate_result_map, **kw - ) - - def limit_clause(self, select, **kw): - text = "" - if select._limit_clause is not None: - text += "\n LIMIT " + self.process(select._limit_clause, **kw) - if select._offset_clause is not None: - if select._limit_clause is None: - text += "\n LIMIT " + self.process(sql.literal(-1)) - text += " OFFSET " + self.process(select._offset_clause, **kw) - else: - text += " OFFSET " + self.process(sql.literal(0), **kw) - return text - - def for_update_clause(self, select, **kw): - # sqlite has no "FOR UPDATE" AFAICT - return "" - - def update_from_clause( - self, update_stmt, from_table, extra_froms, from_hints, **kw - ): - kw["asfrom"] = True - return "FROM " + ", ".join( - t._compiler_dispatch(self, fromhints=from_hints, **kw) - for t in extra_froms - ) - - def visit_is_distinct_from_binary(self, binary, operator, **kw): - return "%s IS NOT %s" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_is_not_distinct_from_binary(self, binary, operator, **kw): - return "%s IS %s" % ( - self.process(binary.left), - self.process(binary.right), - ) - - def visit_json_getitem_op_binary(self, binary, operator, **kw): - if binary.type._type_affinity is sqltypes.JSON: - expr = "JSON_QUOTE(JSON_EXTRACT(%s, %s))" - else: - expr = "JSON_EXTRACT(%s, %s)" - - return expr % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_json_path_getitem_op_binary(self, binary, operator, **kw): - if binary.type._type_affinity is sqltypes.JSON: - expr = "JSON_QUOTE(JSON_EXTRACT(%s, %s))" - else: - expr = "JSON_EXTRACT(%s, %s)" - - return expr % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw), - ) - - def visit_empty_set_op_expr(self, type_, expand_op, **kw): - # slightly old SQLite versions don't seem to be able to handle - # the empty set impl - return self.visit_empty_set_expr(type_) - - def visit_empty_set_expr(self, element_types, **kw): - return "SELECT %s FROM (SELECT %s) WHERE 1!=1" % ( - ", ".join("1" for type_ in element_types or [INTEGER()]), - ", ".join("1" for type_ in element_types or [INTEGER()]), - ) - - def visit_regexp_match_op_binary(self, binary, operator, **kw): - return self._generate_generic_binary(binary, " REGEXP ", **kw) - - def visit_not_regexp_match_op_binary(self, binary, operator, **kw): - return self._generate_generic_binary(binary, " NOT REGEXP ", **kw) - - def _on_conflict_target(self, clause, **kw): - if clause.constraint_target is not None: - target_text = "(%s)" % clause.constraint_target - elif clause.inferred_target_elements is not None: - target_text = "(%s)" % ", ".join( - ( - self.preparer.quote(c) - if isinstance(c, str) - else self.process(c, include_table=False, use_schema=False) - ) - for c in clause.inferred_target_elements - ) - if clause.inferred_target_whereclause is not None: - target_text += " WHERE %s" % self.process( - clause.inferred_target_whereclause, - include_table=False, - use_schema=False, - literal_binds=True, - ) - - else: - target_text = "" - - return target_text - - def visit_on_conflict_do_nothing(self, on_conflict, **kw): - target_text = self._on_conflict_target(on_conflict, **kw) - - if target_text: - return "ON CONFLICT %s DO NOTHING" % target_text - else: - return "ON CONFLICT DO NOTHING" - - def visit_on_conflict_do_update(self, on_conflict, **kw): - clause = on_conflict - - target_text = self._on_conflict_target(on_conflict, **kw) - - action_set_ops = [] - - set_parameters = dict(clause.update_values_to_set) - # create a list of column assignment clauses as tuples - - insert_statement = self.stack[-1]["selectable"] - cols = insert_statement.table.c - for c in cols: - col_key = c.key - - if col_key in set_parameters: - value = set_parameters.pop(col_key) - elif c in set_parameters: - value = set_parameters.pop(c) - else: - continue - - if coercions._is_literal(value): - value = elements.BindParameter(None, value, type_=c.type) - - else: - if ( - isinstance(value, elements.BindParameter) - and value.type._isnull - ): - value = value._clone() - value.type = c.type - value_text = self.process(value.self_group(), use_schema=False) - - key_text = self.preparer.quote(c.name) - action_set_ops.append("%s = %s" % (key_text, value_text)) - - # check for names that don't match columns - if set_parameters: - util.warn( - "Additional column names not matching " - "any column keys in table '%s': %s" - % ( - self.current_executable.table.name, - (", ".join("'%s'" % c for c in set_parameters)), - ) - ) - for k, v in set_parameters.items(): - key_text = ( - self.preparer.quote(k) - if isinstance(k, str) - else self.process(k, use_schema=False) - ) - value_text = self.process( - coercions.expect(roles.ExpressionElementRole, v), - use_schema=False, - ) - action_set_ops.append("%s = %s" % (key_text, value_text)) - - action_text = ", ".join(action_set_ops) - if clause.update_whereclause is not None: - action_text += " WHERE %s" % self.process( - clause.update_whereclause, include_table=True, use_schema=False - ) - - return "ON CONFLICT %s DO UPDATE SET %s" % (target_text, action_text) - - -class SQLiteDDLCompiler(compiler.DDLCompiler): - def get_column_specification(self, column, **kwargs): - coltype = self.dialect.type_compiler_instance.process( - column.type, type_expression=column - ) - colspec = self.preparer.format_column(column) + " " + coltype - default = self.get_column_default_string(column) - if default is not None: - if isinstance(column.server_default.arg, ColumnElement): - default = "(" + default + ")" - colspec += " DEFAULT " + default - - if not column.nullable: - colspec += " NOT NULL" - - on_conflict_clause = column.dialect_options["sqlite"][ - "on_conflict_not_null" - ] - if on_conflict_clause is not None: - colspec += " ON CONFLICT " + on_conflict_clause - - if column.primary_key: - if ( - column.autoincrement is True - and len(column.table.primary_key.columns) != 1 - ): - raise exc.CompileError( - "SQLite does not support autoincrement for " - "composite primary keys" - ) - - if ( - column.table.dialect_options["sqlite"]["autoincrement"] - and len(column.table.primary_key.columns) == 1 - and issubclass(column.type._type_affinity, sqltypes.Integer) - and not column.foreign_keys - ): - colspec += " PRIMARY KEY" - - on_conflict_clause = column.dialect_options["sqlite"][ - "on_conflict_primary_key" - ] - if on_conflict_clause is not None: - colspec += " ON CONFLICT " + on_conflict_clause - - colspec += " AUTOINCREMENT" - - if column.computed is not None: - colspec += " " + self.process(column.computed) - - return colspec - - def visit_primary_key_constraint(self, constraint, **kw): - # for columns with sqlite_autoincrement=True, - # the PRIMARY KEY constraint can only be inline - # with the column itself. - if len(constraint.columns) == 1: - c = list(constraint)[0] - if ( - c.primary_key - and c.table.dialect_options["sqlite"]["autoincrement"] - and issubclass(c.type._type_affinity, sqltypes.Integer) - and not c.foreign_keys - ): - return None - - text = super().visit_primary_key_constraint(constraint) - - on_conflict_clause = constraint.dialect_options["sqlite"][ - "on_conflict" - ] - if on_conflict_clause is None and len(constraint.columns) == 1: - on_conflict_clause = list(constraint)[0].dialect_options["sqlite"][ - "on_conflict_primary_key" - ] - - if on_conflict_clause is not None: - text += " ON CONFLICT " + on_conflict_clause - - return text - - def visit_unique_constraint(self, constraint, **kw): - text = super().visit_unique_constraint(constraint) - - on_conflict_clause = constraint.dialect_options["sqlite"][ - "on_conflict" - ] - if on_conflict_clause is None and len(constraint.columns) == 1: - col1 = list(constraint)[0] - if isinstance(col1, schema.SchemaItem): - on_conflict_clause = list(constraint)[0].dialect_options[ - "sqlite" - ]["on_conflict_unique"] - - if on_conflict_clause is not None: - text += " ON CONFLICT " + on_conflict_clause - - return text - - def visit_check_constraint(self, constraint, **kw): - text = super().visit_check_constraint(constraint) - - on_conflict_clause = constraint.dialect_options["sqlite"][ - "on_conflict" - ] - - if on_conflict_clause is not None: - text += " ON CONFLICT " + on_conflict_clause - - return text - - def visit_column_check_constraint(self, constraint, **kw): - text = super().visit_column_check_constraint(constraint) - - if constraint.dialect_options["sqlite"]["on_conflict"] is not None: - raise exc.CompileError( - "SQLite does not support on conflict clause for " - "column check constraint" - ) - - return text - - def visit_foreign_key_constraint(self, constraint, **kw): - local_table = constraint.elements[0].parent.table - remote_table = constraint.elements[0].column.table - - if local_table.schema != remote_table.schema: - return None - else: - return super().visit_foreign_key_constraint(constraint) - - def define_constraint_remote_table(self, constraint, table, preparer): - """Format the remote table clause of a CREATE CONSTRAINT clause.""" - - return preparer.format_table(table, use_schema=False) - - def visit_create_index( - self, create, include_schema=False, include_table_schema=True, **kw - ): - index = create.element - self._verify_index_table(index) - preparer = self.preparer - text = "CREATE " - if index.unique: - text += "UNIQUE " - - text += "INDEX " - - if create.if_not_exists: - text += "IF NOT EXISTS " - - text += "%s ON %s (%s)" % ( - self._prepared_index_name(index, include_schema=True), - preparer.format_table(index.table, use_schema=False), - ", ".join( - self.sql_compiler.process( - expr, include_table=False, literal_binds=True - ) - for expr in index.expressions - ), - ) - - whereclause = index.dialect_options["sqlite"]["where"] - if whereclause is not None: - where_compiled = self.sql_compiler.process( - whereclause, include_table=False, literal_binds=True - ) - text += " WHERE " + where_compiled - - return text - - def post_create_table(self, table): - if table.dialect_options["sqlite"]["with_rowid"] is False: - return "\n WITHOUT ROWID" - return "" - - -class SQLiteTypeCompiler(compiler.GenericTypeCompiler): - def visit_large_binary(self, type_, **kw): - return self.visit_BLOB(type_) - - def visit_DATETIME(self, type_, **kw): - if ( - not isinstance(type_, _DateTimeMixin) - or type_.format_is_text_affinity - ): - return super().visit_DATETIME(type_) - else: - return "DATETIME_CHAR" - - def visit_DATE(self, type_, **kw): - if ( - not isinstance(type_, _DateTimeMixin) - or type_.format_is_text_affinity - ): - return super().visit_DATE(type_) - else: - return "DATE_CHAR" - - def visit_TIME(self, type_, **kw): - if ( - not isinstance(type_, _DateTimeMixin) - or type_.format_is_text_affinity - ): - return super().visit_TIME(type_) - else: - return "TIME_CHAR" - - def visit_JSON(self, type_, **kw): - # note this name provides NUMERIC affinity, not TEXT. - # should not be an issue unless the JSON value consists of a single - # numeric value. JSONTEXT can be used if this case is required. - return "JSON" - - -class SQLiteIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = { - "add", - "after", - "all", - "alter", - "analyze", - "and", - "as", - "asc", - "attach", - "autoincrement", - "before", - "begin", - "between", - "by", - "cascade", - "case", - "cast", - "check", - "collate", - "column", - "commit", - "conflict", - "constraint", - "create", - "cross", - "current_date", - "current_time", - "current_timestamp", - "database", - "default", - "deferrable", - "deferred", - "delete", - "desc", - "detach", - "distinct", - "drop", - "each", - "else", - "end", - "escape", - "except", - "exclusive", - "exists", - "explain", - "false", - "fail", - "for", - "foreign", - "from", - "full", - "glob", - "group", - "having", - "if", - "ignore", - "immediate", - "in", - "index", - "indexed", - "initially", - "inner", - "insert", - "instead", - "intersect", - "into", - "is", - "isnull", - "join", - "key", - "left", - "like", - "limit", - "match", - "natural", - "not", - "notnull", - "null", - "of", - "offset", - "on", - "or", - "order", - "outer", - "plan", - "pragma", - "primary", - "query", - "raise", - "references", - "reindex", - "rename", - "replace", - "restrict", - "right", - "rollback", - "row", - "select", - "set", - "table", - "temp", - "temporary", - "then", - "to", - "transaction", - "trigger", - "true", - "union", - "unique", - "update", - "using", - "vacuum", - "values", - "view", - "virtual", - "when", - "where", - } - - -class SQLiteExecutionContext(default.DefaultExecutionContext): - @util.memoized_property - def _preserve_raw_colnames(self): - return ( - not self.dialect._broken_dotted_colnames - or self.execution_options.get("sqlite_raw_colnames", False) - ) - - def _translate_colname(self, colname): - # TODO: detect SQLite version 3.10.0 or greater; - # see [ticket:3633] - - # adjust for dotted column names. SQLite - # in the case of UNION may store col names as - # "tablename.colname", or if using an attached database, - # "database.tablename.colname", in cursor.description - if not self._preserve_raw_colnames and "." in colname: - return colname.split(".")[-1], colname - else: - return colname, None - - -class SQLiteDialect(default.DefaultDialect): - name = "sqlite" - supports_alter = False - - # SQlite supports "DEFAULT VALUES" but *does not* support - # "VALUES (DEFAULT)" - supports_default_values = True - supports_default_metavalue = False - - # sqlite issue: - # https://github.com/python/cpython/issues/93421 - # note this parameter is no longer used by the ORM or default dialect - # see #9414 - supports_sane_rowcount_returning = False - - supports_empty_insert = False - supports_cast = True - supports_multivalues_insert = True - use_insertmanyvalues = True - tuple_in_values = True - supports_statement_cache = True - insert_null_pk_still_autoincrements = True - insert_returning = True - update_returning = True - update_returning_multifrom = True - delete_returning = True - update_returning_multifrom = True - - supports_default_metavalue = True - """dialect supports INSERT... VALUES (DEFAULT) syntax""" - - default_metavalue_token = "NULL" - """for INSERT... VALUES (DEFAULT) syntax, the token to put in the - parenthesis.""" - - default_paramstyle = "qmark" - execution_ctx_cls = SQLiteExecutionContext - statement_compiler = SQLiteCompiler - ddl_compiler = SQLiteDDLCompiler - type_compiler_cls = SQLiteTypeCompiler - preparer = SQLiteIdentifierPreparer - ischema_names = ischema_names - colspecs = colspecs - - construct_arguments = [ - ( - sa_schema.Table, - { - "autoincrement": False, - "with_rowid": True, - }, - ), - (sa_schema.Index, {"where": None}), - ( - sa_schema.Column, - { - "on_conflict_primary_key": None, - "on_conflict_not_null": None, - "on_conflict_unique": None, - }, - ), - (sa_schema.Constraint, {"on_conflict": None}), - ] - - _broken_fk_pragma_quotes = False - _broken_dotted_colnames = False - - @util.deprecated_params( - _json_serializer=( - "1.3.7", - "The _json_serializer argument to the SQLite dialect has " - "been renamed to the correct name of json_serializer. The old " - "argument name will be removed in a future release.", - ), - _json_deserializer=( - "1.3.7", - "The _json_deserializer argument to the SQLite dialect has " - "been renamed to the correct name of json_deserializer. The old " - "argument name will be removed in a future release.", - ), - ) - def __init__( - self, - native_datetime=False, - json_serializer=None, - json_deserializer=None, - _json_serializer=None, - _json_deserializer=None, - **kwargs, - ): - default.DefaultDialect.__init__(self, **kwargs) - - if _json_serializer: - json_serializer = _json_serializer - if _json_deserializer: - json_deserializer = _json_deserializer - self._json_serializer = json_serializer - self._json_deserializer = json_deserializer - - # this flag used by pysqlite dialect, and perhaps others in the - # future, to indicate the driver is handling date/timestamp - # conversions (and perhaps datetime/time as well on some hypothetical - # driver ?) - self.native_datetime = native_datetime - - if self.dbapi is not None: - if self.dbapi.sqlite_version_info < (3, 7, 16): - util.warn( - "SQLite version %s is older than 3.7.16, and will not " - "support right nested joins, as are sometimes used in " - "more complex ORM scenarios. SQLAlchemy 1.4 and above " - "no longer tries to rewrite these joins." - % (self.dbapi.sqlite_version_info,) - ) - - # NOTE: python 3.7 on fedora for me has SQLite 3.34.1. These - # version checks are getting very stale. - self._broken_dotted_colnames = self.dbapi.sqlite_version_info < ( - 3, - 10, - 0, - ) - self.supports_default_values = self.dbapi.sqlite_version_info >= ( - 3, - 3, - 8, - ) - self.supports_cast = self.dbapi.sqlite_version_info >= (3, 2, 3) - self.supports_multivalues_insert = ( - # https://www.sqlite.org/releaselog/3_7_11.html - self.dbapi.sqlite_version_info - >= (3, 7, 11) - ) - # see https://www.sqlalchemy.org/trac/ticket/2568 - # as well as https://www.sqlite.org/src/info/600482d161 - self._broken_fk_pragma_quotes = self.dbapi.sqlite_version_info < ( - 3, - 6, - 14, - ) - - if self.dbapi.sqlite_version_info < (3, 35) or util.pypy: - self.update_returning = self.delete_returning = ( - self.insert_returning - ) = False - - if self.dbapi.sqlite_version_info < (3, 32, 0): - # https://www.sqlite.org/limits.html - self.insertmanyvalues_max_parameters = 999 - - _isolation_lookup = util.immutabledict( - {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0} - ) - - def get_isolation_level_values(self, dbapi_connection): - return list(self._isolation_lookup) - - def set_isolation_level(self, dbapi_connection, level): - isolation_level = self._isolation_lookup[level] - - cursor = dbapi_connection.cursor() - cursor.execute(f"PRAGMA read_uncommitted = {isolation_level}") - cursor.close() - - def get_isolation_level(self, dbapi_connection): - cursor = dbapi_connection.cursor() - cursor.execute("PRAGMA read_uncommitted") - res = cursor.fetchone() - if res: - value = res[0] - else: - # https://www.sqlite.org/changes.html#version_3_3_3 - # "Optional READ UNCOMMITTED isolation (instead of the - # default isolation level of SERIALIZABLE) and - # table level locking when database connections - # share a common cache."" - # pre-SQLite 3.3.0 default to 0 - value = 0 - cursor.close() - if value == 0: - return "SERIALIZABLE" - elif value == 1: - return "READ UNCOMMITTED" - else: - assert False, "Unknown isolation level %s" % value - - @reflection.cache - def get_schema_names(self, connection, **kw): - s = "PRAGMA database_list" - dl = connection.exec_driver_sql(s) - - return [db[1] for db in dl if db[1] != "temp"] - - def _format_schema(self, schema, table_name): - if schema is not None: - qschema = self.identifier_preparer.quote_identifier(schema) - name = f"{qschema}.{table_name}" - else: - name = table_name - return name - - def _sqlite_main_query( - self, - table: str, - type_: str, - schema: Optional[str], - sqlite_include_internal: bool, - ): - main = self._format_schema(schema, table) - if not sqlite_include_internal: - filter_table = " AND name NOT LIKE 'sqlite~_%' ESCAPE '~'" - else: - filter_table = "" - query = ( - f"SELECT name FROM {main} " - f"WHERE type='{type_}'{filter_table} " - "ORDER BY name" - ) - return query - - @reflection.cache - def get_table_names( - self, connection, schema=None, sqlite_include_internal=False, **kw - ): - query = self._sqlite_main_query( - "sqlite_master", "table", schema, sqlite_include_internal - ) - names = connection.exec_driver_sql(query).scalars().all() - return names - - @reflection.cache - def get_temp_table_names( - self, connection, sqlite_include_internal=False, **kw - ): - query = self._sqlite_main_query( - "sqlite_temp_master", "table", None, sqlite_include_internal - ) - names = connection.exec_driver_sql(query).scalars().all() - return names - - @reflection.cache - def get_temp_view_names( - self, connection, sqlite_include_internal=False, **kw - ): - query = self._sqlite_main_query( - "sqlite_temp_master", "view", None, sqlite_include_internal - ) - names = connection.exec_driver_sql(query).scalars().all() - return names - - @reflection.cache - def has_table(self, connection, table_name, schema=None, **kw): - self._ensure_has_table_connection(connection) - - if schema is not None and schema not in self.get_schema_names( - connection, **kw - ): - return False - - info = self._get_table_pragma( - connection, "table_info", table_name, schema=schema - ) - return bool(info) - - def _get_default_schema_name(self, connection): - return "main" - - @reflection.cache - def get_view_names( - self, connection, schema=None, sqlite_include_internal=False, **kw - ): - query = self._sqlite_main_query( - "sqlite_master", "view", schema, sqlite_include_internal - ) - names = connection.exec_driver_sql(query).scalars().all() - return names - - @reflection.cache - def get_view_definition(self, connection, view_name, schema=None, **kw): - if schema is not None: - qschema = self.identifier_preparer.quote_identifier(schema) - master = f"{qschema}.sqlite_master" - s = ("SELECT sql FROM %s WHERE name = ? AND type='view'") % ( - master, - ) - rs = connection.exec_driver_sql(s, (view_name,)) - else: - try: - s = ( - "SELECT sql FROM " - " (SELECT * FROM sqlite_master UNION ALL " - " SELECT * FROM sqlite_temp_master) " - "WHERE name = ? " - "AND type='view'" - ) - rs = connection.exec_driver_sql(s, (view_name,)) - except exc.DBAPIError: - s = ( - "SELECT sql FROM sqlite_master WHERE name = ? " - "AND type='view'" - ) - rs = connection.exec_driver_sql(s, (view_name,)) - - result = rs.fetchall() - if result: - return result[0].sql - else: - raise exc.NoSuchTableError( - f"{schema}.{view_name}" if schema else view_name - ) - - @reflection.cache - def get_columns(self, connection, table_name, schema=None, **kw): - pragma = "table_info" - # computed columns are threaded as hidden, they require table_xinfo - if self.server_version_info >= (3, 31): - pragma = "table_xinfo" - info = self._get_table_pragma( - connection, pragma, table_name, schema=schema - ) - columns = [] - tablesql = None - for row in info: - name = row[1] - type_ = row[2].upper() - nullable = not row[3] - default = row[4] - primary_key = row[5] - hidden = row[6] if pragma == "table_xinfo" else 0 - - # hidden has value 0 for normal columns, 1 for hidden columns, - # 2 for computed virtual columns and 3 for computed stored columns - # https://www.sqlite.org/src/info/069351b85f9a706f60d3e98fbc8aaf40c374356b967c0464aede30ead3d9d18b - if hidden == 1: - continue - - generated = bool(hidden) - persisted = hidden == 3 - - if tablesql is None and generated: - tablesql = self._get_table_sql( - connection, table_name, schema, **kw - ) - - columns.append( - self._get_column_info( - name, - type_, - nullable, - default, - primary_key, - generated, - persisted, - tablesql, - ) - ) - if columns: - return columns - elif not self.has_table(connection, table_name, schema): - raise exc.NoSuchTableError( - f"{schema}.{table_name}" if schema else table_name - ) - else: - return ReflectionDefaults.columns() - - def _get_column_info( - self, - name, - type_, - nullable, - default, - primary_key, - generated, - persisted, - tablesql, - ): - if generated: - # the type of a column "cc INTEGER GENERATED ALWAYS AS (1 + 42)" - # somehow is "INTEGER GENERATED ALWAYS" - type_ = re.sub("generated", "", type_, flags=re.IGNORECASE) - type_ = re.sub("always", "", type_, flags=re.IGNORECASE).strip() - - coltype = self._resolve_type_affinity(type_) - - if default is not None: - default = str(default) - - colspec = { - "name": name, - "type": coltype, - "nullable": nullable, - "default": default, - "primary_key": primary_key, - } - if generated: - sqltext = "" - if tablesql: - pattern = r"[^,]*\s+AS\s+\(([^,]*)\)\s*(?:virtual|stored)?" - match = re.search( - re.escape(name) + pattern, tablesql, re.IGNORECASE - ) - if match: - sqltext = match.group(1) - colspec["computed"] = {"sqltext": sqltext, "persisted": persisted} - return colspec - - def _resolve_type_affinity(self, type_): - """Return a data type from a reflected column, using affinity rules. - - SQLite's goal for universal compatibility introduces some complexity - during reflection, as a column's defined type might not actually be a - type that SQLite understands - or indeed, my not be defined *at all*. - Internally, SQLite handles this with a 'data type affinity' for each - column definition, mapping to one of 'TEXT', 'NUMERIC', 'INTEGER', - 'REAL', or 'NONE' (raw bits). The algorithm that determines this is - listed in https://www.sqlite.org/datatype3.html section 2.1. - - This method allows SQLAlchemy to support that algorithm, while still - providing access to smarter reflection utilities by recognizing - column definitions that SQLite only supports through affinity (like - DATE and DOUBLE). - - """ - match = re.match(r"([\w ]+)(\(.*?\))?", type_) - if match: - coltype = match.group(1) - args = match.group(2) - else: - coltype = "" - args = "" - - if coltype in self.ischema_names: - coltype = self.ischema_names[coltype] - elif "INT" in coltype: - coltype = sqltypes.INTEGER - elif "CHAR" in coltype or "CLOB" in coltype or "TEXT" in coltype: - coltype = sqltypes.TEXT - elif "BLOB" in coltype or not coltype: - coltype = sqltypes.NullType - elif "REAL" in coltype or "FLOA" in coltype or "DOUB" in coltype: - coltype = sqltypes.REAL - else: - coltype = sqltypes.NUMERIC - - if args is not None: - args = re.findall(r"(\d+)", args) - try: - coltype = coltype(*[int(a) for a in args]) - except TypeError: - util.warn( - "Could not instantiate type %s with " - "reflected arguments %s; using no arguments." - % (coltype, args) - ) - coltype = coltype() - else: - coltype = coltype() - - return coltype - - @reflection.cache - def get_pk_constraint(self, connection, table_name, schema=None, **kw): - constraint_name = None - table_data = self._get_table_sql(connection, table_name, schema=schema) - if table_data: - PK_PATTERN = r"CONSTRAINT (\w+) PRIMARY KEY" - result = re.search(PK_PATTERN, table_data, re.I) - constraint_name = result.group(1) if result else None - - cols = self.get_columns(connection, table_name, schema, **kw) - # consider only pk columns. This also avoids sorting the cached - # value returned by get_columns - cols = [col for col in cols if col.get("primary_key", 0) > 0] - cols.sort(key=lambda col: col.get("primary_key")) - pkeys = [col["name"] for col in cols] - - if pkeys: - return {"constrained_columns": pkeys, "name": constraint_name} - else: - return ReflectionDefaults.pk_constraint() - - @reflection.cache - def get_foreign_keys(self, connection, table_name, schema=None, **kw): - # sqlite makes this *extremely difficult*. - # First, use the pragma to get the actual FKs. - pragma_fks = self._get_table_pragma( - connection, "foreign_key_list", table_name, schema=schema - ) - - fks = {} - - for row in pragma_fks: - (numerical_id, rtbl, lcol, rcol) = (row[0], row[2], row[3], row[4]) - - if not rcol: - # no referred column, which means it was not named in the - # original DDL. The referred columns of the foreign key - # constraint are therefore the primary key of the referred - # table. - try: - referred_pk = self.get_pk_constraint( - connection, rtbl, schema=schema, **kw - ) - referred_columns = referred_pk["constrained_columns"] - except exc.NoSuchTableError: - # ignore not existing parents - referred_columns = [] - else: - # note we use this list only if this is the first column - # in the constraint. for subsequent columns we ignore the - # list and append "rcol" if present. - referred_columns = [] - - if self._broken_fk_pragma_quotes: - rtbl = re.sub(r"^[\"\[`\']|[\"\]`\']$", "", rtbl) - - if numerical_id in fks: - fk = fks[numerical_id] - else: - fk = fks[numerical_id] = { - "name": None, - "constrained_columns": [], - "referred_schema": schema, - "referred_table": rtbl, - "referred_columns": referred_columns, - "options": {}, - } - fks[numerical_id] = fk - - fk["constrained_columns"].append(lcol) - - if rcol: - fk["referred_columns"].append(rcol) - - def fk_sig(constrained_columns, referred_table, referred_columns): - return ( - tuple(constrained_columns) - + (referred_table,) - + tuple(referred_columns) - ) - - # then, parse the actual SQL and attempt to find DDL that matches - # the names as well. SQLite saves the DDL in whatever format - # it was typed in as, so need to be liberal here. - - keys_by_signature = { - fk_sig( - fk["constrained_columns"], - fk["referred_table"], - fk["referred_columns"], - ): fk - for fk in fks.values() - } - - table_data = self._get_table_sql(connection, table_name, schema=schema) - - def parse_fks(): - if table_data is None: - # system tables, etc. - return - - # note that we already have the FKs from PRAGMA above. This whole - # regexp thing is trying to locate additional detail about the - # FKs, namely the name of the constraint and other options. - # so parsing the columns is really about matching it up to what - # we already have. - FK_PATTERN = ( - r"(?:CONSTRAINT (\w+) +)?" - r"FOREIGN KEY *\( *(.+?) *\) +" - r'REFERENCES +(?:(?:"(.+?)")|([a-z0-9_]+)) *\( *((?:(?:"[^"]+"|[a-z0-9_]+) *(?:, *)?)+)\) *' # noqa: E501 - r"((?:ON (?:DELETE|UPDATE) " - r"(?:SET NULL|SET DEFAULT|CASCADE|RESTRICT|NO ACTION) *)*)" - r"((?:NOT +)?DEFERRABLE)?" - r"(?: +INITIALLY +(DEFERRED|IMMEDIATE))?" - ) - for match in re.finditer(FK_PATTERN, table_data, re.I): - ( - constraint_name, - constrained_columns, - referred_quoted_name, - referred_name, - referred_columns, - onupdatedelete, - deferrable, - initially, - ) = match.group(1, 2, 3, 4, 5, 6, 7, 8) - constrained_columns = list( - self._find_cols_in_sig(constrained_columns) - ) - if not referred_columns: - referred_columns = constrained_columns - else: - referred_columns = list( - self._find_cols_in_sig(referred_columns) - ) - referred_name = referred_quoted_name or referred_name - options = {} - - for token in re.split(r" *\bON\b *", onupdatedelete.upper()): - if token.startswith("DELETE"): - ondelete = token[6:].strip() - if ondelete and ondelete != "NO ACTION": - options["ondelete"] = ondelete - elif token.startswith("UPDATE"): - onupdate = token[6:].strip() - if onupdate and onupdate != "NO ACTION": - options["onupdate"] = onupdate - - if deferrable: - options["deferrable"] = "NOT" not in deferrable.upper() - if initially: - options["initially"] = initially.upper() - - yield ( - constraint_name, - constrained_columns, - referred_name, - referred_columns, - options, - ) - - fkeys = [] - - for ( - constraint_name, - constrained_columns, - referred_name, - referred_columns, - options, - ) in parse_fks(): - sig = fk_sig(constrained_columns, referred_name, referred_columns) - if sig not in keys_by_signature: - util.warn( - "WARNING: SQL-parsed foreign key constraint " - "'%s' could not be located in PRAGMA " - "foreign_keys for table %s" % (sig, table_name) - ) - continue - key = keys_by_signature.pop(sig) - key["name"] = constraint_name - key["options"] = options - fkeys.append(key) - # assume the remainders are the unnamed, inline constraints, just - # use them as is as it's extremely difficult to parse inline - # constraints - fkeys.extend(keys_by_signature.values()) - if fkeys: - return fkeys - else: - return ReflectionDefaults.foreign_keys() - - def _find_cols_in_sig(self, sig): - for match in re.finditer(r'(?:"(.+?)")|([a-z0-9_]+)', sig, re.I): - yield match.group(1) or match.group(2) - - @reflection.cache - def get_unique_constraints( - self, connection, table_name, schema=None, **kw - ): - auto_index_by_sig = {} - for idx in self.get_indexes( - connection, - table_name, - schema=schema, - include_auto_indexes=True, - **kw, - ): - if not idx["name"].startswith("sqlite_autoindex"): - continue - sig = tuple(idx["column_names"]) - auto_index_by_sig[sig] = idx - - table_data = self._get_table_sql( - connection, table_name, schema=schema, **kw - ) - unique_constraints = [] - - def parse_uqs(): - if table_data is None: - return - UNIQUE_PATTERN = r'(?:CONSTRAINT "?(.+?)"? +)?UNIQUE *\((.+?)\)' - INLINE_UNIQUE_PATTERN = ( - r'(?:(".+?")|(?:[\[`])?([a-z0-9_]+)(?:[\]`])?) ' - r"+[a-z0-9_ ]+? +UNIQUE" - ) - - for match in re.finditer(UNIQUE_PATTERN, table_data, re.I): - name, cols = match.group(1, 2) - yield name, list(self._find_cols_in_sig(cols)) - - # we need to match inlines as well, as we seek to differentiate - # a UNIQUE constraint from a UNIQUE INDEX, even though these - # are kind of the same thing :) - for match in re.finditer(INLINE_UNIQUE_PATTERN, table_data, re.I): - cols = list( - self._find_cols_in_sig(match.group(1) or match.group(2)) - ) - yield None, cols - - for name, cols in parse_uqs(): - sig = tuple(cols) - if sig in auto_index_by_sig: - auto_index_by_sig.pop(sig) - parsed_constraint = {"name": name, "column_names": cols} - unique_constraints.append(parsed_constraint) - # NOTE: auto_index_by_sig might not be empty here, - # the PRIMARY KEY may have an entry. - if unique_constraints: - return unique_constraints - else: - return ReflectionDefaults.unique_constraints() - - @reflection.cache - def get_check_constraints(self, connection, table_name, schema=None, **kw): - table_data = self._get_table_sql( - connection, table_name, schema=schema, **kw - ) - - CHECK_PATTERN = r"(?:CONSTRAINT (.+) +)?" r"CHECK *\( *(.+) *\),? *" - cks = [] - # NOTE: we aren't using re.S here because we actually are - # taking advantage of each CHECK constraint being all on one - # line in the table definition in order to delineate. This - # necessarily makes assumptions as to how the CREATE TABLE - # was emitted. - - for match in re.finditer(CHECK_PATTERN, table_data or "", re.I): - name = match.group(1) - - if name: - name = re.sub(r'^"|"$', "", name) - - cks.append({"sqltext": match.group(2), "name": name}) - cks.sort(key=lambda d: d["name"] or "~") # sort None as last - if cks: - return cks - else: - return ReflectionDefaults.check_constraints() - - @reflection.cache - def get_indexes(self, connection, table_name, schema=None, **kw): - pragma_indexes = self._get_table_pragma( - connection, "index_list", table_name, schema=schema - ) - indexes = [] - - # regular expression to extract the filter predicate of a partial - # index. this could fail to extract the predicate correctly on - # indexes created like - # CREATE INDEX i ON t (col || ') where') WHERE col <> '' - # but as this function does not support expression-based indexes - # this case does not occur. - partial_pred_re = re.compile(r"\)\s+where\s+(.+)", re.IGNORECASE) - - if schema: - schema_expr = "%s." % self.identifier_preparer.quote_identifier( - schema - ) - else: - schema_expr = "" - - include_auto_indexes = kw.pop("include_auto_indexes", False) - for row in pragma_indexes: - # ignore implicit primary key index. - # https://www.mail-archive.com/sqlite-users@sqlite.org/msg30517.html - if not include_auto_indexes and row[1].startswith( - "sqlite_autoindex" - ): - continue - indexes.append( - dict( - name=row[1], - column_names=[], - unique=row[2], - dialect_options={}, - ) - ) - - # check partial indexes - if len(row) >= 5 and row[4]: - s = ( - "SELECT sql FROM %(schema)ssqlite_master " - "WHERE name = ? " - "AND type = 'index'" % {"schema": schema_expr} - ) - rs = connection.exec_driver_sql(s, (row[1],)) - index_sql = rs.scalar() - predicate_match = partial_pred_re.search(index_sql) - if predicate_match is None: - # unless the regex is broken this case shouldn't happen - # because we know this is a partial index, so the - # definition sql should match the regex - util.warn( - "Failed to look up filter predicate of " - "partial index %s" % row[1] - ) - else: - predicate = predicate_match.group(1) - indexes[-1]["dialect_options"]["sqlite_where"] = text( - predicate - ) - - # loop thru unique indexes to get the column names. - for idx in list(indexes): - pragma_index = self._get_table_pragma( - connection, "index_info", idx["name"], schema=schema - ) - - for row in pragma_index: - if row[2] is None: - util.warn( - "Skipped unsupported reflection of " - "expression-based index %s" % idx["name"] - ) - indexes.remove(idx) - break - else: - idx["column_names"].append(row[2]) - - indexes.sort(key=lambda d: d["name"] or "~") # sort None as last - if indexes: - return indexes - elif not self.has_table(connection, table_name, schema): - raise exc.NoSuchTableError( - f"{schema}.{table_name}" if schema else table_name - ) - else: - return ReflectionDefaults.indexes() - - def _is_sys_table(self, table_name): - return table_name in { - "sqlite_schema", - "sqlite_master", - "sqlite_temp_schema", - "sqlite_temp_master", - } - - @reflection.cache - def _get_table_sql(self, connection, table_name, schema=None, **kw): - if schema: - schema_expr = "%s." % ( - self.identifier_preparer.quote_identifier(schema) - ) - else: - schema_expr = "" - try: - s = ( - "SELECT sql FROM " - " (SELECT * FROM %(schema)ssqlite_master UNION ALL " - " SELECT * FROM %(schema)ssqlite_temp_master) " - "WHERE name = ? " - "AND type in ('table', 'view')" % {"schema": schema_expr} - ) - rs = connection.exec_driver_sql(s, (table_name,)) - except exc.DBAPIError: - s = ( - "SELECT sql FROM %(schema)ssqlite_master " - "WHERE name = ? " - "AND type in ('table', 'view')" % {"schema": schema_expr} - ) - rs = connection.exec_driver_sql(s, (table_name,)) - value = rs.scalar() - if value is None and not self._is_sys_table(table_name): - raise exc.NoSuchTableError(f"{schema_expr}{table_name}") - return value - - def _get_table_pragma(self, connection, pragma, table_name, schema=None): - quote = self.identifier_preparer.quote_identifier - if schema is not None: - statements = [f"PRAGMA {quote(schema)}."] - else: - # because PRAGMA looks in all attached databases if no schema - # given, need to specify "main" schema, however since we want - # 'temp' tables in the same namespace as 'main', need to run - # the PRAGMA twice - statements = ["PRAGMA main.", "PRAGMA temp."] - - qtable = quote(table_name) - for statement in statements: - statement = f"{statement}{pragma}({qtable})" - cursor = connection.exec_driver_sql(statement) - if not cursor._soft_closed: - # work around SQLite issue whereby cursor.description - # is blank when PRAGMA returns no rows: - # https://www.sqlite.org/cvstrac/tktview?tn=1884 - result = cursor.fetchall() - else: - result = [] - if result: - return result - else: - return [] diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/dml.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/dml.py deleted file mode 100644 index dcf5e44..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/dml.py +++ /dev/null @@ -1,240 +0,0 @@ -# dialects/sqlite/dml.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -from __future__ import annotations - -from typing import Any - -from .._typing import _OnConflictIndexElementsT -from .._typing import _OnConflictIndexWhereT -from .._typing import _OnConflictSetT -from .._typing import _OnConflictWhereT -from ... import util -from ...sql import coercions -from ...sql import roles -from ...sql._typing import _DMLTableArgument -from ...sql.base import _exclusive_against -from ...sql.base import _generative -from ...sql.base import ColumnCollection -from ...sql.base import ReadOnlyColumnCollection -from ...sql.dml import Insert as StandardInsert -from ...sql.elements import ClauseElement -from ...sql.elements import KeyedColumnElement -from ...sql.expression import alias -from ...util.typing import Self - -__all__ = ("Insert", "insert") - - -def insert(table: _DMLTableArgument) -> Insert: - """Construct a sqlite-specific variant :class:`_sqlite.Insert` - construct. - - .. container:: inherited_member - - The :func:`sqlalchemy.dialects.sqlite.insert` function creates - a :class:`sqlalchemy.dialects.sqlite.Insert`. This class is based - on the dialect-agnostic :class:`_sql.Insert` construct which may - be constructed using the :func:`_sql.insert` function in - SQLAlchemy Core. - - The :class:`_sqlite.Insert` construct includes additional methods - :meth:`_sqlite.Insert.on_conflict_do_update`, - :meth:`_sqlite.Insert.on_conflict_do_nothing`. - - """ - return Insert(table) - - -class Insert(StandardInsert): - """SQLite-specific implementation of INSERT. - - Adds methods for SQLite-specific syntaxes such as ON CONFLICT. - - The :class:`_sqlite.Insert` object is created using the - :func:`sqlalchemy.dialects.sqlite.insert` function. - - .. versionadded:: 1.4 - - .. seealso:: - - :ref:`sqlite_on_conflict_insert` - - """ - - stringify_dialect = "sqlite" - inherit_cache = False - - @util.memoized_property - def excluded( - self, - ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: - """Provide the ``excluded`` namespace for an ON CONFLICT statement - - SQLite's ON CONFLICT clause allows reference to the row that would - be inserted, known as ``excluded``. This attribute provides - all columns in this row to be referenceable. - - .. tip:: The :attr:`_sqlite.Insert.excluded` attribute is an instance - of :class:`_expression.ColumnCollection`, which provides an - interface the same as that of the :attr:`_schema.Table.c` - collection described at :ref:`metadata_tables_and_columns`. - With this collection, ordinary names are accessible like attributes - (e.g. ``stmt.excluded.some_column``), but special names and - dictionary method names should be accessed using indexed access, - such as ``stmt.excluded["column name"]`` or - ``stmt.excluded["values"]``. See the docstring for - :class:`_expression.ColumnCollection` for further examples. - - """ - return alias(self.table, name="excluded").columns - - _on_conflict_exclusive = _exclusive_against( - "_post_values_clause", - msgs={ - "_post_values_clause": "This Insert construct already has " - "an ON CONFLICT clause established" - }, - ) - - @_generative - @_on_conflict_exclusive - def on_conflict_do_update( - self, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - set_: _OnConflictSetT = None, - where: _OnConflictWhereT = None, - ) -> Self: - r""" - Specifies a DO UPDATE SET action for ON CONFLICT clause. - - :param index_elements: - A sequence consisting of string column names, :class:`_schema.Column` - objects, or other column expression objects that will be used - to infer a target index or unique constraint. - - :param index_where: - Additional WHERE criterion that can be used to infer a - conditional target index. - - :param set\_: - A dictionary or other mapping object - where the keys are either names of columns in the target table, - or :class:`_schema.Column` objects or other ORM-mapped columns - matching that of the target table, and expressions or literals - as values, specifying the ``SET`` actions to take. - - .. versionadded:: 1.4 The - :paramref:`_sqlite.Insert.on_conflict_do_update.set_` - parameter supports :class:`_schema.Column` objects from the target - :class:`_schema.Table` as keys. - - .. warning:: This dictionary does **not** take into account - Python-specified default UPDATE values or generation functions, - e.g. those specified using :paramref:`_schema.Column.onupdate`. - These values will not be exercised for an ON CONFLICT style of - UPDATE, unless they are manually specified in the - :paramref:`.Insert.on_conflict_do_update.set_` dictionary. - - :param where: - Optional argument. If present, can be a literal SQL - string or an acceptable expression for a ``WHERE`` clause - that restricts the rows affected by ``DO UPDATE SET``. Rows - not meeting the ``WHERE`` condition will not be updated - (effectively a ``DO NOTHING`` for those rows). - - """ - - self._post_values_clause = OnConflictDoUpdate( - index_elements, index_where, set_, where - ) - return self - - @_generative - @_on_conflict_exclusive - def on_conflict_do_nothing( - self, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - ) -> Self: - """ - Specifies a DO NOTHING action for ON CONFLICT clause. - - :param index_elements: - A sequence consisting of string column names, :class:`_schema.Column` - objects, or other column expression objects that will be used - to infer a target index or unique constraint. - - :param index_where: - Additional WHERE criterion that can be used to infer a - conditional target index. - - """ - - self._post_values_clause = OnConflictDoNothing( - index_elements, index_where - ) - return self - - -class OnConflictClause(ClauseElement): - stringify_dialect = "sqlite" - - constraint_target: None - inferred_target_elements: _OnConflictIndexElementsT - inferred_target_whereclause: _OnConflictIndexWhereT - - def __init__( - self, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - ): - if index_elements is not None: - self.constraint_target = None - self.inferred_target_elements = index_elements - self.inferred_target_whereclause = index_where - else: - self.constraint_target = self.inferred_target_elements = ( - self.inferred_target_whereclause - ) = None - - -class OnConflictDoNothing(OnConflictClause): - __visit_name__ = "on_conflict_do_nothing" - - -class OnConflictDoUpdate(OnConflictClause): - __visit_name__ = "on_conflict_do_update" - - def __init__( - self, - index_elements: _OnConflictIndexElementsT = None, - index_where: _OnConflictIndexWhereT = None, - set_: _OnConflictSetT = None, - where: _OnConflictWhereT = None, - ): - super().__init__( - index_elements=index_elements, - index_where=index_where, - ) - - if isinstance(set_, dict): - if not set_: - raise ValueError("set parameter dictionary must not be empty") - elif isinstance(set_, ColumnCollection): - set_ = dict(set_) - else: - raise ValueError( - "set parameter must be a non-empty dictionary " - "or a ColumnCollection such as the `.c.` collection " - "of a Table object" - ) - self.update_values_to_set = [ - (coercions.expect(roles.DMLColumnRole, key), value) - for key, value in set_.items() - ] - self.update_whereclause = where diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/json.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/json.py deleted file mode 100644 index ec29802..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/json.py +++ /dev/null @@ -1,92 +0,0 @@ -# dialects/sqlite/json.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from ... import types as sqltypes - - -class JSON(sqltypes.JSON): - """SQLite JSON type. - - SQLite supports JSON as of version 3.9 through its JSON1_ extension. Note - that JSON1_ is a - `loadable extension `_ and as such - may not be available, or may require run-time loading. - - :class:`_sqlite.JSON` is used automatically whenever the base - :class:`_types.JSON` datatype is used against a SQLite backend. - - .. seealso:: - - :class:`_types.JSON` - main documentation for the generic - cross-platform JSON datatype. - - The :class:`_sqlite.JSON` type supports persistence of JSON values - as well as the core index operations provided by :class:`_types.JSON` - datatype, by adapting the operations to render the ``JSON_EXTRACT`` - function wrapped in the ``JSON_QUOTE`` function at the database level. - Extracted values are quoted in order to ensure that the results are - always JSON string values. - - - .. versionadded:: 1.3 - - - .. _JSON1: https://www.sqlite.org/json1.html - - """ - - -# Note: these objects currently match exactly those of MySQL, however since -# these are not generalizable to all JSON implementations, remain separately -# implemented for each dialect. -class _FormatTypeMixin: - def _format_value(self, value): - raise NotImplementedError() - - def bind_processor(self, dialect): - super_proc = self.string_bind_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - def literal_processor(self, dialect): - super_proc = self.string_literal_processor(dialect) - - def process(value): - value = self._format_value(value) - if super_proc: - value = super_proc(value) - return value - - return process - - -class JSONIndexType(_FormatTypeMixin, sqltypes.JSON.JSONIndexType): - def _format_value(self, value): - if isinstance(value, int): - value = "$[%s]" % value - else: - value = '$."%s"' % value - return value - - -class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType): - def _format_value(self, value): - return "$%s" % ( - "".join( - [ - "[%s]" % elem if isinstance(elem, int) else '."%s"' % elem - for elem in value - ] - ) - ) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/provision.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/provision.py deleted file mode 100644 index f18568b..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/provision.py +++ /dev/null @@ -1,198 +0,0 @@ -# dialects/sqlite/provision.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -import os -import re - -from ... import exc -from ...engine import url as sa_url -from ...testing.provision import create_db -from ...testing.provision import drop_db -from ...testing.provision import follower_url_from_main -from ...testing.provision import generate_driver_url -from ...testing.provision import log -from ...testing.provision import post_configure_engine -from ...testing.provision import run_reap_dbs -from ...testing.provision import stop_test_class_outside_fixtures -from ...testing.provision import temp_table_keyword_args -from ...testing.provision import upsert - - -# TODO: I can't get this to build dynamically with pytest-xdist procs -_drivernames = { - "pysqlite", - "aiosqlite", - "pysqlcipher", - "pysqlite_numeric", - "pysqlite_dollar", -} - - -def _format_url(url, driver, ident): - """given a sqlite url + desired driver + ident, make a canonical - URL out of it - - """ - url = sa_url.make_url(url) - - if driver is None: - driver = url.get_driver_name() - - filename = url.database - - needs_enc = driver == "pysqlcipher" - name_token = None - - if filename and filename != ":memory:": - assert "test_schema" not in filename - tokens = re.split(r"[_\.]", filename) - - new_filename = f"{driver}" - - for token in tokens: - if token in _drivernames: - if driver is None: - driver = token - continue - elif token in ("db", "enc"): - continue - elif name_token is None: - name_token = token.strip("_") - - assert name_token, f"sqlite filename has no name token: {url.database}" - - new_filename = f"{name_token}_{driver}" - if ident: - new_filename += f"_{ident}" - new_filename += ".db" - if needs_enc: - new_filename += ".enc" - url = url.set(database=new_filename) - - if needs_enc: - url = url.set(password="test") - - url = url.set(drivername="sqlite+%s" % (driver,)) - - return url - - -@generate_driver_url.for_db("sqlite") -def generate_driver_url(url, driver, query_str): - url = _format_url(url, driver, None) - - try: - url.get_dialect() - except exc.NoSuchModuleError: - return None - else: - return url - - -@follower_url_from_main.for_db("sqlite") -def _sqlite_follower_url_from_main(url, ident): - return _format_url(url, None, ident) - - -@post_configure_engine.for_db("sqlite") -def _sqlite_post_configure_engine(url, engine, follower_ident): - from sqlalchemy import event - - if follower_ident: - attach_path = f"{follower_ident}_{engine.driver}_test_schema.db" - else: - attach_path = f"{engine.driver}_test_schema.db" - - @event.listens_for(engine, "connect") - def connect(dbapi_connection, connection_record): - # use file DBs in all cases, memory acts kind of strangely - # as an attached - - # NOTE! this has to be done *per connection*. New sqlite connection, - # as we get with say, QueuePool, the attaches are gone. - # so schemes to delete those attached files have to be done at the - # filesystem level and not rely upon what attachments are in a - # particular SQLite connection - dbapi_connection.execute( - f'ATTACH DATABASE "{attach_path}" AS test_schema' - ) - - @event.listens_for(engine, "engine_disposed") - def dispose(engine): - """most databases should be dropped using - stop_test_class_outside_fixtures - - however a few tests like AttachedDBTest might not get triggered on - that main hook - - """ - - if os.path.exists(attach_path): - os.remove(attach_path) - - filename = engine.url.database - - if filename and filename != ":memory:" and os.path.exists(filename): - os.remove(filename) - - -@create_db.for_db("sqlite") -def _sqlite_create_db(cfg, eng, ident): - pass - - -@drop_db.for_db("sqlite") -def _sqlite_drop_db(cfg, eng, ident): - _drop_dbs_w_ident(eng.url.database, eng.driver, ident) - - -def _drop_dbs_w_ident(databasename, driver, ident): - for path in os.listdir("."): - fname, ext = os.path.split(path) - if ident in fname and ext in [".db", ".db.enc"]: - log.info("deleting SQLite database file: %s", path) - os.remove(path) - - -@stop_test_class_outside_fixtures.for_db("sqlite") -def stop_test_class_outside_fixtures(config, db, cls): - db.dispose() - - -@temp_table_keyword_args.for_db("sqlite") -def _sqlite_temp_table_keyword_args(cfg, eng): - return {"prefixes": ["TEMPORARY"]} - - -@run_reap_dbs.for_db("sqlite") -def _reap_sqlite_dbs(url, idents): - log.info("db reaper connecting to %r", url) - log.info("identifiers in file: %s", ", ".join(idents)) - url = sa_url.make_url(url) - for ident in idents: - for drivername in _drivernames: - _drop_dbs_w_ident(url.database, drivername, ident) - - -@upsert.for_db("sqlite") -def _upsert( - cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False -): - from sqlalchemy.dialects.sqlite import insert - - stmt = insert(table) - - if set_lambda: - stmt = stmt.on_conflict_do_update(set_=set_lambda(stmt.excluded)) - else: - stmt = stmt.on_conflict_do_nothing() - - stmt = stmt.returning( - *returning, sort_by_parameter_order=sort_by_parameter_order - ) - return stmt diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py deleted file mode 100644 index 388a4df..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py +++ /dev/null @@ -1,155 +0,0 @@ -# dialects/sqlite/pysqlcipher.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -""" -.. dialect:: sqlite+pysqlcipher - :name: pysqlcipher - :dbapi: sqlcipher 3 or pysqlcipher - :connectstring: sqlite+pysqlcipher://:passphrase@/file_path[?kdf_iter=] - - Dialect for support of DBAPIs that make use of the - `SQLCipher `_ backend. - - -Driver ------- - -Current dialect selection logic is: - -* If the :paramref:`_sa.create_engine.module` parameter supplies a DBAPI module, - that module is used. -* Otherwise for Python 3, choose https://pypi.org/project/sqlcipher3/ -* If not available, fall back to https://pypi.org/project/pysqlcipher3/ -* For Python 2, https://pypi.org/project/pysqlcipher/ is used. - -.. warning:: The ``pysqlcipher3`` and ``pysqlcipher`` DBAPI drivers are no - longer maintained; the ``sqlcipher3`` driver as of this writing appears - to be current. For future compatibility, any pysqlcipher-compatible DBAPI - may be used as follows:: - - import sqlcipher_compatible_driver - - from sqlalchemy import create_engine - - e = create_engine( - "sqlite+pysqlcipher://:password@/dbname.db", - module=sqlcipher_compatible_driver - ) - -These drivers make use of the SQLCipher engine. This system essentially -introduces new PRAGMA commands to SQLite which allows the setting of a -passphrase and other encryption parameters, allowing the database file to be -encrypted. - - -Connect Strings ---------------- - -The format of the connect string is in every way the same as that -of the :mod:`~sqlalchemy.dialects.sqlite.pysqlite` driver, except that the -"password" field is now accepted, which should contain a passphrase:: - - e = create_engine('sqlite+pysqlcipher://:testing@/foo.db') - -For an absolute file path, two leading slashes should be used for the -database name:: - - e = create_engine('sqlite+pysqlcipher://:testing@//path/to/foo.db') - -A selection of additional encryption-related pragmas supported by SQLCipher -as documented at https://www.zetetic.net/sqlcipher/sqlcipher-api/ can be passed -in the query string, and will result in that PRAGMA being called for each -new connection. Currently, ``cipher``, ``kdf_iter`` -``cipher_page_size`` and ``cipher_use_hmac`` are supported:: - - e = create_engine('sqlite+pysqlcipher://:testing@/foo.db?cipher=aes-256-cfb&kdf_iter=64000') - -.. warning:: Previous versions of sqlalchemy did not take into consideration - the encryption-related pragmas passed in the url string, that were silently - ignored. This may cause errors when opening files saved by a - previous sqlalchemy version if the encryption options do not match. - - -Pooling Behavior ----------------- - -The driver makes a change to the default pool behavior of pysqlite -as described in :ref:`pysqlite_threading_pooling`. The pysqlcipher driver -has been observed to be significantly slower on connection than the -pysqlite driver, most likely due to the encryption overhead, so the -dialect here defaults to using the :class:`.SingletonThreadPool` -implementation, -instead of the :class:`.NullPool` pool used by pysqlite. As always, the pool -implementation is entirely configurable using the -:paramref:`_sa.create_engine.poolclass` parameter; the :class:`. -StaticPool` may -be more feasible for single-threaded use, or :class:`.NullPool` may be used -to prevent unencrypted connections from being held open for long periods of -time, at the expense of slower startup time for new connections. - - -""" # noqa - -from .pysqlite import SQLiteDialect_pysqlite -from ... import pool - - -class SQLiteDialect_pysqlcipher(SQLiteDialect_pysqlite): - driver = "pysqlcipher" - supports_statement_cache = True - - pragmas = ("kdf_iter", "cipher", "cipher_page_size", "cipher_use_hmac") - - @classmethod - def import_dbapi(cls): - try: - import sqlcipher3 as sqlcipher - except ImportError: - pass - else: - return sqlcipher - - from pysqlcipher3 import dbapi2 as sqlcipher - - return sqlcipher - - @classmethod - def get_pool_class(cls, url): - return pool.SingletonThreadPool - - def on_connect_url(self, url): - super_on_connect = super().on_connect_url(url) - - # pull the info we need from the URL early. Even though URL - # is immutable, we don't want any in-place changes to the URL - # to affect things - passphrase = url.password or "" - url_query = dict(url.query) - - def on_connect(conn): - cursor = conn.cursor() - cursor.execute('pragma key="%s"' % passphrase) - for prag in self.pragmas: - value = url_query.get(prag, None) - if value is not None: - cursor.execute('pragma %s="%s"' % (prag, value)) - cursor.close() - - if super_on_connect: - super_on_connect(conn) - - return on_connect - - def create_connect_args(self, url): - plain_url = url._replace(password=None) - plain_url = plain_url.difference_update_query(self.pragmas) - return super().create_connect_args(plain_url) - - -dialect = SQLiteDialect_pysqlcipher diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py deleted file mode 100644 index f39baf3..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py +++ /dev/null @@ -1,756 +0,0 @@ -# dialects/sqlite/pysqlite.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - - -r""" -.. dialect:: sqlite+pysqlite - :name: pysqlite - :dbapi: sqlite3 - :connectstring: sqlite+pysqlite:///file_path - :url: https://docs.python.org/library/sqlite3.html - - Note that ``pysqlite`` is the same driver as the ``sqlite3`` - module included with the Python distribution. - -Driver ------- - -The ``sqlite3`` Python DBAPI is standard on all modern Python versions; -for cPython and Pypy, no additional installation is necessary. - - -Connect Strings ---------------- - -The file specification for the SQLite database is taken as the "database" -portion of the URL. Note that the format of a SQLAlchemy url is:: - - driver://user:pass@host/database - -This means that the actual filename to be used starts with the characters to -the **right** of the third slash. So connecting to a relative filepath -looks like:: - - # relative path - e = create_engine('sqlite:///path/to/database.db') - -An absolute path, which is denoted by starting with a slash, means you -need **four** slashes:: - - # absolute path - e = create_engine('sqlite:////path/to/database.db') - -To use a Windows path, regular drive specifications and backslashes can be -used. Double backslashes are probably needed:: - - # absolute path on Windows - e = create_engine('sqlite:///C:\\path\\to\\database.db') - -To use sqlite ``:memory:`` database specify it as the filename using -``sqlite://:memory:``. It's also the default if no filepath is -present, specifying only ``sqlite://`` and nothing else:: - - # in-memory database - e = create_engine('sqlite://:memory:') - # also in-memory database - e2 = create_engine('sqlite://') - -.. _pysqlite_uri_connections: - -URI Connections -^^^^^^^^^^^^^^^ - -Modern versions of SQLite support an alternative system of connecting using a -`driver level URI `_, which has the advantage -that additional driver-level arguments can be passed including options such as -"read only". The Python sqlite3 driver supports this mode under modern Python -3 versions. The SQLAlchemy pysqlite driver supports this mode of use by -specifying "uri=true" in the URL query string. The SQLite-level "URI" is kept -as the "database" portion of the SQLAlchemy url (that is, following a slash):: - - e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true") - -.. note:: The "uri=true" parameter must appear in the **query string** - of the URL. It will not currently work as expected if it is only - present in the :paramref:`_sa.create_engine.connect_args` - parameter dictionary. - -The logic reconciles the simultaneous presence of SQLAlchemy's query string and -SQLite's query string by separating out the parameters that belong to the -Python sqlite3 driver vs. those that belong to the SQLite URI. This is -achieved through the use of a fixed list of parameters known to be accepted by -the Python side of the driver. For example, to include a URL that indicates -the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the -SQLite "mode" and "nolock" parameters, they can all be passed together on the -query string:: - - e = create_engine( - "sqlite:///file:path/to/database?" - "check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true" - ) - -Above, the pysqlite / sqlite3 DBAPI would be passed arguments as:: - - sqlite3.connect( - "file:path/to/database?mode=ro&nolock=1", - check_same_thread=True, timeout=10, uri=True - ) - -Regarding future parameters added to either the Python or native drivers. new -parameter names added to the SQLite URI scheme should be automatically -accommodated by this scheme. New parameter names added to the Python driver -side can be accommodated by specifying them in the -:paramref:`_sa.create_engine.connect_args` dictionary, -until dialect support is -added by SQLAlchemy. For the less likely case that the native SQLite driver -adds a new parameter name that overlaps with one of the existing, known Python -driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would -require adjustment for the URL scheme to continue to support this. - -As is always the case for all SQLAlchemy dialects, the entire "URL" process -can be bypassed in :func:`_sa.create_engine` through the use of the -:paramref:`_sa.create_engine.creator` -parameter which allows for a custom callable -that creates a Python sqlite3 driver level connection directly. - -.. versionadded:: 1.3.9 - -.. seealso:: - - `Uniform Resource Identifiers `_ - in - the SQLite documentation - -.. _pysqlite_regexp: - -Regular Expression Support ---------------------------- - -.. versionadded:: 1.4 - -Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided -using Python's re.search_ function. SQLite itself does not include a working -regular expression operator; instead, it includes a non-implemented placeholder -operator ``REGEXP`` that calls a user-defined function that must be provided. - -SQLAlchemy's implementation makes use of the pysqlite create_function_ hook -as follows:: - - - def regexp(a, b): - return re.search(a, b) is not None - - sqlite_connection.create_function( - "regexp", 2, regexp, - ) - -There is currently no support for regular expression flags as a separate -argument, as these are not supported by SQLite's REGEXP operator, however these -may be included inline within the regular expression string. See `Python regular expressions`_ for -details. - -.. seealso:: - - `Python regular expressions`_: Documentation for Python's regular expression syntax. - -.. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function - -.. _re.search: https://docs.python.org/3/library/re.html#re.search - -.. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search - - - -Compatibility with sqlite3 "native" date and datetime types ------------------------------------------------------------ - -The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and -sqlite3.PARSE_COLNAMES options, which have the effect of any column -or expression explicitly cast as "date" or "timestamp" will be converted -to a Python date or datetime object. The date and datetime types provided -with the pysqlite dialect are not currently compatible with these options, -since they render the ISO date/datetime including microseconds, which -pysqlite's driver does not. Additionally, SQLAlchemy does not at -this time automatically render the "cast" syntax required for the -freestanding functions "current_timestamp" and "current_date" to return -datetime/date types natively. Unfortunately, pysqlite -does not provide the standard DBAPI types in ``cursor.description``, -leaving SQLAlchemy with no way to detect these types on the fly -without expensive per-row type checks. - -Keeping in mind that pysqlite's parsing option is not recommended, -nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES -can be forced if one configures "native_datetime=True" on create_engine():: - - engine = create_engine('sqlite://', - connect_args={'detect_types': - sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES}, - native_datetime=True - ) - -With this flag enabled, the DATE and TIMESTAMP types (but note - not the -DATETIME or TIME types...confused yet ?) will not perform any bind parameter -or result processing. Execution of "func.current_date()" will return a string. -"func.current_timestamp()" is registered as returning a DATETIME type in -SQLAlchemy, so this function still receives SQLAlchemy-level result -processing. - -.. _pysqlite_threading_pooling: - -Threading/Pooling Behavior ---------------------------- - -The ``sqlite3`` DBAPI by default prohibits the use of a particular connection -in a thread which is not the one in which it was created. As SQLite has -matured, it's behavior under multiple threads has improved, and even includes -options for memory only databases to be used in multiple threads. - -The thread prohibition is known as "check same thread" and may be controlled -using the ``sqlite3`` parameter ``check_same_thread``, which will disable or -enable this check. SQLAlchemy's default behavior here is to set -``check_same_thread`` to ``False`` automatically whenever a file-based database -is in use, to establish compatibility with the default pool class -:class:`.QueuePool`. - -The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently -based on the kind of SQLite database that's requested: - -* When a ``:memory:`` SQLite database is specified, the dialect by default - will use :class:`.SingletonThreadPool`. This pool maintains a single - connection per thread, so that all access to the engine within the current - thread use the same ``:memory:`` database - other threads would access a - different ``:memory:`` database. The ``check_same_thread`` parameter - defaults to ``True``. -* When a file-based database is specified, the dialect will use - :class:`.QueuePool` as the source of connections. at the same time, - the ``check_same_thread`` flag is set to False by default unless overridden. - - .. versionchanged:: 2.0 - - SQLite file database engines now use :class:`.QueuePool` by default. - Previously, :class:`.NullPool` were used. The :class:`.NullPool` class - may be used by specifying it via the - :paramref:`_sa.create_engine.poolclass` parameter. - -Disabling Connection Pooling for File Databases -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Pooling may be disabled for a file based database by specifying the -:class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass` -parameter:: - - from sqlalchemy import NullPool - engine = create_engine("sqlite:///myfile.db", poolclass=NullPool) - -It's been observed that the :class:`.NullPool` implementation incurs an -extremely small performance overhead for repeated checkouts due to the lack of -connection re-use implemented by :class:`.QueuePool`. However, it still -may be beneficial to use this class if the application is experiencing -issues with files being locked. - -Using a Memory Database in Multiple Threads -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To use a ``:memory:`` database in a multithreaded scenario, the same -connection object must be shared among threads, since the database exists -only within the scope of that connection. The -:class:`.StaticPool` implementation will maintain a single connection -globally, and the ``check_same_thread`` flag can be passed to Pysqlite -as ``False``:: - - from sqlalchemy.pool import StaticPool - engine = create_engine('sqlite://', - connect_args={'check_same_thread':False}, - poolclass=StaticPool) - -Note that using a ``:memory:`` database in multiple threads requires a recent -version of SQLite. - -Using Temporary Tables with SQLite -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Due to the way SQLite deals with temporary tables, if you wish to use a -temporary table in a file-based SQLite database across multiple checkouts -from the connection pool, such as when using an ORM :class:`.Session` where -the temporary table should continue to remain after :meth:`.Session.commit` or -:meth:`.Session.rollback` is called, a pool which maintains a single -connection must be used. Use :class:`.SingletonThreadPool` if the scope is -only needed within the current thread, or :class:`.StaticPool` is scope is -needed within multiple threads for this case:: - - # maintain the same connection per thread - from sqlalchemy.pool import SingletonThreadPool - engine = create_engine('sqlite:///mydb.db', - poolclass=SingletonThreadPool) - - - # maintain the same connection across all threads - from sqlalchemy.pool import StaticPool - engine = create_engine('sqlite:///mydb.db', - poolclass=StaticPool) - -Note that :class:`.SingletonThreadPool` should be configured for the number -of threads that are to be used; beyond that number, connections will be -closed out in a non deterministic way. - - -Dealing with Mixed String / Binary Columns ------------------------------------------------------- - -The SQLite database is weakly typed, and as such it is possible when using -binary values, which in Python are represented as ``b'some string'``, that a -particular SQLite database can have data values within different rows where -some of them will be returned as a ``b''`` value by the Pysqlite driver, and -others will be returned as Python strings, e.g. ``''`` values. This situation -is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used -consistently, however if a particular SQLite database has data that was -inserted using the Pysqlite driver directly, or when using the SQLAlchemy -:class:`.String` type which was later changed to :class:`.LargeBinary`, the -table will not be consistently readable because SQLAlchemy's -:class:`.LargeBinary` datatype does not handle strings so it has no way of -"encoding" a value that is in string format. - -To deal with a SQLite table that has mixed string / binary data in the -same column, use a custom type that will check each row individually:: - - from sqlalchemy import String - from sqlalchemy import TypeDecorator - - class MixedBinary(TypeDecorator): - impl = String - cache_ok = True - - def process_result_value(self, value, dialect): - if isinstance(value, str): - value = bytes(value, 'utf-8') - elif value is not None: - value = bytes(value) - - return value - -Then use the above ``MixedBinary`` datatype in the place where -:class:`.LargeBinary` would normally be used. - -.. _pysqlite_serializable: - -Serializable isolation / Savepoints / Transactional DDL -------------------------------------------------------- - -In the section :ref:`sqlite_concurrency`, we refer to the pysqlite -driver's assortment of issues that prevent several features of SQLite -from working correctly. The pysqlite DBAPI driver has several -long-standing bugs which impact the correctness of its transactional -behavior. In its default mode of operation, SQLite features such as -SERIALIZABLE isolation, transactional DDL, and SAVEPOINT support are -non-functional, and in order to use these features, workarounds must -be taken. - -The issue is essentially that the driver attempts to second-guess the user's -intent, failing to start transactions and sometimes ending them prematurely, in -an effort to minimize the SQLite databases's file locking behavior, even -though SQLite itself uses "shared" locks for read-only activities. - -SQLAlchemy chooses to not alter this behavior by default, as it is the -long-expected behavior of the pysqlite driver; if and when the pysqlite -driver attempts to repair these issues, that will be more of a driver towards -defaults for SQLAlchemy. - -The good news is that with a few events, we can implement transactional -support fully, by disabling pysqlite's feature entirely and emitting BEGIN -ourselves. This is achieved using two event listeners:: - - from sqlalchemy import create_engine, event - - engine = create_engine("sqlite:///myfile.db") - - @event.listens_for(engine, "connect") - def do_connect(dbapi_connection, connection_record): - # disable pysqlite's emitting of the BEGIN statement entirely. - # also stops it from emitting COMMIT before any DDL. - dbapi_connection.isolation_level = None - - @event.listens_for(engine, "begin") - def do_begin(conn): - # emit our own BEGIN - conn.exec_driver_sql("BEGIN") - -.. warning:: When using the above recipe, it is advised to not use the - :paramref:`.Connection.execution_options.isolation_level` setting on - :class:`_engine.Connection` and :func:`_sa.create_engine` - with the SQLite driver, - as this function necessarily will also alter the ".isolation_level" setting. - - -Above, we intercept a new pysqlite connection and disable any transactional -integration. Then, at the point at which SQLAlchemy knows that transaction -scope is to begin, we emit ``"BEGIN"`` ourselves. - -When we take control of ``"BEGIN"``, we can also control directly SQLite's -locking modes, introduced at -`BEGIN TRANSACTION `_, -by adding the desired locking mode to our ``"BEGIN"``:: - - @event.listens_for(engine, "begin") - def do_begin(conn): - conn.exec_driver_sql("BEGIN EXCLUSIVE") - -.. seealso:: - - `BEGIN TRANSACTION `_ - - on the SQLite site - - `sqlite3 SELECT does not BEGIN a transaction `_ - - on the Python bug tracker - - `sqlite3 module breaks transactions and potentially corrupts data `_ - - on the Python bug tracker - -.. _pysqlite_udfs: - -User-Defined Functions ----------------------- - -pysqlite supports a `create_function() `_ -method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries. -These functions are registered with a specific DBAPI Connection. - -SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the -connection when it is created. That is accomplished with an event listener:: - - from sqlalchemy import create_engine - from sqlalchemy import event - from sqlalchemy import text - - - def udf(): - return "udf-ok" - - - engine = create_engine("sqlite:///./db_file") - - - @event.listens_for(engine, "connect") - def connect(conn, rec): - conn.create_function("udf", 0, udf) - - - for i in range(5): - with engine.connect() as conn: - print(conn.scalar(text("SELECT UDF()"))) - - -""" # noqa - -import math -import os -import re - -from .base import DATE -from .base import DATETIME -from .base import SQLiteDialect -from ... import exc -from ... import pool -from ... import types as sqltypes -from ... import util - - -class _SQLite_pysqliteTimeStamp(DATETIME): - def bind_processor(self, dialect): - if dialect.native_datetime: - return None - else: - return DATETIME.bind_processor(self, dialect) - - def result_processor(self, dialect, coltype): - if dialect.native_datetime: - return None - else: - return DATETIME.result_processor(self, dialect, coltype) - - -class _SQLite_pysqliteDate(DATE): - def bind_processor(self, dialect): - if dialect.native_datetime: - return None - else: - return DATE.bind_processor(self, dialect) - - def result_processor(self, dialect, coltype): - if dialect.native_datetime: - return None - else: - return DATE.result_processor(self, dialect, coltype) - - -class SQLiteDialect_pysqlite(SQLiteDialect): - default_paramstyle = "qmark" - supports_statement_cache = True - returns_native_bytes = True - - colspecs = util.update_copy( - SQLiteDialect.colspecs, - { - sqltypes.Date: _SQLite_pysqliteDate, - sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp, - }, - ) - - description_encoding = None - - driver = "pysqlite" - - @classmethod - def import_dbapi(cls): - from sqlite3 import dbapi2 as sqlite - - return sqlite - - @classmethod - def _is_url_file_db(cls, url): - if (url.database and url.database != ":memory:") and ( - url.query.get("mode", None) != "memory" - ): - return True - else: - return False - - @classmethod - def get_pool_class(cls, url): - if cls._is_url_file_db(url): - return pool.QueuePool - else: - return pool.SingletonThreadPool - - def _get_server_version_info(self, connection): - return self.dbapi.sqlite_version_info - - _isolation_lookup = SQLiteDialect._isolation_lookup.union( - { - "AUTOCOMMIT": None, - } - ) - - def set_isolation_level(self, dbapi_connection, level): - if level == "AUTOCOMMIT": - dbapi_connection.isolation_level = None - else: - dbapi_connection.isolation_level = "" - return super().set_isolation_level(dbapi_connection, level) - - def on_connect(self): - def regexp(a, b): - if b is None: - return None - return re.search(a, b) is not None - - if util.py38 and self._get_server_version_info(None) >= (3, 9): - # sqlite must be greater than 3.8.3 for deterministic=True - # https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function - # the check is more conservative since there were still issues - # with following 3.8 sqlite versions - create_func_kw = {"deterministic": True} - else: - create_func_kw = {} - - def set_regexp(dbapi_connection): - dbapi_connection.create_function( - "regexp", 2, regexp, **create_func_kw - ) - - def floor_func(dbapi_connection): - # NOTE: floor is optionally present in sqlite 3.35+ , however - # as it is normally non-present we deliver floor() unconditionally - # for now. - # https://www.sqlite.org/lang_mathfunc.html - dbapi_connection.create_function( - "floor", 1, math.floor, **create_func_kw - ) - - fns = [set_regexp, floor_func] - - def connect(conn): - for fn in fns: - fn(conn) - - return connect - - def create_connect_args(self, url): - if url.username or url.password or url.host or url.port: - raise exc.ArgumentError( - "Invalid SQLite URL: %s\n" - "Valid SQLite URL forms are:\n" - " sqlite:///:memory: (or, sqlite://)\n" - " sqlite:///relative/path/to/file.db\n" - " sqlite:////absolute/path/to/file.db" % (url,) - ) - - # theoretically, this list can be augmented, at least as far as - # parameter names accepted by sqlite3/pysqlite, using - # inspect.getfullargspec(). for the moment this seems like overkill - # as these parameters don't change very often, and as always, - # parameters passed to connect_args will always go to the - # sqlite3/pysqlite driver. - pysqlite_args = [ - ("uri", bool), - ("timeout", float), - ("isolation_level", str), - ("detect_types", int), - ("check_same_thread", bool), - ("cached_statements", int), - ] - opts = url.query - pysqlite_opts = {} - for key, type_ in pysqlite_args: - util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts) - - if pysqlite_opts.get("uri", False): - uri_opts = dict(opts) - # here, we are actually separating the parameters that go to - # sqlite3/pysqlite vs. those that go the SQLite URI. What if - # two names conflict? again, this seems to be not the case right - # now, and in the case that new names are added to - # either side which overlap, again the sqlite3/pysqlite parameters - # can be passed through connect_args instead of in the URL. - # If SQLite native URIs add a parameter like "timeout" that - # we already have listed here for the python driver, then we need - # to adjust for that here. - for key, type_ in pysqlite_args: - uri_opts.pop(key, None) - filename = url.database - if uri_opts: - # sorting of keys is for unit test support - filename += "?" + ( - "&".join( - "%s=%s" % (key, uri_opts[key]) - for key in sorted(uri_opts) - ) - ) - else: - filename = url.database or ":memory:" - if filename != ":memory:": - filename = os.path.abspath(filename) - - pysqlite_opts.setdefault( - "check_same_thread", not self._is_url_file_db(url) - ) - - return ([filename], pysqlite_opts) - - def is_disconnect(self, e, connection, cursor): - return isinstance( - e, self.dbapi.ProgrammingError - ) and "Cannot operate on a closed database." in str(e) - - -dialect = SQLiteDialect_pysqlite - - -class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite): - """numeric dialect for testing only - - internal use only. This dialect is **NOT** supported by SQLAlchemy - and may change at any time. - - """ - - supports_statement_cache = True - default_paramstyle = "numeric" - driver = "pysqlite_numeric" - - _first_bind = ":1" - _not_in_statement_regexp = None - - def __init__(self, *arg, **kw): - kw.setdefault("paramstyle", "numeric") - super().__init__(*arg, **kw) - - def create_connect_args(self, url): - arg, opts = super().create_connect_args(url) - opts["factory"] = self._fix_sqlite_issue_99953() - return arg, opts - - def _fix_sqlite_issue_99953(self): - import sqlite3 - - first_bind = self._first_bind - if self._not_in_statement_regexp: - nis = self._not_in_statement_regexp - - def _test_sql(sql): - m = nis.search(sql) - assert not m, f"Found {nis.pattern!r} in {sql!r}" - - else: - - def _test_sql(sql): - pass - - def _numeric_param_as_dict(parameters): - if parameters: - assert isinstance(parameters, tuple) - return { - str(idx): value for idx, value in enumerate(parameters, 1) - } - else: - return () - - class SQLiteFix99953Cursor(sqlite3.Cursor): - def execute(self, sql, parameters=()): - _test_sql(sql) - if first_bind in sql: - parameters = _numeric_param_as_dict(parameters) - return super().execute(sql, parameters) - - def executemany(self, sql, parameters): - _test_sql(sql) - if first_bind in sql: - parameters = [ - _numeric_param_as_dict(p) for p in parameters - ] - return super().executemany(sql, parameters) - - class SQLiteFix99953Connection(sqlite3.Connection): - def cursor(self, factory=None): - if factory is None: - factory = SQLiteFix99953Cursor - return super().cursor(factory=factory) - - def execute(self, sql, parameters=()): - _test_sql(sql) - if first_bind in sql: - parameters = _numeric_param_as_dict(parameters) - return super().execute(sql, parameters) - - def executemany(self, sql, parameters): - _test_sql(sql) - if first_bind in sql: - parameters = [ - _numeric_param_as_dict(p) for p in parameters - ] - return super().executemany(sql, parameters) - - return SQLiteFix99953Connection - - -class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric): - """numeric dialect that uses $ for testing only - - internal use only. This dialect is **NOT** supported by SQLAlchemy - and may change at any time. - - """ - - supports_statement_cache = True - default_paramstyle = "numeric_dollar" - driver = "pysqlite_dollar" - - _first_bind = "$1" - _not_in_statement_regexp = re.compile(r"[^\d]:\d+") - - def __init__(self, *arg, **kw): - kw.setdefault("paramstyle", "numeric_dollar") - super().__init__(*arg, **kw) diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt b/venv/lib/python3.11/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt deleted file mode 100644 index e6be205..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt +++ /dev/null @@ -1,145 +0,0 @@ -Rules for Migrating TypeEngine classes to 0.6 ---------------------------------------------- - -1. the TypeEngine classes are used for: - - a. Specifying behavior which needs to occur for bind parameters - or result row columns. - - b. Specifying types that are entirely specific to the database - in use and have no analogue in the sqlalchemy.types package. - - c. Specifying types where there is an analogue in sqlalchemy.types, - but the database in use takes vendor-specific flags for those - types. - - d. If a TypeEngine class doesn't provide any of this, it should be - *removed* from the dialect. - -2. the TypeEngine classes are *no longer* used for generating DDL. Dialects -now have a TypeCompiler subclass which uses the same visit_XXX model as -other compilers. - -3. the "ischema_names" and "colspecs" dictionaries are now required members on -the Dialect class. - -4. The names of types within dialects are now important. If a dialect-specific type -is a subclass of an existing generic type and is only provided for bind/result behavior, -the current mixed case naming can remain, i.e. _PGNumeric for Numeric - in this case, -end users would never need to use _PGNumeric directly. However, if a dialect-specific -type is specifying a type *or* arguments that are not present generically, it should -match the real name of the type on that backend, in uppercase. E.g. postgresql.INET, -mysql.ENUM, postgresql.ARRAY. - -Or follow this handy flowchart: - - is the type meant to provide bind/result is the type the same name as an - behavior to a generic type (i.e. MixedCase) ---- no ---> UPPERCASE type in types.py ? - type in types.py ? | | - | no yes - yes | | - | | does your type need special - | +<--- yes --- behavior or arguments ? - | | | - | | no - name the type using | | - _MixedCase, i.e. v V - _OracleBoolean. it name the type don't make a - stays private to the dialect identically as that type, make sure the dialect's - and is invoked *only* via within the DB, base.py imports the types.py - the colspecs dict. using UPPERCASE UPPERCASE name into its namespace - | (i.e. BIT, NCHAR, INTERVAL). - | Users can import it. - | | - v v - subclass the closest is the name of this type - MixedCase type types.py, identical to an UPPERCASE - i.e. <--- no ------- name in types.py ? - class _DateTime(types.DateTime), - class DATETIME2(types.DateTime), | - class BIT(types.TypeEngine). yes - | - v - the type should - subclass the - UPPERCASE - type in types.py - (i.e. class BLOB(types.BLOB)) - - -Example 1. pysqlite needs bind/result processing for the DateTime type in types.py, -which applies to all DateTimes and subclasses. It's named _SLDateTime and -subclasses types.DateTime. - -Example 2. MS-SQL has a TIME type which takes a non-standard "precision" argument -that is rendered within DDL. So it's named TIME in the MS-SQL dialect's base.py, -and subclasses types.TIME. Users can then say mssql.TIME(precision=10). - -Example 3. MS-SQL dialects also need special bind/result processing for date -But its DATE type doesn't render DDL differently than that of a plain -DATE, i.e. it takes no special arguments. Therefore we are just adding behavior -to types.Date, so it's named _MSDate in the MS-SQL dialect's base.py, and subclasses -types.Date. - -Example 4. MySQL has a SET type, there's no analogue for this in types.py. So -MySQL names it SET in the dialect's base.py, and it subclasses types.String, since -it ultimately deals with strings. - -Example 5. PostgreSQL has a DATETIME type. The DBAPIs handle dates correctly, -and no special arguments are used in PG's DDL beyond what types.py provides. -PostgreSQL dialect therefore imports types.DATETIME into its base.py. - -Ideally one should be able to specify a schema using names imported completely from a -dialect, all matching the real name on that backend: - - from sqlalchemy.dialects.postgresql import base as pg - - t = Table('mytable', metadata, - Column('id', pg.INTEGER, primary_key=True), - Column('name', pg.VARCHAR(300)), - Column('inetaddr', pg.INET) - ) - -where above, the INTEGER and VARCHAR types are ultimately from sqlalchemy.types, -but the PG dialect makes them available in its own namespace. - -5. "colspecs" now is a dictionary of generic or uppercased types from sqlalchemy.types -linked to types specified in the dialect. Again, if a type in the dialect does not -specify any special behavior for bind_processor() or result_processor() and does not -indicate a special type only available in this database, it must be *removed* from the -module and from this dictionary. - -6. "ischema_names" indicates string descriptions of types as returned from the database -linked to TypeEngine classes. - - a. The string name should be matched to the most specific type possible within - sqlalchemy.types, unless there is no matching type within sqlalchemy.types in which - case it points to a dialect type. *It doesn't matter* if the dialect has its - own subclass of that type with special bind/result behavior - reflect to the types.py - UPPERCASE type as much as possible. With very few exceptions, all types - should reflect to an UPPERCASE type. - - b. If the dialect contains a matching dialect-specific type that takes extra arguments - which the generic one does not, then point to the dialect-specific type. E.g. - mssql.VARCHAR takes a "collation" parameter which should be preserved. - -5. DDL, or what was formerly issued by "get_col_spec()", is now handled exclusively by -a subclass of compiler.GenericTypeCompiler. - - a. your TypeCompiler class will receive generic and uppercase types from - sqlalchemy.types. Do not assume the presence of dialect-specific attributes on - these types. - - b. the visit_UPPERCASE methods on GenericTypeCompiler should *not* be overridden with - methods that produce a different DDL name. Uppercase types don't do any kind of - "guessing" - if visit_TIMESTAMP is called, the DDL should render as TIMESTAMP in - all cases, regardless of whether or not that type is legal on the backend database. - - c. the visit_UPPERCASE methods *should* be overridden with methods that add additional - arguments and flags to those types. - - d. the visit_lowercase methods are overridden to provide an interpretation of a generic - type. E.g. visit_large_binary() might be overridden to say "return self.visit_BIT(type_)". - - e. visit_lowercase methods should *never* render strings directly - it should always - be via calling a visit_UPPERCASE() method. -- cgit v1.2.3