diff --git a/changelogs/fragments/591-mysql_info-db_tables_count.yml b/changelogs/fragments/591-mysql_info-db_tables_count.yml new file mode 100644 index 00000000..abbc1cbc --- /dev/null +++ b/changelogs/fragments/591-mysql_info-db_tables_count.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - mysql_info - adds the count of tables for each database to the returned values. It is possible to exclude this new field using the ``db_table_count`` exclusion filter. (https://github.com/ansible-collections/community.mysql/pull/691) diff --git a/plugins/modules/mysql_info.py b/plugins/modules/mysql_info.py index 3a30597c..8c3845d3 100644 --- a/plugins/modules/mysql_info.py +++ b/plugins/modules/mysql_info.py @@ -35,7 +35,7 @@ exclude_fields: description: - List of fields which are not needed to collect. - - "Supports elements: C(db_size). Unsupported elements will be ignored." + - "Supports elements: C(db_size), C(db_table_count). Unsupported elements will be ignored." type: list elements: str version_added: '0.1.0' @@ -204,13 +204,19 @@ returned: if not excluded by filter type: dict sample: - - { "mysql": { "size": 656594 }, "information_schema": { "size": 73728 } } + - { "mysql": { "size": 656594, "tables": 31 }, "information_schema": { "size": 73728, "tables": 79 } } contains: size: description: Database size in bytes. returned: if not excluded by filter type: dict sample: { 'size': 656594 } + tables: + description: Count of tables and views in that database. + returned: if not excluded by filter + type: dict + sample: { 'tables': 12 } + version_added: '3.11.0' settings: description: Global settings (variables) information. returned: if not excluded by filter @@ -656,40 +662,39 @@ def __get_users_info(self): def __get_databases(self, exclude_fields, return_empty_dbs): """Get info about databases.""" - if not exclude_fields: - query = ('SELECT table_schema AS "name", ' - 'SUM(data_length + index_length) AS "size" ' - 'FROM information_schema.TABLES GROUP BY table_schema') - else: - if 'db_size' in exclude_fields: - query = ('SELECT table_schema AS "name" ' - 'FROM information_schema.TABLES GROUP BY table_schema') - - res = self.__exec_sql(query) - - if res: - for db in res: - self.info['databases'][db['name']] = {} - - if not exclude_fields or 'db_size' not in exclude_fields: - if db['size'] is None: - db['size'] = 0 - - self.info['databases'][db['name']]['size'] = int(db['size']) - - # If empty dbs are not needed in the returned dict, exit from the method - if not return_empty_dbs: - return None - - # Add info about empty databases (issue #65727): - res = self.__exec_sql('SHOW DATABASES') - if res: - for db in res: - if db['Database'] not in self.info['databases']: - self.info['databases'][db['Database']] = {} - if not exclude_fields or 'db_size' not in exclude_fields: - self.info['databases'][db['Database']]['size'] = 0 + def is_field_included(field_name): + return not exclude_fields or 'db_{}'.format(field_name) not in exclude_fields + + def create_db_info(db_data): + info = {} + if is_field_included('size'): + info['size'] = int(db_data.get('size', 0) or 0) + if is_field_included('table_count'): + info['tables'] = int(db_data.get('tables', 0) or 0) + return info + + # Build the main query + query_parts = ['SELECT table_schema AS "name"'] + if is_field_included('size'): + query_parts.append('SUM(data_length + index_length) AS "size"') + if is_field_included('table_count'): + query_parts.append('COUNT(table_name) as "tables"') + + query = "{} FROM information_schema.TABLES GROUP BY table_schema".format(", ".join(query_parts)) + + # Get and process databases with tables + databases = self.__exec_sql(query) or [] + for db in databases: + self.info['databases'][db['name']] = create_db_info(db) + + # Handle empty databases if requested + if return_empty_dbs: + empty_databases = self.__exec_sql('SHOW DATABASES') or [] + for db in empty_databases: + db_name = db['Database'] + if db_name not in self.info['databases']: + self.info['databases'][db_name] = create_db_info({}) def __exec_sql(self, query, ddl=False): """Execute SQL. diff --git a/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml b/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml new file mode 100644 index 00000000..da1058b3 --- /dev/null +++ b/tests/integration/targets/test_mysql_info/tasks/filter_databases.yml @@ -0,0 +1,161 @@ +--- + +- module_defaults: + community.mysql.mysql_db: &mysql_defaults + login_user: "{{ mysql_user }}" + login_password: "{{ mysql_password }}" + login_host: "{{ mysql_host }}" + login_port: "{{ mysql_primary_port }}" + community.mysql.mysql_query: *mysql_defaults + community.mysql.mysql_info: *mysql_defaults + community.mysql.mysql_user: *mysql_defaults + + block: + + # ================================ Prepare ============================== + - name: Mysql_info databases | Prepare | Create databases + community.mysql.mysql_db: + name: + - db_tables_count_empty + - db_tables_count_1 + - db_tables_count_2 + - db_only_views # https://github.com/ansible-Getions/community.mysql/issues/204 + state: present + + - name: Mysql_info databases | Prepare | Create tables + community.mysql.mysql_query: + query: + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_1.t1 + (id int, name varchar(9)) + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_2.t1 + (id int, name1 varchar(9)) + - >- + CREATE TABLE IF NOT EXISTS db_tables_count_2.t2 + (id int, name1 varchar(9)) + - >- + CREATE VIEW db_only_views.v_today (today) AS SELECT CURRENT_DATE + + # ================================== Tests ============================== + + - name: Mysql_info databases | Get all non-empty databases fields + community.mysql.mysql_info: + filter: + - databases + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs fields except db_size + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + # 'unsupported' element is passed to check that an unsupported value + # won't break anything (will be ignored regarding to the module's + # documentation). + - name: Mysql_info databases | Get all dbs fields with unsupported value + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + - unsupported + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs fields except tables + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_table_count + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables is defined or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables is defined or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables is defined or + 'db_tables_count_empty' in result.databases | dict2items + | map(attribute='key') + + - name: Mysql_info databases | Get all dbs even empty ones + community.mysql.mysql_info: + filter: + - databases + return_empty_dbs: true + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size != 16384 or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size != 32768 or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size != 0 or + result.databases['db_only_views'].tables != 1 or + result.databases['db_tables_count_empty'].size != 0 or + result.databases['db_tables_count_empty'].tables != 0 + + - name: Mysql_info databases | Get all dbs even empty ones without size + community.mysql.mysql_info: + filter: + - databases + exclude_fields: + - db_size + return_empty_dbs: true + register: result + failed_when: + - > + result.databases['db_tables_count_1'].size is defined or + result.databases['db_tables_count_1'].tables != 1 or + result.databases['db_tables_count_2'].size is defined or + result.databases['db_tables_count_2'].tables != 2 or + result.databases['db_only_views'].size is defined or + result.databases['db_only_views'].tables != 1 or + result.databases['db_tables_count_empty'].size is defined or + result.databases['db_tables_count_empty'].tables != 0 + + # ================================== Cleanup ============================ + + - name: Mysql_info databases | Cleanup databases + community.mysql.mysql_db: + name: + - db_tables_count_empty + - db_tables_count_1 + - db_tables_count_2 + - db_only_views + state: absent diff --git a/tests/integration/targets/test_mysql_info/tasks/main.yml b/tests/integration/targets/test_mysql_info/tasks/main.yml index 42350c6e..61f238fe 100644 --- a/tests/integration/targets/test_mysql_info/tasks/main.yml +++ b/tests/integration/targets/test_mysql_info/tasks/main.yml @@ -132,94 +132,11 @@ - result.global_status is not defined - result.users is not defined - # Test exclude_fields: db_size - # 'unsupported' element is passed to check that an unsupported value - # won't break anything (will be ignored regarding to the module's documentation). - - name: Collect info about databases excluding their sizes - mysql_info: - <<: *mysql_params - filter: - - databases - exclude_fields: - - db_size - - unsupported - register: result - - - assert: - that: - - result is not changed - - result.databases != {} - - result.databases.mysql == {} - - ######################################################## - # Issue #65727, empty databases must be in returned dict - # - - name: Create empty database acme - mysql_db: - <<: *mysql_params - name: acme - - - name: Collect info about databases - mysql_info: - <<: *mysql_params - filter: - - databases - return_empty_dbs: true - register: result - - # Check acme is in returned dict - - assert: - that: - - result is not changed - - result.databases.acme.size == 0 - - result.databases.mysql != {} - - - name: Collect info about databases excluding their sizes - mysql_info: - <<: *mysql_params - filter: - - databases - exclude_fields: - - db_size - return_empty_dbs: true - register: result - - # Check acme is in returned dict - - assert: - that: - - result is not changed - - result.databases.acme == {} - - result.databases.mysql == {} - - - name: Remove acme database - mysql_db: - <<: *mysql_params - name: acme - state: absent - - include_tasks: issue-28.yml - # https://github.com/ansible-collections/community.mysql/issues/204 - - name: Create database containing only views - mysql_db: - <<: *mysql_params - name: allviews - - - name: Create view - mysql_query: - <<: *mysql_params - login_db: allviews - query: 'CREATE VIEW v_today (today) AS SELECT CURRENT_DATE' - - - name: Fetch info - mysql_info: - <<: *mysql_params - register: result - - - name: Check - assert: - that: - - result.databases.allviews.size == 0 + - name: Import tasks file to tests tables count in database filter + ansible.builtin.import_tasks: + file: filter_databases.yml - name: Import tasks file to tests users_info filter ansible.builtin.import_tasks: