jaaql-middleware-python 4.34.0__tar.gz → 4.34.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/PKG-INFO +1 -1
  2. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/constants.py +1 -1
  3. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/interpreter/interpret_jaaql.py +89 -29
  4. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/model.py +78 -17
  5. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql_middleware_python.egg-info/PKG-INFO +1 -1
  6. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/LICENSE.txt +0 -0
  7. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/README.md +0 -0
  8. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/__init__.py +0 -0
  9. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/config/__init__.py +0 -0
  10. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/config/config-docker.ini +0 -0
  11. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/config/config-test.ini +0 -0
  12. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/config/config.ini +0 -0
  13. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/config_constants.py +0 -0
  14. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/db/__init__.py +0 -0
  15. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/db/db_interface.py +0 -0
  16. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/db/db_pg_interface.py +0 -0
  17. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/db/db_utils.py +0 -0
  18. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/db/db_utils_no_circ.py +0 -0
  19. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/documentation/__init__.py +0 -0
  20. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/documentation/documentation_internal.py +0 -0
  21. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/documentation/documentation_public.py +0 -0
  22. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/documentation/documentation_shared.py +0 -0
  23. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/email/__init__.py +0 -0
  24. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/email/email_manager.py +0 -0
  25. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/email/email_manager_service.py +0 -0
  26. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/email/patch_ems.py +0 -0
  27. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/exceptions/__init__.py +0 -0
  28. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/exceptions/custom_http_status.py +0 -0
  29. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/exceptions/http_status_exception.py +0 -0
  30. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/exceptions/jaaql_interpretable_handled_errors.py +0 -0
  31. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/exceptions/not_yet_implement_exception.py +0 -0
  32. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/generated_constants.py +0 -0
  33. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/interpreter/__init__.py +0 -0
  34. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/jaaql.py +0 -0
  35. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/migrations/__init__.py +0 -0
  36. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/migrations/migrations.py +0 -0
  37. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/__init__.py +0 -0
  38. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/base_controller.py +0 -0
  39. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/base_model.py +0 -0
  40. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/controller.py +0 -0
  41. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/controller_interface.py +0 -0
  42. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/exception_queries.py +0 -0
  43. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/generated_queries.py +0 -0
  44. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/handmade_queries.py +0 -0
  45. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/model_interface.py +0 -0
  46. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/mvc/response.py +0 -0
  47. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/openapi/__init__.py +0 -0
  48. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/openapi/swagger_documentation.py +0 -0
  49. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/patch.py +0 -0
  50. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/01.install_domains.generated.sql +0 -0
  51. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/02.install_super_user.exceptions.sql +0 -0
  52. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/03.install_super_user.handwritten.sql +0 -0
  53. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/04.install_jaaql_data_structures.generated.sql +0 -0
  54. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/05.install_static_data.generated.sql +0 -0
  55. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/06.install_jaaql.exceptions.sql +0 -0
  56. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/ZZZZ.generated_functions_views_and_permissions.sql +0 -0
  57. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/ZZZZ.reset_references.sql +0 -0
  58. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/scripts/swagger_template.html +0 -0
  59. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/__init__.py +0 -0
  60. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/cached_canned_query_service.py +0 -0
  61. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/migrations_manager_service.py +0 -0
  62. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/patch_mms.py +0 -0
  63. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/patch_shared_var_service.py +0 -0
  64. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/services/shared_var_service.py +0 -0
  65. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/__init__.py +0 -0
  66. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/cron.py +0 -0
  67. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/crypt_utils.py +0 -0
  68. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/options.py +0 -0
  69. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/utils.py +0 -0
  70. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/utils_no_project_imports.py +0 -0
  71. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql/utilities/vault.py +0 -0
  72. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql_middleware_python.egg-info/SOURCES.txt +0 -0
  73. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql_middleware_python.egg-info/dependency_links.txt +0 -0
  74. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql_middleware_python.egg-info/requires.txt +0 -0
  75. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/jaaql_middleware_python.egg-info/top_level.txt +0 -0
  76. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/setup.cfg +0 -0
  77. {jaaql_middleware_python-4.34.0 → jaaql_middleware_python-4.34.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaaql-middleware-python
3
- Version: 4.34.0
3
+ Version: 4.34.2
4
4
  Summary: The jaaql package, allowing for rapid development and deployment of RESTful HTTP applications
5
5
  Home-page: https://github.com/JAAQL/JAAQL-middleware-python
6
6
  Author: Software Quality Measurement and Improvement bv
@@ -208,5 +208,5 @@ ROLE__postgres = "postgres"
208
208
 
209
209
  PROTOCOL__postgres = "postgresql://"
210
210
 
211
- VERSION = "4.34.0"
211
+ VERSION = "4.34.2"
212
212
 
@@ -336,35 +336,95 @@ class InterpretJAAQL:
336
336
  requires_dba_check=check_required and canned_query_service is not None)
