jaaql-middleware-python 5.3.11__tar.gz → 5.3.13__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.
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/PKG-INFO +1 -1
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/constants.py +1 -1
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_interface.py +5 -3
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_pg_interface.py +8 -1
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/documentation/documentation_shared.py +3 -3
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/interpreter/interpret_jaaql.py +168 -7
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/controller.py +3 -7
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/model.py +30 -19
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql_middleware_python.egg-info/PKG-INFO +1 -1
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/LICENSE.txt +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/README.md +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/config-docker.ini +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/config-test.ini +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/config.ini +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config_constants.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_utils.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_utils_no_circ.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/documentation/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/documentation/documentation_internal.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/documentation/documentation_public.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/email/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/email/email_manager.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/email/email_manager_service.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/email/patch_ems.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/custom_http_status.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/http_status_exception.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/jaaql_interpretable_handled_errors.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/not_yet_implement_exception.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/generated_constants.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/interpreter/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/jaaql.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/migrations/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/migrations/migrations.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/base_controller.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/base_model.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/controller_interface.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/exception_queries.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/generated_queries.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/handmade_queries.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/model_interface.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/response.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/openapi/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/openapi/swagger_documentation.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/patch.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/01.install_domains.generated.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/02.install_super_user.exceptions.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/03.install_super_user.handwritten.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/04.install_jaaql_data_structures.generated.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/05.install_static_data.generated.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/06.install_jaaql.exceptions.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/ZZZZ.generated_functions_views_and_permissions.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/ZZZZ.reset_references.sql +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/scripts/swagger_template.html +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/cached_canned_query_service.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/migrations_manager_service.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/patch_mms.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/patch_shared_var_service.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/shared_var_service.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/__init__.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/bootstrap_secrets.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/cron.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/crypt_utils.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/options.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/utils.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/utils_no_project_imports.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/vault.py +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql_middleware_python.egg-info/SOURCES.txt +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql_middleware_python.egg-info/dependency_links.txt +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql_middleware_python.egg-info/requires.txt +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql_middleware_python.egg-info/top_level.txt +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/setup.cfg +0 -0
- {jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaaql-middleware-python
|
|
3
|
-
Version: 5.3.
|
|
3
|
+
Version: 5.3.13
|
|
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
|
|
@@ -153,7 +153,7 @@ class DBInterface(ABC):
|
|
|
153
153
|
pass
|
|
154
154
|
|
|
155
155
|
def execute_query_fetching_results(self, conn, query, parameters=None, echo=ECHO__none, as_objects=False, wait_hook: queue.Queue = None,
|
|
156
|
-
requires_dba_check: bool = False, prepare: bool = False):
|
|
156
|
+
requires_dba_check: bool = False, prepare: bool = False, capture_provenance: list = None):
|
|
157
157
|
if echo not in ECHO__allowed:
|
|
158
158
|
allowed_echoes = ", ".join([str(allowed_echo) for allowed_echo in ECHO__allowed])
|
|
159
159
|
raise HttpStatusException(ERR__unknown_echo % (str(echo), allowed_echoes), HTTPStatus.BAD_REQUEST)
|
|
@@ -177,7 +177,8 @@ class DBInterface(ABC):
|
|
|
177
177
|
if wait_hook:
|
|
178
178
|
wait_hook = None
|
|
179
179
|
|
|
180
|
-
columns, type_codes, rows = self.execute_query(conn, query, new_parameters, wait_hook, prepare=prepare
|
|
180
|
+
columns, type_codes, rows = self.execute_query(conn, query, new_parameters, wait_hook, prepare=prepare,
|
|
181
|
+
capture_provenance=capture_provenance)
|
|
181
182
|
|
|
182
183
|
ret = {
|
|
183
184
|
RET__columns: columns,
|
|
@@ -253,7 +254,8 @@ class DBInterface(ABC):
|
|
|
253
254
|
pass
|
|
254
255
|
|
|
255
256
|
@abstractmethod
|
|
256
|
-
def execute_query(self, conn, query, parameters: Optional[dict] = None, wait_hook: queue.Queue = None, prepare: bool = False
|
|
257
|
+
def execute_query(self, conn, query, parameters: Optional[dict] = None, wait_hook: queue.Queue = None, prepare: bool = False,
|
|
258
|
+
capture_provenance: list = None):
|
|
257
259
|
pass
|
|
258
260
|
|
|
259
261
|
@abstractmethod
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_pg_interface.py
RENAMED
|
@@ -327,7 +327,8 @@ class DBPGInterface(DBInterface):
|
|
|
327
327
|
return HttpStatusException(ERR__invalid_token, HTTPStatus.UNAUTHORIZED)
|
|
328
328
|
return None
|
|
329
329
|
|
|
330
|
-
def execute_query(self, conn, query, parameters=None, wait_hook: queue.Queue = None, prepare: bool = False
|
|
330
|
+
def execute_query(self, conn, query, parameters=None, wait_hook: queue.Queue = None, prepare: bool = False,
|
|
331
|
+
capture_provenance: list = None):
|
|
331
332
|
x = 0
|
|
332
333
|
err = None
|
|
333
334
|
while x < PGCONN__max_conns:
|
|
@@ -402,6 +403,12 @@ class DBPGInterface(DBInterface):
|
|
|
402
403
|
if cursor.description is None:
|
|
403
404
|
return [], [], []
|
|
404
405
|
else:
|
|
406
|
+
if capture_provenance is not None:
|
|
407
|
+
capture_provenance.clear()
|
|
408
|
+
pgresult = cursor.pgresult
|
|
409
|
+
capture_provenance.extend(
|
|
410
|
+
(pgresult.ftype(idx), pgresult.ftable(idx), pgresult.ftablecol(idx))
|
|
411
|
+
for idx in range(pgresult.nfields))
|
|
405
412
|
return [desc[0] for desc in cursor.description], [desc.type_code for desc in cursor.description], cursor.fetchall()
|
|
406
413
|
except OperationalError as ex:
|
|
407
414
|
if ex.sqlstate is None or ex.sqlstate.startswith("08") or ex.sqlstate.startswith("57"):
|
|
@@ -166,7 +166,7 @@ DOCUMENTATION__submit = SwaggerDocumentation(
|
|
|
166
166
|
method=REST__POST,
|
|
167
167
|
arguments=ARG_RESP__allow_all,
|
|
168
168
|
response=RES__allow_all,
|
|
169
|
-
parallel_verification=
|
|
169
|
+
parallel_verification=True
|
|
170
170
|
)
|
|
171
171
|
)
|
|
172
172
|
|
|
@@ -178,7 +178,7 @@ DOCUMENTATION__execute = SwaggerDocumentation(
|
|
|
178
178
|
method=REST__POST,
|
|
179
179
|
arguments=ARG_RESP__allow_all,
|
|
180
180
|
response=RES__allow_all,
|
|
181
|
-
parallel_verification=
|
|
181
|
+
parallel_verification=True
|
|
182
182
|
)
|
|
183
183
|
)
|
|
184
184
|
|
|
@@ -190,6 +190,6 @@ DOCUMENTATION__call_proc = SwaggerDocumentation(
|
|
|
190
190
|
method=REST__POST,
|
|
191
191
|
arguments=ARG_RESP__allow_all,
|
|
192
192
|
response=RES__allow_all,
|
|
193
|
-
parallel_verification=
|
|
193
|
+
parallel_verification=True
|
|
194
194
|
)
|
|
195
195
|
)
|
|
@@ -375,7 +375,25 @@ class InterpretJAAQL:
|
|
|
375
375
|
last_query = self.encrypt_literals(last_query, encryption_key)
|
|
376
376
|
found_params = {**found_parameter_dictionary, **enc_parameter_dictionary}
|
|
377
377
|
|
|
378
|
+
temp_view_name = None
|
|
379
|
+
provenance_res = None
|
|
380
|
+
|
|
378
381
|
if attempt_fetch_domain_types:
|
|
382
|
+
described, provenance = self.describe_query_provenance(
|
|
383
|
+
conn, last_query, found_params, wait_hook=wait_hook,
|
|
384
|
+
requires_dba_check=check_required and canned_query_service is not None)
|
|
385
|
+
wait_hook = None
|
|
386
|
+
check_required = False
|
|
387
|
+
self.db_interface.execute_query_fetching_results(conn, "SAVEPOINT __jaaql_provenance")
|
|
388
|
+
try:
|
|
389
|
+
provenance_res = self.fetch_domain_types_via_provenance(conn, last_query, described, provenance)
|
|
390
|
+
self.db_interface.execute_query_fetching_results(conn, "RELEASE SAVEPOINT __jaaql_provenance")
|
|
391
|
+
except Exception:
|
|
392
|
+
traceback.print_exc()
|
|
393
|
+
provenance_res = None
|
|
394
|
+
self.db_interface.execute_query_fetching_results(conn, "ROLLBACK TO SAVEPOINT __jaaql_provenance")
|
|
395
|
+
|
|
396
|
+
if attempt_fetch_domain_types and provenance_res is None:
|
|
379
397
|
temp_view_name = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
|
|
380
398
|
last_query = "CREATE TEMP VIEW \"temp_view__" + temp_view_name + "\" AS (" + last_query + ")"
|
|
381
399
|
self.db_interface.execute_query_fetching_results(conn, last_query, found_params, wait_hook=wait_hook,
|
|
@@ -436,23 +454,22 @@ WITH RECURSIVE column_hierarchy AS (
|
|
|
436
454
|
AND u.view_schema = ch.table_schema
|
|
437
455
|
AND u.view_name = ch.table_name
|
|
438
456
|
AND u.column_name = ch.column_name
|
|
457
|
+
AND c2.udt_name = ch.udt_name
|
|
458
|
+
AND c2.domain_name IS NOT DISTINCT FROM ch.domain_name
|
|
439
459
|
ORDER BY
|
|
440
|
-
CASE WHEN ch.domain_name IS NOT DISTINCT FROM c2.domain_name THEN 3 ELSE 0 END DESC,
|
|
441
|
-
CASE WHEN ch.udt_name = c2.udt_name THEN 2 ELSE 0 END DESC,
|
|
442
|
-
CASE WHEN ch.data_type = c2.data_type THEN 1 ELSE 0 END DESC,
|
|
443
460
|
c2.table_schema,
|
|
444
461
|
c2.table_name
|
|
445
462
|
LIMIT 1
|
|
446
463
|
) ref ON true
|
|
447
464
|
),
|
|
448
465
|
deep AS (
|
|
449
|
-
SELECT
|
|
466
|
+
SELECT
|
|
450
467
|
column_name,
|
|
451
468
|
data_type,
|
|
452
469
|
udt_name,
|
|
453
470
|
domain_name
|
|
454
471
|
FROM column_hierarchy
|
|
455
|
-
|
|
472
|
+
WHERE level = 1
|
|
456
473
|
),
|
|
457
474
|
nul AS (
|
|
458
475
|
SELECT
|
|
@@ -472,7 +489,7 @@ JOIN nul n USING (column_name)
|
|
|
472
489
|
ORDER BY d.column_name;
|
|
473
490
|
"""
|
|
474
491
|
found_params = {}
|
|
475
|
-
elif do_prepare_only and not psql:
|
|
492
|
+
elif do_prepare_only and not attempt_fetch_domain_types and not psql:
|
|
476
493
|
arg_open = "(" if len(found_params) != 0 else ""
|
|
477
494
|
arg_close = ")" if len(found_params) != 0 else ""
|
|
478
495
|
last_query = "PREPARE _jaaql_query_check_" + do_prepare_only + arg_open + ", ".join(
|
|
@@ -509,6 +526,8 @@ ORDER BY d.column_name;
|
|
|
509
526
|
"rows": [result.stdout],
|
|
510
527
|
"type_codes": [""]
|
|
511
528
|
}
|
|
529
|
+
elif provenance_res is not None:
|
|
530
|
+
res = provenance_res
|
|
512
531
|
else:
|
|
513
532
|
res = self.db_interface.execute_query_fetching_results(conn, last_query, found_params, wait_hook=wait_hook,
|
|
514
533
|
requires_dba_check=check_required and canned_query_service is not None,
|
|
@@ -518,7 +537,7 @@ ORDER BY d.column_name;
|
|
|
518
537
|
self.db_interface.execute_query_fetching_results(conn, "DEALLOCATE _jaaql_query_check_" + do_prepare_only, found_params,
|
|
519
538
|
wait_hook=wait_hook,
|
|
520
539
|
requires_dba_check=check_required and canned_query_service is not None)
|
|
521
|
-
elif attempt_fetch_domain_types:
|
|
540
|
+
elif attempt_fetch_domain_types and temp_view_name is not None:
|
|
522
541
|
self.db_interface.execute_query_fetching_results(conn, "DROP VIEW IF EXISTS \"temp_view__" + temp_view_name + "\"", found_params,
|
|
523
542
|
wait_hook=wait_hook,
|
|
524
543
|
requires_dba_check=check_required and canned_query_service is not None)
|
|
@@ -835,6 +854,148 @@ ORDER BY d.column_name;
|
|
|
835
854
|
|
|
836
855
|
return new_query + query[last_end_match:]
|
|
837
856
|
|
|
857
|
+
PROVENANCE__max_view_depth = 8
|
|
858
|
+
|
|
859
|
+
def describe_query_provenance(self, conn, query, parameters, wait_hook: queue.Queue = None, requires_dba_check: bool = False):
|
|
860
|
+
provenance = []
|
|
861
|
+
described = self.db_interface.execute_query_fetching_results(
|
|
862
|
+
conn, "SELECT * FROM (" + query + "\n) __jaaql_provenance LIMIT 0", parameters, wait_hook=wait_hook,
|
|
863
|
+
requires_dba_check=requires_dba_check, capture_provenance=provenance)
|
|
864
|
+
return described, provenance
|
|
865
|
+
|
|
866
|
+
def fetch_domain_types_via_provenance(self, conn, query, described, provenance):
|
|
867
|
+
null_extended, plan_cost = self.fetch_null_extended_relations(conn, query)
|
|
868
|
+
result_types = self.fetch_result_types_via_temp_view(conn, query)
|
|
869
|
+
if len(result_types) != len(provenance):
|
|
870
|
+
raise Exception("Temp view column count does not match the described result")
|
|
871
|
+
sources = self.resolve_column_provenance_batch(conn, [(table_oid, table_column) for _, table_oid, table_column in provenance])
|
|
872
|
+
|
|
873
|
+
rows = []
|
|
874
|
+
for column_name, (data_type, udt_name, domain_name), (type_oid, table_oid, table_column) in zip(
|
|
875
|
+
described["columns"], result_types, provenance):
|
|
876
|
+
source = sources.get((table_oid, table_column))
|
|
877
|
+
if source is None:
|
|
878
|
+
is_nullable = True
|
|
879
|
+
else:
|
|
880
|
+
nspname, relname, attnotnull = source
|
|
881
|
+
is_nullable = not attnotnull or (nspname, relname) in null_extended
|
|
882
|
+
rows.append([column_name, data_type, udt_name, domain_name, is_nullable])
|
|
883
|
+
|
|
884
|
+
rows.sort(key=lambda row: row[0])
|
|
885
|
+
return {
|
|
886
|
+
"columns": ["column_name", "data_type", "udt_name", "domain_name", "is_nullable"],
|
|
887
|
+
"rows": rows,
|
|
888
|
+
"type_codes": [],
|
|
889
|
+
"plan_cost": plan_cost
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
def fetch_result_types_via_temp_view(self, conn, query):
|
|
893
|
+
temp_view_name = "temp_view__" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
|
894
|
+
self.db_interface.execute_query_fetching_results(conn, "CREATE TEMP VIEW \"" + temp_view_name + "\" AS (" + query + ")")
|
|
895
|
+
try:
|
|
896
|
+
res = self.db_interface.execute_query_fetching_results(conn, """
|
|
897
|
+
SELECT format_type(COALESCE(bt.oid, t.oid), a.atttypmod) AS data_type,
|
|
898
|
+
COALESCE(bt.typname, t.typname) AS udt_name,
|
|
899
|
+
CASE WHEN t.typtype = 'd' THEN t.typname END AS domain_name
|
|
900
|
+
FROM pg_attribute a
|
|
901
|
+
JOIN pg_type t ON t.oid = a.atttypid
|
|
902
|
+
LEFT JOIN pg_type bt ON t.typtype = 'd' AND bt.oid = t.typbasetype
|
|
903
|
+
WHERE a.attrelid = '""" + temp_view_name + """'::regclass
|
|
904
|
+
AND a.attnum > 0
|
|
905
|
+
ORDER BY a.attnum""")
|
|
906
|
+
finally:
|
|
907
|
+
try:
|
|
908
|
+
self.db_interface.execute_query_fetching_results(conn, "DROP VIEW IF EXISTS \"" + temp_view_name + "\"")
|
|
909
|
+
except Exception:
|
|
910
|
+
pass
|
|
911
|
+
return res["rows"]
|
|
912
|
+
|
|
913
|
+
def resolve_column_provenance_batch(self, conn, pairs):
|
|
914
|
+
view_cache = {}
|
|
915
|
+
current = {}
|
|
916
|
+
results = {}
|
|
917
|
+
for table_oid, table_column in pairs:
|
|
918
|
+
if table_oid and table_column > 0:
|
|
919
|
+
current[(table_oid, table_column)] = (table_oid, table_column)
|
|
920
|
+
else:
|
|
921
|
+
results[(table_oid, table_column)] = None
|
|
922
|
+
|
|
923
|
+
for _ in range(self.PROVENANCE__max_view_depth + 1):
|
|
924
|
+
pending = {}
|
|
925
|
+
for origin, cursor in current.items():
|
|
926
|
+
if origin not in results:
|
|
927
|
+
pending.setdefault(cursor, []).append(origin)
|
|
928
|
+
if len(pending) == 0:
|
|
929
|
+
break
|
|
930
|
+
attributes = self.batch_attribute_lookup(conn, pending.keys())
|
|
931
|
+
for cursor, origins in pending.items():
|
|
932
|
+
attribute = attributes.get(cursor)
|
|
933
|
+
if attribute is None or attribute[4]:
|
|
934
|
+
for origin in origins:
|
|
935
|
+
results[origin] = None
|
|
936
|
+
continue
|
|
937
|
+
relkind, nspname, relname, attnotnull, attisdropped = attribute
|
|
938
|
+
if relkind != "v":
|
|
939
|
+
for origin in origins:
|
|
940
|
+
results[origin] = (nspname, relname, attnotnull)
|
|
941
|
+
continue
|
|
942
|
+
if cursor[0] not in view_cache:
|
|
943
|
+
viewdef = self.db_interface.execute_query_fetching_results(
|
|
944
|
+
conn, "SELECT pg_get_viewdef(" + str(int(cursor[0])) + ")")["rows"][0][0].strip().rstrip(";")
|
|
945
|
+
inner_provenance = []
|
|
946
|
+
self.db_interface.execute_query_fetching_results(
|
|
947
|
+
conn, "SELECT * FROM (" + viewdef + "\n) __jaaql_provenance LIMIT 0", capture_provenance=inner_provenance)
|
|
948
|
+
view_cache[cursor[0]] = inner_provenance
|
|
949
|
+
inner = view_cache[cursor[0]]
|
|
950
|
+
if cursor[1] > len(inner):
|
|
951
|
+
for origin in origins:
|
|
952
|
+
results[origin] = None
|
|
953
|
+
continue
|
|
954
|
+
_, inner_oid, inner_column = inner[cursor[1] - 1]
|
|
955
|
+
if inner_oid and inner_column > 0:
|
|
956
|
+
for origin in origins:
|
|
957
|
+
current[origin] = (inner_oid, inner_column)
|
|
958
|
+
else:
|
|
959
|
+
# The view column is an expression: its type is known but base nullability is not recoverable
|
|
960
|
+
for origin in origins:
|
|
961
|
+
results[origin] = (nspname, relname, False)
|
|
962
|
+
|
|
963
|
+
for origin in current:
|
|
964
|
+
results.setdefault(origin, None)
|
|
965
|
+
return results
|
|
966
|
+
|
|
967
|
+
def batch_attribute_lookup(self, conn, pairs):
|
|
968
|
+
values = ", ".join("(" + str(int(table_oid)) + ", " + str(int(table_column)) + ")" for table_oid, table_column in pairs)
|
|
969
|
+
res = self.db_interface.execute_query_fetching_results(conn, """
|
|
970
|
+
SELECT c.oid, a.attnum, c.relkind, n.nspname, c.relname, a.attnotnull, a.attisdropped
|
|
971
|
+
FROM pg_class c
|
|
972
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
973
|
+
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
974
|
+
WHERE (c.oid, a.attnum) IN (""" + values + ")")
|
|
975
|
+
return {(row[0], row[1]): tuple(row[2:]) for row in res["rows"]}
|
|
976
|
+
|
|
977
|
+
def fetch_null_extended_relations(self, conn, query):
|
|
978
|
+
plan_payload = self.db_interface.execute_query_fetching_results(
|
|
979
|
+
conn, "EXPLAIN (VERBOSE, FORMAT JSON) " + query)["rows"][0][0]
|
|
980
|
+
if isinstance(plan_payload, str):
|
|
981
|
+
plan_payload = json.loads(plan_payload)
|
|
982
|
+
null_extended = set()
|
|
983
|
+
|
|
984
|
+
def walk(node, is_null_extended):
|
|
985
|
+
if node.get("Parent Relationship") in ("InitPlan", "SubPlan"):
|
|
986
|
+
return
|
|
987
|
+
if is_null_extended and "Relation Name" in node:
|
|
988
|
+
null_extended.add((node.get("Schema"), node["Relation Name"]))
|
|
989
|
+
join_type = node.get("Join Type")
|
|
990
|
+
for child_idx, child in enumerate(node.get("Plans", [])):
|
|
991
|
+
walk(child, is_null_extended
|
|
992
|
+
or (join_type == "Left" and child_idx == 1)
|
|
993
|
+
or (join_type == "Right" and child_idx == 0)
|
|
994
|
+
or join_type == "Full")
|
|
995
|
+
|
|
996
|
+
walk(plan_payload[0]["Plan"], False)
|
|
997
|
+
return null_extended, plan_payload[0]["Plan"].get("Total Cost")
|
|
998
|
+
|
|
838
999
|
def pre_prepare_statement(self, query, parameters, match_regex: str = REGEX_query_argument, encryption_key: bytes = None,
|
|
839
1000
|
require_presence: bool = True, for_prepare: bool = False, skipped_singletons: list[str] = None,
|
|
840
1001
|
replace_with_nulls: bool = False):
|
|
@@ -102,20 +102,16 @@ class JAAQLController(BaseJAAQLController):
|
|
|
102
102
|
def prepare(http_inputs: dict, account_id: str):
|
|
103
103
|
return self.model.fetch_domains(http_inputs, account_id)
|
|
104
104
|
|
|
105
|
-
# verification_hook (and ip_address, which follows it) are optional: the router only injects
|
|
106
|
-
# verification_hook when the method's parallel_verification is True. With it False these verify
|
|
107
|
-
# synchronously (verification_hook stays None), so there is no shared serial verifier thread to
|
|
108
|
-
# wedge/queue-stall - which is what timed out @dba requests during a \wipe dbms rebuild.
|
|
109
105
|
@self.publish_route('/submit', DOCUMENTATION__submit)
|
|
110
|
-
def submit(http_inputs: dict, account_id: str, verification_hook: queue.Queue
|
|
106
|
+
def submit(http_inputs: dict, account_id: str, verification_hook: queue.Queue, ip_address: str):
|
|
111
107
|
return self.model.submit(http_inputs, account_id, verification_hook=verification_hook, ip_address=ip_address)
|
|
112
108
|
|
|
113
109
|
@self.publish_route('/execute', DOCUMENTATION__execute)
|
|
114
|
-
def execute(http_inputs: dict, account_id: str, verification_hook: queue.Queue
|
|
110
|
+
def execute(http_inputs: dict, account_id: str, verification_hook: queue.Queue):
|
|
115
111
|
return self.model.execute(http_inputs, account_id, verification_hook=verification_hook)
|
|
116
112
|
|
|
117
113
|
@self.publish_route('/call-proc', DOCUMENTATION__execute)
|
|
118
|
-
def call_proc(http_inputs: dict, account_id: str, verification_hook: queue.Queue
|
|
114
|
+
def call_proc(http_inputs: dict, account_id: str, verification_hook: queue.Queue):
|
|
119
115
|
return self.model.call_proc(http_inputs, account_id, verification_hook=verification_hook)
|
|
120
116
|
|
|
121
117
|
@self.publish_route('/internal/clean', DOCUMENTATION__clean)
|
|
@@ -54,6 +54,8 @@ import requests
|
|
|
54
54
|
|
|
55
55
|
from jaaql.migrations.migrations import run_migrations
|
|
56
56
|
|
|
57
|
+
PREPARE__concurrency = 6
|
|
58
|
+
|
|
57
59
|
NGINX_MARKER__first = "limit_req_status 429;\n "
|
|
58
60
|
NGINX_MARKER__second = "proxy_set_header X-Real-IP $remote_addr;\n "
|
|
59
61
|
NGINX_INSERT__frozen = " return 503;\n "
|
|
@@ -398,28 +400,16 @@ ORDER BY
|
|
|
398
400
|
|
|
399
401
|
self.is_dba(db_connection)
|
|
400
402
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
for query in inputs["queries"]:
|
|
403
|
+
def prepare_one(query):
|
|
404
404
|
my_uuid = str(uuid.uuid4()).replace("-", "_")
|
|
405
405
|
exc = None
|
|
406
406
|
cost = None
|
|
407
407
|
type_resolution_method = None
|
|
408
408
|
columns = None
|
|
409
409
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
query["query"].strip(),
|
|
414
|
-
do_prepare_only=my_uuid
|
|
415
|
-
)
|
|
416
|
-
cost = float(results["rows"][0][0].split("..")[1].split(" ")[0])
|
|
417
|
-
except Exception as ex:
|
|
418
|
-
exc = str(ex).replace("PREPARE _jaaql_query_check_" + my_uuid + " as ", "").replace(
|
|
419
|
-
" ... _jaaql_query_check_" + my_uuid + " as ", "")
|
|
420
|
-
|
|
421
|
-
if exc is None and not cost_only:
|
|
422
|
-
# Try temp_view domain type resolution first
|
|
410
|
+
if not cost_only:
|
|
411
|
+
# Try provenance/temp_view domain type resolution first; its EXPLAIN also yields the
|
|
412
|
+
# plan cost, so a success skips the separate PREPARE validation pass entirely
|
|
423
413
|
try:
|
|
424
414
|
domain_types = execute_supplied_statement(
|
|
425
415
|
db_connection,
|
|
@@ -428,6 +418,7 @@ ORDER BY
|
|
|
428
418
|
attempt_fetch_domain_types=True
|
|
429
419
|
)
|
|
430
420
|
type_resolution_method = "temp_view"
|
|
421
|
+
cost = domain_types.get("plan_cost")
|
|
431
422
|
|
|
432
423
|
temp_columns = [row[0] for row in domain_types['rows']]
|
|
433
424
|
|
|
@@ -456,8 +447,22 @@ ORDER BY
|
|
|
456
447
|
except Exception:
|
|
457
448
|
pass # This is fine
|
|
458
449
|
|
|
450
|
+
if columns is None and exc is None:
|
|
451
|
+
# Introspection unavailable (or cost_only): run the classic PREPARE pass for
|
|
452
|
+
# validation with canonical error messages, and the cost
|
|
453
|
+
try:
|
|
454
|
+
results = execute_supplied_statement(
|
|
455
|
+
db_connection,
|
|
456
|
+
query["query"].strip(),
|
|
457
|
+
do_prepare_only=my_uuid
|
|
458
|
+
)
|
|
459
|
+
cost = float(results["rows"][0][0].split("..")[1].split(" ")[0])
|
|
460
|
+
except Exception as ex:
|
|
461
|
+
exc = str(ex).replace("PREPARE _jaaql_query_check_" + my_uuid + " as ", "").replace(
|
|
462
|
+
" ... _jaaql_query_check_" + my_uuid + " as ", "")
|
|
463
|
+
|
|
459
464
|
# If temp_view didn't work, fallback to gdesc/psql
|
|
460
|
-
if
|
|
465
|
+
if exc is None and not cost_only:
|
|
461
466
|
try:
|
|
462
467
|
type_resolution_method = "gdesc"
|
|
463
468
|
psql = [
|
|
@@ -483,14 +488,20 @@ ORDER BY
|
|
|
483
488
|
type_resolution_method = "failed"
|
|
484
489
|
columns = None
|
|
485
490
|
|
|
486
|
-
|
|
491
|
+
return {
|
|
487
492
|
"location": query["file"] + ":" + str(query["line_number"]),
|
|
488
493
|
"name": query["name"],
|
|
489
494
|
"cost": cost,
|
|
490
495
|
"columns": columns,
|
|
491
496
|
"exception": exc,
|
|
492
497
|
"type_resolution_method": type_resolution_method
|
|
493
|
-
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
from gevent.pool import Pool as GeventPool
|
|
502
|
+
res = list(GeventPool(PREPARE__concurrency).map(prepare_one, inputs["queries"]))
|
|
503
|
+
except ImportError:
|
|
504
|
+
res = [prepare_one(query) for query in inputs["queries"]]
|
|
494
505
|
|
|
495
506
|
if inputs.get("sort_cost", True):
|
|
496
507
|
return sorted(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaaql-middleware-python
|
|
3
|
-
Version: 5.3.
|
|
3
|
+
Version: 5.3.13
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/config-docker.ini
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/config/config-test.ini
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/db/db_utils_no_circ.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/documentation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/email/email_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/exceptions/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/generated_constants.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/interpreter/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/migrations/__init__.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/migrations/migrations.py
RENAMED
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/base_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/controller_interface.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/exception_queries.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/generated_queries.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/handmade_queries.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/mvc/model_interface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/services/patch_mms.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/crypt_utils.py
RENAMED
|
File without changes
|
{jaaql_middleware_python-5.3.11 → jaaql_middleware_python-5.3.13}/jaaql/utilities/options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|