diff --git a/docker-jans-all-in-one/Dockerfile b/docker-jans-all-in-one/Dockerfile index 18d58301c80..cde1b43d0cb 100644 --- a/docker-jans-all-in-one/Dockerfile +++ b/docker-jans-all-in-one/Dockerfile @@ -59,7 +59,7 @@ RUN apk update \ # Assets sync # =========== -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad # note that as we're pulling from a monorepo (with multiple project in it) # we are using partial-clone and sparse-checkout to get the assets diff --git a/docker-jans-auth-server/Dockerfile b/docker-jans-auth-server/Dockerfile index 0a5fd91a640..b73e7147e2c 100644 --- a/docker-jans-auth-server/Dockerfile +++ b/docker-jans-auth-server/Dockerfile @@ -103,7 +103,7 @@ RUN mkdir -p ${JETTY_BASE}/jans-auth/agama/fl \ /app/static/rdbm \ /app/schema -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup # note that as we're pulling from a monorepo (with multiple project in it) diff --git a/docker-jans-casa/Dockerfile b/docker-jans-casa/Dockerfile index 3b04b41e14a..7180c358068 100644 --- a/docker-jans-casa/Dockerfile +++ b/docker-jans-casa/Dockerfile @@ -61,7 +61,7 @@ RUN mkdir -p /app/static/rdbm \ /app/schema \ /app/templates/jans-casa -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup # note that as we're pulling from a monorepo (with multiple project in it) diff --git a/docker-jans-cloudtools/Dockerfile b/docker-jans-cloudtools/Dockerfile index d2dac0b55f1..ccb26873f1b 100644 --- a/docker-jans-cloudtools/Dockerfile +++ b/docker-jans-cloudtools/Dockerfile @@ -44,7 +44,7 @@ RUN wget -q https://repo1.maven.org/maven2/org/codehaus/janino/janino/3.1.9/jani # Assets sync # =========== -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad # note that as we're pulling from a monorepo (with multiple project in it) # we are using partial-clone and sparse-checkout to get the assets diff --git a/docker-jans-cloudtools/scripts/cleaner.py b/docker-jans-cloudtools/scripts/cleaner.py index 9041fa8091b..a85c87f497e 100644 --- a/docker-jans-cloudtools/scripts/cleaner.py +++ b/docker-jans-cloudtools/scripts/cleaner.py @@ -35,10 +35,10 @@ def cleanup(limit): try: if client.dialect == "mysql": - query = text(f"DELETE FROM {client.quoted_id(table)} WHERE del = :deleted AND exp < NOW() LIMIT {limit}") # nosec: B608 + query = f"DELETE FROM {client.quoted_id(table)} WHERE del = :deleted AND exp < NOW() LIMIT {limit}" # nosec: B608 else: # likely postgres - query = text(f"DELETE FROM {client.quoted_id(table)} WHERE doc_id IN (SELECT doc_id FROM {client.quoted_id(table)} WHERE del = :deleted AND exp < NOW() LIMIT {limit})") # nosec: B608 - conn.execute(query, {"deleted": True}) + query = f"DELETE FROM {client.quoted_id(table)} WHERE doc_id IN (SELECT doc_id FROM {client.quoted_id(table)} WHERE del = :deleted AND exp < NOW() LIMIT {limit})" # nosec: B608 + conn.execute(text(query), {"deleted": True}) logger.info(f"Cleanup expired entries in {table}") except Exception as exc: logger.warning(f"Unable to cleanup expired entries in {table}; reason={exc}") diff --git a/docker-jans-config-api/Dockerfile b/docker-jans-config-api/Dockerfile index c1a7a8b261d..f9654b33082 100644 --- a/docker-jans-config-api/Dockerfile +++ b/docker-jans-config-api/Dockerfile @@ -74,7 +74,7 @@ RUN mkdir -p /etc/jans/conf \ /usr/share/java \ /opt/jans/bin -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup ARG JANS_CONFIG_API_RESOURCES=jans-config-api/server/src/main/resources diff --git a/docker-jans-configurator/Dockerfile b/docker-jans-configurator/Dockerfile index d66c513ed74..16679fb51dc 100644 --- a/docker-jans-configurator/Dockerfile +++ b/docker-jans-configurator/Dockerfile @@ -27,7 +27,7 @@ RUN mkdir -p /opt/jans/configurator/javalibs \ # Assets sync # =========== -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG GIT_CLONE_DEPTH=100 WORKDIR /tmp/jans diff --git a/docker-jans-fido2/Dockerfile b/docker-jans-fido2/Dockerfile index 6f8b8cfc3b8..99c68093f39 100644 --- a/docker-jans-fido2/Dockerfile +++ b/docker-jans-fido2/Dockerfile @@ -67,7 +67,7 @@ RUN mkdir -p /etc/jans/conf \ /app/templates/jans-fido2 \ /app/static/fido2 -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup # note that as we're pulling from a monorepo (with multiple project in it) diff --git a/docker-jans-link/Dockerfile b/docker-jans-link/Dockerfile index 1da51b6e50e..0e48694649c 100644 --- a/docker-jans-link/Dockerfile +++ b/docker-jans-link/Dockerfile @@ -63,7 +63,7 @@ RUN mkdir -p /etc/jans/conf \ /app/schema \ /app/templates/jans-link -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup # note that as we're pulling from a monorepo (with multiple project in it) diff --git a/docker-jans-persistence-loader/Dockerfile b/docker-jans-persistence-loader/Dockerfile index e045ebf5520..ef73e93d503 100644 --- a/docker-jans-persistence-loader/Dockerfile +++ b/docker-jans-persistence-loader/Dockerfile @@ -18,7 +18,7 @@ RUN apk update \ RUN mkdir -p /app/static /app/schema /app/static/opendj /app/templates # janssenproject/jans SHA commit -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup ARG JANS_SCRIPT_CATALOG_DIR=docs/script-catalog ARG JANS_CONFIG_API_RESOURCES=jans-config-api/server/src/main/resources diff --git a/docker-jans-persistence-loader/scripts/sql_setup.py b/docker-jans-persistence-loader/scripts/sql_setup.py index 1e6f195bf06..759e828ced7 100644 --- a/docker-jans-persistence-loader/scripts/sql_setup.py +++ b/docker-jans-persistence-loader/scripts/sql_setup.py @@ -264,8 +264,9 @@ def column_to_multivalued(table_name, col_name): # to change the storage format of a JSON column, drop the column and # add the column back specifying the new storage format with self.client.engine.connect() as conn: - conn.execute(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP COLUMN {self.client.quoted_id(col_name)}") - conn.execute(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}") + with conn.begin(): + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP COLUMN {self.client.quoted_id(col_name)}")) + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}")) # force-reload metadata as we may have changed the schema before migrating old data self.client._metadata = None @@ -287,7 +288,8 @@ def add_column(table_name, col_name): data_type = self.get_data_type(col_name, table_name) with self.client.engine.connect() as conn: - conn.execute(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}") + with conn.begin(): + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}")) def change_column_type(table_name, col_name, old_data_type, data_type): if self.client.dialect == "mysql": @@ -298,27 +300,28 @@ def change_column_type(table_name, col_name, old_data_type, data_type): f"ALTER COLUMN {self.client.quoted_id(col_name)} TYPE {data_type}" with self.client.engine.connect() as conn: - # mysql will raise error if changing type to text but the column already indexed without explicit key length - # hence the associated index must be dropped first - if self.client.dialect == "mysql" and old_data_type.startswith("VARCHAR") and data_type == "TEXT": - for idx in conn.execute( - text( - "SELECT index_name " - "FROM information_schema.statistics " - "WHERE table_name = :table_name " - "AND index_name LIKE :index_name " - "AND column_name = :col_name;" - ), - { - "table_name": table_name, - "index_name": f"{table_name}_{col_name}", - "col_name": col_name - }, - ): - conn.execute(f"ALTER TABLE {table_name} DROP INDEX {idx[0]}") - - # change the type - conn.execute(query) + with conn.begin(): + # mysql will raise error if changing type to text but the column already indexed without explicit key length + # hence the associated index must be dropped first + if self.client.dialect == "mysql" and old_data_type.startswith("VARCHAR") and data_type == "TEXT": + for idx in conn.execute( + text( + "SELECT index_name " + "FROM information_schema.statistics " + "WHERE table_name = :table_name " + "AND index_name LIKE :index_name " + "AND column_name = :col_name;" + ), + { + "table_name": table_name, + "index_name": f"{table_name}_{col_name}", + "col_name": col_name + }, + ): + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP INDEX {self.client.quoted_id(idx[0])}")) + + # change the type + conn.execute(text(query)) def column_from_multivalued(table_name, col_name): old_data_type = table_mapping[table_name][col_name] @@ -334,29 +337,30 @@ def column_from_multivalued(table_name, col_name): } with self.client.engine.connect() as conn: - # mysql will raise error if dropping column which has functional index, - # hence the associated index must be dropped first - if self.client.dialect == "mysql": - for idx in conn.execute( - text( - "SELECT index_name " - "FROM information_schema.statistics " - "WHERE table_name = :table_name " - "AND index_name LIKE :index_name '%' " - "AND expression LIKE '%' :col_name '%';" - ), - { - "table_name": table_name, - "index_name": f"{table_name}_json_", - "col_name": col_name - }, - ): - conn.execute(f"ALTER TABLE {table_name} DROP INDEX {idx[0]}") - - # to change the storage format of a JSON column, drop the column and - # add the column back specifying the new storage format - conn.execute(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP COLUMN {self.client.quoted_id(col_name)}") - conn.execute(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}") + with conn.begin(): + # mysql will raise error if dropping column which has functional index, + # hence the associated index must be dropped first + if self.client.dialect == "mysql": + for idx in conn.execute( + text( + "SELECT index_name " + "FROM information_schema.statistics " + "WHERE table_name = :table_name " + "AND index_name LIKE :index_name " + "AND expression LIKE :col_name;" + ), + { + "table_name": table_name, + "index_name": f"{table_name}_json_%", + "col_name": f"%{col_name}%" + }, + ): + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP INDEX {self.client.quoted_id(idx[0])}")) + + # to change the storage format of a JSON column, drop the column and + # add the column back specifying the new storage format + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} DROP COLUMN {self.client.quoted_id(col_name)}")) + conn.execute(text(f"ALTER TABLE {self.client.quoted_id(table_name)} ADD COLUMN {self.client.quoted_id(col_name)} {data_type}")) # force-reload metadata as we may have changed the schema before migrating old data self.client._metadata = None diff --git a/docker-jans-saml/Dockerfile b/docker-jans-saml/Dockerfile index 8a8bfc652d7..a1a1749865e 100644 --- a/docker-jans-saml/Dockerfile +++ b/docker-jans-saml/Dockerfile @@ -39,7 +39,7 @@ RUN mkdir -p /app/static/rdbm \ /app/schema \ /app/templates/jans-saml -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup # note that as we're pulling from a monorepo (with multiple project in it) diff --git a/docker-jans-saml/scripts/configure_kc.py b/docker-jans-saml/scripts/configure_kc.py index 45c2664dfe0..eeb70d7daea 100644 --- a/docker-jans-saml/scripts/configure_kc.py +++ b/docker-jans-saml/scripts/configure_kc.py @@ -1,4 +1,3 @@ -import base64 import os import json import logging.config @@ -358,8 +357,8 @@ def __init__(self, manager): hide_parameters=True, ) - metadata = MetaData(bind=self.engine) - metadata.reflect() + metadata = MetaData() + metadata.reflect(self.engine) @property def xa_grant_name(self): @@ -370,7 +369,7 @@ def check_xa_recover_admin(self): with self.engine.connect() as conn: query = text("SHOW GRANTS FOR :username") - for grant in conn.execute(query, username=self.user): + for grant in conn.execute(query, {"username": self.user}): if self.xa_grant_name in grant[0]: granted = True break @@ -385,10 +384,12 @@ def grant_xa_recover_admin(self): f"and KC_DB is set to 'mysql'; trying to grant required privilege {self.xa_grant_name} to {self.user!r} user ..." ) - query = text("GRANT :grant_name ON *.* TO :username@'%';") + # Note: SQL identifiers (privilege names, table names) cannot be bound parameters + # Use quoted_identifier or direct interpolation for privilege name, bind for user + query = text(f"GRANT {self.xa_grant_name} ON *.* TO :username@'%';") try: - conn.execute(query, grant_name=self.xa_grant_name, username=self.user) + conn.execute(query, {"username": self.user}) except OperationalError as exc: logger.warning(f"Unable to grant {self.xa_grant_name} privilege to {self.user!r} user; reason={exc.orig.args[1]}") diff --git a/docker-jans-scim/Dockerfile b/docker-jans-scim/Dockerfile index b4f027a225a..14719644e07 100644 --- a/docker-jans-scim/Dockerfile +++ b/docker-jans-scim/Dockerfile @@ -62,7 +62,7 @@ RUN mkdir -p /etc/jans/conf \ /app/schema \ /app/templates/jans-scim -ENV JANS_SOURCE_VERSION=6bcc41a0e7f2708e52fe2c950d357dc872b87498 +ENV JANS_SOURCE_VERSION=436bf147b379c2bf005f554db4b8cce3971c58ad ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup ARG JANS_SCIM_RESOURCE_DIR=jans-scim/server/src/main/resources diff --git a/jans-pycloudlib/jans/pycloudlib/lock/sql_lock.py b/jans-pycloudlib/jans/pycloudlib/lock/sql_lock.py index 46e21f6f469..2a24bee28b4 100644 --- a/jans-pycloudlib/jans/pycloudlib/lock/sql_lock.py +++ b/jans-pycloudlib/jans/pycloudlib/lock/sql_lock.py @@ -39,9 +39,9 @@ def _prepare_table(self, table_name) -> None: raise_on_error = False # if error is not about duplicated table, force raising exception - if self._dialect in ("pgsql", "postgresql") and exc.orig.pgcode != "42P07": + if self.client.dialect in ("pgsql", "postgresql") and exc.orig.pgcode != "42P07": raise_on_error = True - elif self._dialect == "mysql" and exc.orig.args[0] != 1050: + elif self.client.dialect == "mysql" and exc.orig.args[0] != 1050: raise_on_error = True if raise_on_error: @@ -69,14 +69,14 @@ def get(self, key: str) -> dict[str, _t.Any]: Returns: Mapping of lock data. """ - stmt = select([self.table]).where(self.table.c.doc_id == key).limit(1) + stmt = select(self.table).where(self.table.c.doc_id == key).limit(1) with self.client.engine.connect() as conn: result = conn.execute(stmt) entry = result.fetchone() if entry: - rowset = dict(entry) + rowset = dict(entry._mapping) return json.loads(rowset["jansData"]) | {"name": rowset["doc_id"]} return {} @@ -98,12 +98,13 @@ def post(self, key: str, owner: str, ttl: float, updated_at: str) -> bool: ) with self.client.engine.connect() as conn: - try: - result = conn.execute(stmt) - created = bool(result.inserted_primary_key) - except IntegrityError: - created = False - return created + with conn.begin(): + try: + result = conn.execute(stmt) + created = bool(result.inserted_primary_key) + except IntegrityError: + created = False + return created def put(self, key: str, owner: str, ttl: float, updated_at: str) -> bool: """Update specific lock. @@ -122,8 +123,9 @@ def put(self, key: str, owner: str, ttl: float, updated_at: str) -> bool: ) with self.client.engine.connect() as conn: - result = conn.execute(stmt) - return bool(result.rowcount) + with conn.begin(): + result = conn.execute(stmt) + return bool(result.rowcount) def delete(self, key: str) -> bool: """Delete specific lock. @@ -137,8 +139,9 @@ def delete(self, key: str) -> bool: stmt = self.table.delete().where(self.table.c.doc_id == key) with self.client.engine.connect() as conn: - result = conn.execute(stmt) - return bool(result.rowcount) + with conn.begin(): + result = conn.execute(stmt) + return bool(result.rowcount) def connected(self) -> bool: """Check if connection is established. diff --git a/jans-pycloudlib/jans/pycloudlib/persistence/sql.py b/jans-pycloudlib/jans/pycloudlib/persistence/sql.py index abe3b502d69..5330d558ac3 100644 --- a/jans-pycloudlib/jans/pycloudlib/persistence/sql.py +++ b/jans-pycloudlib/jans/pycloudlib/persistence/sql.py @@ -24,6 +24,7 @@ from sqlalchemy import select from sqlalchemy import delete from sqlalchemy import event +from sqlalchemy import text from sqlalchemy.engine.url import URL from sqlalchemy.dialects.mysql import insert as mysql_insert from sqlalchemy.dialects.postgresql import insert as postgres_insert @@ -645,13 +646,14 @@ def engine_url(self) -> URL: Note: When Cloud SQL Connector is enabled, this URL is not used. The connection is handled by the connector's creator function instead. """ - return URL( + return URL.create( drivername=self.adapter.connector, username=os.environ.get("CN_SQL_DB_USER", "jans"), password=get_sql_password(self.manager), host=os.environ.get("CN_SQL_DB_HOST", "localhost"), port=int(os.environ.get("CN_SQL_DB_PORT", "3306")), database=os.environ.get("CN_SQL_DB_NAME", "jans"), + query={}, ) @property @@ -670,14 +672,14 @@ def metadata(self) -> MetaData: ) # do reflection on database table - self._metadata = MetaData(bind=self.engine) - self._metadata.reflect() + self._metadata = MetaData() + self._metadata.reflect(self.engine) return self._metadata def connected(self) -> bool: """Check whether connection is alive by executing simple query.""" with self.engine.connect() as conn: - result = conn.execute("SELECT 1 AS is_alive") + result = conn.execute(text("SELECT 1 AS is_alive")) return bool(result.fetchone()[0] > 0) def create_table(self, table_name: str, column_mapping: dict[str, str], pk_column: str) -> None: @@ -695,12 +697,13 @@ def create_table(self, table_name: str, column_mapping: dict[str, str], pk_colum query = f"CREATE TABLE {self.quoted_id(table_name)} ({columns_fmt}, {pk_def})" with self.engine.connect() as conn: - try: - conn.execute(query) - # refresh metadata as we have newly created table - self.metadata.reflect() - except DatabaseError as exc: # noqa: B902 - self.adapter.on_create_table_error(exc) + with conn.begin(): + try: + conn.execute(text(query)) + # refresh metadata as we have newly created table + self.metadata.reflect(conn) + except DatabaseError as exc: + self.adapter.on_create_table_error(exc) def get_table_mapping(self) -> dict[str, dict[str, str]]: """Get mapping of column name and type from all tables.""" @@ -734,10 +737,11 @@ def get_table_mapping(self) -> dict[str, dict[str, str]]: def create_index(self, query: str) -> None: """Create index using raw query.""" with self.engine.connect() as conn: - try: - conn.execute(query) - except DatabaseError as exc: # noqa: B902 - self.adapter.on_create_index_error(exc) + with conn.begin(): + try: + conn.execute(text(query)) + except DatabaseError as exc: + self.adapter.on_create_index_error(exc) def quoted_id(self, identifier: str) -> str: """Get quoted identifier name.""" @@ -749,7 +753,7 @@ def row_exists(self, table_name: str, id_: str) -> bool: if table is None: return False - query = select([func.count()]).select_from(table).where( + query = select(func.count()).select_from(table).where( table.c.doc_id == id_ ) with self.engine.connect() as conn: @@ -763,10 +767,11 @@ def insert_into(self, table_name: str, column_mapping: dict[str, _t.Any]) -> Non query = table.insert().values(column_mapping) with self.engine.connect() as conn: - try: - conn.execute(query) - except DatabaseError as exc: # noqa: B902 - self.adapter.on_insert_into_error(exc) + with conn.begin(): + try: + conn.execute(query) + except DatabaseError as exc: + self.adapter.on_insert_into_error(exc) def get(self, table_name: str, id_: str, column_names: _t.Union[list[str], None] = None) -> dict[str, _t.Any]: """Get a row from a table with matching ID.""" @@ -774,20 +779,18 @@ def get(self, table_name: str, id_: str, column_names: _t.Union[list[str], None] attrs = column_names or [] if attrs: - cols = [table.c[attr] for attr in attrs] + _select = select(*[table.c[attr] for attr in attrs]) else: - cols = [table] + _select = select(table) - query = select(cols).select_from(table).where( - table.c.doc_id == id_ - ) + query = _select.where(table.c.doc_id == id_) with self.engine.connect() as conn: result = conn.execute(query) entry = result.fetchone() if not entry: return {} - return dict(entry) + return dict(entry._mapping) def update(self, table_name: str, id_: str, column_mapping: dict[str, _t.Any]) -> bool: """Update a table row with matching ID.""" @@ -795,7 +798,8 @@ def update(self, table_name: str, id_: str, column_mapping: dict[str, _t.Any]) - query = table.update().where(table.c.doc_id == id_).values(column_mapping) with self.engine.connect() as conn: - result = conn.execute(query) + with conn.begin(): + result = conn.execute(query) return bool(result.rowcount) def search(self, table_name: str, column_names: _t.Union[list[str], None] = None) -> _t.Iterator[dict[str, _t.Any]]: @@ -804,21 +808,21 @@ def search(self, table_name: str, column_names: _t.Union[list[str], None] = None attrs = column_names or [] if attrs: - cols = [table.c[attr] for attr in attrs] + query = select(*[table.c[attr] for attr in attrs]) else: - cols = [table] + query = select(table) - query = select(cols).select_from(table) with self.engine.connect() as conn: result = conn.execute(query) for entry in result: - yield dict(entry) + yield dict(entry._mapping) @property def server_version(self) -> str: """Display server version.""" - version: str = self.engine.scalar(self.adapter.server_version_query) - return version + with self.engine.connect() as conn: + version: str = conn.scalar(text(self.adapter.server_version_query)) + return version def _transform_value(self, key: str, values: _t.Any) -> _t.Any: """Transform value from one to another based on its data type. @@ -948,8 +952,9 @@ def delete(self, table_name: str, id_: str) -> bool: query = delete(table).where(table.c.doc_id == id_) with self.engine.connect() as conn: - result = conn.execute(query) - return bool(result.rowcount) + with conn.begin(): + result = conn.execute(query) + return bool(result.rowcount) @property def use_simple_json(self): @@ -980,8 +985,9 @@ def upsert(self, table_name: str, column_mapping: dict[str, _t.Any]) -> None: column_mapping = self._apply_json_defaults(table, column_mapping) with self.engine.connect() as conn: - query = self.adapter.upsert_query(table, column_mapping, update_mapping) - conn.execute(query) + with conn.begin(): + query = self.adapter.upsert_query(table, column_mapping, update_mapping) + conn.execute(query) def upsert_from_file( self, diff --git a/jans-pycloudlib/pyproject.toml b/jans-pycloudlib/pyproject.toml index ec0dbd068dc..755a8d712dc 100644 --- a/jans-pycloudlib/pyproject.toml +++ b/jans-pycloudlib/pyproject.toml @@ -52,7 +52,7 @@ dependencies = [ "cryptography>=2.8", "google-cloud-secret-manager>=2.2.0", "pymysql>=1.0.2", - "sqlalchemy>=1.3,<2.0", + "sqlalchemy>=2.0", "psycopg2>=2.8.6", "Click>=6.7", "ldif>=4.1.1",