337
337
  last_query = """
338
338
  WITH RECURSIVE column_hierarchy AS (
339
- -- Base case: Select columns from 'managed_service'
340
- SELECT
341
- C.column_name, C.data_type, C.udt_name, C.domain_name,
342
- VC.table_catalog, VC.table_schema, VC.table_name,
343
- true as is_nullable,
344
- 1 as level
345
- FROM information_schema.columns C
346
- LEFT JOIN information_schema.view_column_usage VC on VC.view_name = C.table_name AND VC.column_name = C.column_name
347
- WHERE C.table_name = 'temp_view__""" + temp_view_name + """'
348
-
349
- UNION ALL
350
-
351
- SELECT
352
- C.column_name, C.data_type, C.udt_name, C.domain_name,
353
- VC.table_catalog, VC.table_schema, VC.table_name,
354
- CASE WHEN C.is_nullable = 'NO' then false else true end as is_nullable,
355
- CH.level + 1 as level
356
- FROM information_schema.columns C
357
- JOIN column_hierarchy CH ON C.column_name = CH.column_name
358
- LEFT JOIN information_schema.view_column_usage VC on VC.view_name = C.table_name AND VC.column_name = C.column_name
359
- WHERE C.table_name = CH.table_name AND C.table_schema = CH.table_schema AND C.table_catalog = CH.table_catalog
360
- )SELECT
361
- MAX(CH.column_name) as column_name,
362
- MAX(CH.data_type) as data_type,
363
- MAX(CH.udt_name) as udt_name,
364
- MAX(CH.domain_name) as domain_name,
365
- NOT(bool_or(NOT(CH.is_nullable))) as is_nullable
366
- FROM column_hierarchy CH
367
- GROUP BY CH.column_name, CH.data_type, CH.udt_name, CH.domain_name;"""
339
+ SELECT
340
+ c.column_name,
341
+ c.data_type,
342
+ c.udt_name,
343
+ c.domain_name,
344
+ c.table_catalog,
345
+ c.table_schema,
346
+ c.table_name,
347
+ true AS is_nullable,
348
+ 1 AS level
349
+ FROM information_schema.columns c
350
+ WHERE c.table_name = 'temp_view__""" + temp_view_name + """'
351
+ AND c.table_schema = (
352
+ SELECT n.nspname
353
+ FROM pg_namespace n
354
+ WHERE n.oid = pg_my_temp_schema()
355
+ )
356
+
357
+ UNION ALL
358
+
359
+ SELECT
360
+ ch.column_name,
361
+ ref.data_type,
362
+ ref.udt_name,
363
+ ref.domain_name,
364
+ ref.table_catalog,
365
+ ref.table_schema,
366
+ ref.table_name,
367
+ (ref.is_nullable = 'YES') AS is_nullable,
368
+ ch.level + 1 AS level
369
+ FROM column_hierarchy ch
370
+ JOIN information_schema.views v
371
+ ON v.table_catalog = ch.table_catalog
372
+ AND v.table_schema = ch.table_schema
373
+ AND v.table_name = ch.table_name
374
+ JOIN LATERAL (
375
+ SELECT
376
+ c2.data_type,
377
+ c2.udt_name,
378
+ c2.domain_name,
379
+ c2.table_catalog,
380
+ c2.table_schema,
381
+ c2.table_name,
382
+ c2.is_nullable
383
+ FROM information_schema.view_column_usage u
384
+ JOIN information_schema.columns c2
385
+ ON c2.table_catalog = u.table_catalog
386
+ AND c2.table_schema = u.table_schema
387
+ AND c2.table_name = u.table_name
388
+ AND c2.column_name = u.column_name
389
+ WHERE u.view_catalog = ch.table_catalog
390
+ AND u.view_schema = ch.table_schema
391
+ AND u.view_name = ch.table_name
392
+ AND u.column_name = ch.column_name
393
+ ORDER BY
394
+ CASE WHEN ch.domain_name IS NOT DISTINCT FROM c2.domain_name THEN 3 ELSE 0 END DESC,
395
+ CASE WHEN ch.udt_name = c2.udt_name THEN 2 ELSE 0 END DESC,
396
+ CASE WHEN ch.data_type = c2.data_type THEN 1 ELSE 0 END DESC,
397
+ c2.table_schema,
398
+ c2.table_name
399
+ LIMIT 1
400
+ ) ref ON true
401
+ ),
402
+ deep AS (
403
+ SELECT DISTINCT ON (column_name)
404
+ column_name,
405
+ data_type,
406
+ udt_name,
407
+ domain_name
408
+ FROM column_hierarchy
409
+ ORDER BY column_name, level DESC
410
+ ),
411
+ nul AS (
412
+ SELECT
413
+ column_name,
414
+ NOT(bool_or(NOT is_nullable)) AS is_nullable
415
+ FROM column_hierarchy
416
+ GROUP BY column_name
417
+ )
418
+ SELECT
419
+ d.column_name,
420
+ d.data_type,
421
+ d.udt_name,
422
+ d.domain_name,
423
+ n.is_nullable
424
+ FROM deep d
425
+ JOIN nul n USING (column_name)
426
+ ORDER BY d.column_name;
427
+ """
368
428
  found_params = {}
369
429
  elif do_prepare_only and not psql:
370
430
  arg_open = "(" if len(found_params) != 0 else ""
@@ -203,28 +203,46 @@ class JAAQLModel(BaseJAAQLModel):
203
203
  def parse_gdesc_output(self, output):
204
204
  """
