File indexing completed on 2026-04-27 07:41:42
0001 """
0002 SQL builders and row helpers for PanDA database queries.
0003
0004 Pure functions that construct SQL strings and transform database rows.
0005 No database I/O — callers execute the returned (sql, params) tuples.
0006 """
0007
0008 from .constants import PANDA_SCHEMA, ERROR_COMPONENTS
0009
0010
0011
0012
0013 def build_union_query(fields, where_clauses, params, order_by, limit):
0014 """Build a UNION ALL query across jobsactive4 and jobsarchived4."""
0015 field_list = ', '.join(f'"{f}"' for f in fields)
0016 where_sql = ''
0017 if where_clauses:
0018 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0019
0020 sql = f"""
0021 SELECT * FROM (
0022 SELECT {field_list} FROM "{PANDA_SCHEMA}"."jobsactive4"{where_sql}
0023 UNION ALL
0024 SELECT {field_list} FROM "{PANDA_SCHEMA}"."jobsarchived4"{where_sql}
0025 ) combined
0026 ORDER BY {order_by}
0027 LIMIT {limit}
0028 """
0029 full_params = list(params) + list(params)
0030 return sql, full_params
0031
0032
0033 def build_count_query(where_clauses, params):
0034 """Build a count-by-status query across both job tables."""
0035 where_sql = ''
0036 if where_clauses:
0037 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0038
0039 sql = f"""
0040 SELECT "jobstatus", COUNT(*) FROM (
0041 SELECT "jobstatus" FROM "{PANDA_SCHEMA}"."jobsactive4"{where_sql}
0042 UNION ALL
0043 SELECT "jobstatus" FROM "{PANDA_SCHEMA}"."jobsarchived4"{where_sql}
0044 ) combined
0045 GROUP BY "jobstatus"
0046 ORDER BY COUNT(*) DESC
0047 """
0048 full_params = list(params) + list(params)
0049 return sql, full_params
0050
0051
0052 def build_task_query(fields, where_clauses, params, order_by, limit):
0053 """Build a query against the jedi_tasks table."""
0054 field_list = ', '.join(f'"{f}"' for f in fields)
0055 where_sql = ''
0056 if where_clauses:
0057 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0058 sql = f"""
0059 SELECT {field_list}
0060 FROM "{PANDA_SCHEMA}"."jedi_tasks"{where_sql}
0061 ORDER BY {order_by}
0062 LIMIT {limit}
0063 """
0064 return sql, list(params)
0065
0066
0067 def build_task_count_query(where_clauses, params):
0068 """Build a count-by-status query for jedi_tasks."""
0069 where_sql = ''
0070 if where_clauses:
0071 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0072 sql = f"""
0073 SELECT "status", COUNT(*)
0074 FROM "{PANDA_SCHEMA}"."jedi_tasks"{where_sql}
0075 GROUP BY "status"
0076 ORDER BY COUNT(*) DESC
0077 """
0078 return sql, list(params)
0079
0080
0081
0082
0083 def row_to_dict(row, fields):
0084 """Convert a database row to a dict, formatting datetimes."""
0085 result = {}
0086 for i, field in enumerate(fields):
0087 val = row[i]
0088 if val is not None and hasattr(val, 'isoformat'):
0089 val = val.isoformat()
0090 result[field] = val
0091 return result
0092
0093
0094 def extract_errors(job_dict):
0095 """Extract non-zero error components from a job dict."""
0096 errors = []
0097 for comp in ERROR_COMPONENTS:
0098 code = job_dict.get(comp['code'])
0099 if code and int(code) != 0:
0100 errors.append({
0101 'component': comp['name'],
0102 'code': int(code),
0103 'diag': job_dict.get(comp['diag'], ''),
0104 })
0105 transexitcode = job_dict.get('transexitcode')
0106 if transexitcode and str(transexitcode).strip() not in ('', '0'):
0107 errors.append({
0108 'component': 'transformation',
0109 'code': transexitcode,
0110 'diag': '',
0111 })
0112 return errors
0113
0114
0115 def like_or_eq(field, value):
0116 """Return (where_clause, param) using LIKE if value contains %, else =."""
0117 if '%' in value:
0118 return f'"{field}" LIKE %s', value
0119 return f'"{field}" = %s', value
0120
0121
0122
0123
0124 def build_union_query_dt(fields, where_clauses, params, order_by, limit, offset):
0125 """Build a UNION ALL query with OFFSET for DataTables pagination."""
0126 field_list = ', '.join(f'"{f}"' for f in fields)
0127 where_sql = ''
0128 if where_clauses:
0129 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0130
0131 sql = f"""
0132 SELECT * FROM (
0133 SELECT {field_list} FROM "{PANDA_SCHEMA}"."jobsactive4"{where_sql}
0134 UNION ALL
0135 SELECT {field_list} FROM "{PANDA_SCHEMA}"."jobsarchived4"{where_sql}
0136 ) combined
0137 ORDER BY {order_by}
0138 LIMIT {limit} OFFSET {offset}
0139 """
0140 full_params = list(params) + list(params)
0141 return sql, full_params
0142
0143
0144 def build_union_count(where_clauses, params):
0145 """Build a total count query across both job tables."""
0146 where_sql = ''
0147 if where_clauses:
0148 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0149
0150 sql = f"""
0151 SELECT COUNT(*) FROM (
0152 SELECT 1 FROM "{PANDA_SCHEMA}"."jobsactive4"{where_sql}
0153 UNION ALL
0154 SELECT 1 FROM "{PANDA_SCHEMA}"."jobsarchived4"{where_sql}
0155 ) combined
0156 """
0157 full_params = list(params) + list(params)
0158 return sql, full_params
0159
0160
0161 def build_union_count_by_field(field, where_clauses, params):
0162 """Build a GROUP BY count for a single field across both job tables."""
0163 where_sql = ''
0164 if where_clauses:
0165 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0166
0167 sql = f"""
0168 SELECT "{field}", COUNT(*) FROM (
0169 SELECT "{field}" FROM "{PANDA_SCHEMA}"."jobsactive4"{where_sql}
0170 UNION ALL
0171 SELECT "{field}" FROM "{PANDA_SCHEMA}"."jobsarchived4"{where_sql}
0172 ) combined
0173 WHERE "{field}" IS NOT NULL
0174 GROUP BY "{field}"
0175 ORDER BY COUNT(*) DESC
0176 """
0177 full_params = list(params) + list(params)
0178 return sql, full_params
0179
0180
0181 def build_task_query_dt(fields, where_clauses, params, order_by, limit, offset):
0182 """Build a task query with OFFSET for DataTables pagination."""
0183 field_list = ', '.join(f'"{f}"' for f in fields)
0184 where_sql = ''
0185 if where_clauses:
0186 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0187 sql = f"""
0188 SELECT {field_list}
0189 FROM "{PANDA_SCHEMA}"."jedi_tasks"{where_sql}
0190 ORDER BY {order_by}
0191 LIMIT {limit} OFFSET {offset}
0192 """
0193 return sql, list(params)
0194
0195
0196 def build_task_count(where_clauses, params):
0197 """Build a total count query for jedi_tasks."""
0198 where_sql = ''
0199 if where_clauses:
0200 where_sql = ' WHERE ' + ' AND '.join(where_clauses)
0201 sql = f"""
0202 SELECT COUNT(*)
0203 FROM "{PANDA_SCHEMA}"."jedi_tasks"{where_sql}
0204 """
0205 return sql, list(params)
0206
0207
0208 def build_task_count_by_field(field, where_clauses, params):
0209 """Build a GROUP BY count for a single field in jedi_tasks."""
0210 all_clauses = list(where_clauses) + [f'"{field}" IS NOT NULL']
0211 where_sql = ' WHERE ' + ' AND '.join(all_clauses)
0212 sql = f"""
0213 SELECT "{field}", COUNT(*)
0214 FROM "{PANDA_SCHEMA}"."jedi_tasks"{where_sql}
0215 GROUP BY "{field}"
0216 ORDER BY COUNT(*) DESC
0217 """
0218 return sql, list(params)
0219
0220
0221 def build_search_clauses(fields, search_value):
0222 """Build ILIKE search clauses across multiple fields."""
0223 clauses = []
0224 params = []
0225 for f in fields:
0226 clauses.append(f'CAST("{f}" AS TEXT) ILIKE %s')
0227 params.append(f'%{search_value}%')
0228 return f"({' OR '.join(clauses)})", params