205
205
  Parse the output of gdesc to extract column names and types.
206
+
207
+ If duplicate column names exist, raise an exception listing them.
206
208
  """
207
209
  lines = output.strip().split('\n')
208
210
  columns = {}
209
211
  parsing = False
212
+
213
+ seen = {}
210
214
  for line in lines:
211
215
  if line.strip().replace(" ", "") == "Column|Type":
212
216
  # Found the header line
213
217
  parsing = True
214
218
  continue
219
+
215
220
  if parsing:
216
221
  if line.strip() == '':
217
222
  # End of table
218
223
  break
224
+
219
225
  # Parse the line
220
226
  parts = [part.strip() for part in line.strip().split('|')]
221
227
  if len(parts) >= 2:
222
228
  column_name = parts[0]
223
229
  column_type = parts[1]
230
+
231
+ seen[column_name] = seen.get(column_name, 0) + 1
232
+
233
+ # Still build the dict, but we'll error afterwards if duplicates exist
224
234
  columns[column_name] = {
225
235
  "type": column_type,
226
236
  "nullable": True # We can't figure this out with gdesc
227
237
  }
238
+
239
+ dupes = [name for name, count in seen.items() if count > 1]
240
+ if dupes:
241
+ dupes.sort()
242
+ # Include counts so it's unambiguous
243
+ dupe_str = ", ".join([f"{name} (x{seen[name]})" for name in dupes])
244
+ raise Exception("Duplicate output column names are not supported: " + dupe_str)
245
+
228
246
  return columns
229
247
 
230
248
  def fetch_domains(self, inputs: dict, account_id: str):
@@ -269,32 +287,58 @@ ORDER BY
269
287
  cost = None
270
288
  type_resolution_method = None
271
289
  columns = None
290
+
272
291
  try:
273
- results = execute_supplied_statement(db_connection, query["query"].strip(),
274
- do_prepare_only=my_uuid) # Fetch cursor descriptors here too and then merge
292
+ results = execute_supplied_statement(
293
+ db_connection,
294
+ query["query"].strip(),
295
+ do_prepare_only=my_uuid
296
+ )
275
297
  cost = float(results["rows"][0][0].split("..")[1].split(" ")[0])
276
298
  except Exception as ex:
277
299
  exc = str(ex).replace("PREPARE _jaaql_query_check_" + my_uuid + " as ", "").replace(
278
300
  " ... _jaaql_query_check_" + my_uuid + " as ", "")
279
301
 
280
302
  if exc is None and not cost_only:
303
+ # Try temp_view domain type resolution first
281
304
  try:
282
- domain_types = execute_supplied_statement(db_connection, query["query"].strip(), do_prepare_only=my_uuid, attempt_fetch_domain_types=True)
305
+ domain_types = execute_supplied_statement(
306
+ db_connection,
307
+ query["query"].strip(),
308
+ do_prepare_only=my_uuid,
309
+ attempt_fetch_domain_types=True
310
+ )
283
311
  type_resolution_method = "temp_view"
312
+
284
313
  temp_columns = [row[0] for row in domain_types['rows']]
285
- nullable = [row[4] for row in domain_types['rows']]
286
- temp_types = [row[3] if row[3] is not None else row[2] for row in domain_types['rows']]
287
- columns = {
288
- col: {
289
- "type": temp_types[idx],
290
- "nullable": nullable[idx]
314
+
315
+ # Detect duplicates BEFORE building the dict (dict would overwrite)
316
+ counts = {}
317
+ for c in temp_columns:
318
+ counts[c] = counts.get(c, 0) + 1
319
+
320
+ dupes = [name for name, count in counts.items() if count > 1]
321
+ if dupes:
322
+ dupes.sort()
323
+ dupe_str = ", ".join([f"{name} (x{counts[name]})" for name in dupes])
324
+ exc = "Duplicate output column names are not supported: " + dupe_str
325
+ columns = None
326
+ else:
327
+ nullable = [row[4] for row in domain_types['rows']]
328
+ temp_types = [row[3] if row[3] is not None else row[2] for row in domain_types['rows']]
329
+ columns = {
330
+ col: {
331
+ "type": temp_types[idx],
332
+ "nullable": nullable[idx]
333
+ }
334
+ for col, idx in zip(temp_columns, range(len(temp_columns)))
291
335
  }
292
- for col, idx in zip(temp_columns, range(len(temp_columns)))
293
- }
294
- except Exception as ex:
336
+
337
+ except Exception:
295
338
  pass # This is fine
296
339
 
297
- if columns is None:
340
+ # If temp_view didn't work, fallback to gdesc/psql
341
+ if columns is None and exc is None:
298
342
  try:
299
343
  type_resolution_method = "gdesc"
300
344
  psql = [
@@ -303,11 +347,22 @@ ORDER BY
303
347
  "-d", inputs[KEY__database],
304
348
  "-q", "-X", "--pset", "pager=off", "-f", "-"
305
349
  ]
306
- results = execute_supplied_statement(db_connection, query["query"].strip(), do_prepare_only=my_uuid, psql=psql,
307
- pre_psql="SET SESSION AUTHORIZATION \"" + account_id + "\";")
350
+ results = execute_supplied_statement(
351
+ db_connection,
352
+ query["query"].strip(),
353
+ do_prepare_only=my_uuid,
354
+ psql=psql,
355
+ pre_psql="SET SESSION AUTHORIZATION \"" + account_id + "\";"
356
+ )
357
+
358
+ # parse_gdesc_output now throws if duplicates exist
308
359
  columns = self.parse_gdesc_output(results['rows'][0])
309
- except:
360
+
361
+ except Exception as ex:
362
+ # This catches duplicate column errors and also any parse failures
363
+ exc = str(ex)
310
364
  type_resolution_method = "failed"
365
+ columns = None
311
366
 
312
367
  res.append({
313
368
  "location": query["file"] + ":" + str(query["line_number"]),
@@ -319,7 +374,13 @@ ORDER BY
319
374
  })
320
375
 
321
376
  if inputs.get("sort_cost", True):
322
- return sorted(res, key=lambda x: (x["exception"] if x["exception"] is not None else '', float('-inf') if x["cost"] is None else -x["cost"]))
377
+ return sorted(
378
+ res,
379
+ key=lambda x: (
380
+ x["exception"] if x["exception"] is not None else '',
381
+ float('-inf') if x["cost"] is None else -x["cost"]
382
+ )
383
+ )
323
384
  else:
324
385
  return res
325
386
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaaql-middleware-python
3
- Version: 4.34.0
3
+ Version: 4.34.2
4
4
  Summary: The jaaql package, allowing for rapid development and deployment of RESTful HTTP applications
5
5
  Home-page: https://github.com/JAAQL/JAAQL-middleware-python
6
6
  Author: Software Quality Measurement and Improvement bv