maxc-cli 0.9.3__tar.gz → 0.9.4__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.
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/PKG-INFO +1 -1
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/__init__.py +1 -1
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/odps.py +40 -3
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/PKG-INFO +1 -1
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_query_result_csv_fallback.py +110 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/MANIFEST.in +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/README.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/pyproject.toml +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/scripts/pyinstaller_entry.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/scripts/regression_test.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/setup.cfg +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/setup.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/__main__.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/_samples.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/agent_platforms.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/app.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/audit.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/auth_continuation.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/auth_providers.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/__init__.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/auth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/catalog.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/data.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/job.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/mcp.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/meta.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/query.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/backend/semantic.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/cache.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/catalog_bootstrap.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/cli.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/config.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/enterprise_tls.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/exceptions.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/function_metadata.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/help_format.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/helpers.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/job_ids.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/masking.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/mcp_serve.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/models.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/oauth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/odps_runtime.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/output.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/proxy_auth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/semantic.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/semantic_management.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/setting_parser.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/SKILL.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/agents/openai.yaml +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/bootstrap-auth.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/bootstrap-flow.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/command-patterns.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/json-output-format.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/maxcompute-select-guide.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/maxcompute-sql-notes.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/partition-guide.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/red-lines.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/registered-functions.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/semantic-packages.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/setup-install.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/sql-common-errors.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/sql-query-patterns.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/skills/references/text2sql-principles.md +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/state_permissions.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/store.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli/utils.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/SOURCES.txt +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/entry_points.txt +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/requires.txt +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/src/maxc_cli.egg-info/top_level.txt +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_agent_hints_and_cli.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_agent_platforms.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_agent_skill_commands.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_agent_skill_commands_context.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_ai_native_contract_regressions.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_auth_logout.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_auth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_data.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_data_serialization.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_mcp.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_meta.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_backend_query_cost.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_build_release_archive_compat.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_build_release_script.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_cache.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_catalog.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_catalog_bootstrap.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_cli_arg_validation.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_cli_mock.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_cli_query_parse_and_sanitize.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_compat.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_config_atomic_write.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_connection_bootstrap.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_e2e_smoke.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_effective_hints_contract.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_enterprise_tls.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_envelope_shape.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_error_self_correction.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_error_translation.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_exit_codes.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_external_auth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_flag_hoist.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_help_format.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_help_version_e2e.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_helpers.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_helpers_csv.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_installer_contracts.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_integration.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_integration_real.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_job_improvements.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_job_inspection.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_job_store_durability.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_kb_commands.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_manifest_runtime_contract.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_masking.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_mcp_serve.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_meta_schema_and_partition_cols.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_oauth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_odps_runtime.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_output_action_safety.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_output_format_contract.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_packaging_metadata.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_phase1_improvements.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_proxy_auth.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_pyinstaller_bundle.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_python39_compat.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_query_auto_promote.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_query_namespace_settings.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_registered_functions.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_semantic_management.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_semantic_scope.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_semantic_transport.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_setting_parser.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_skill_cli_consistency.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_skill_eval.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_skill_renderer.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_startup_imports.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_state_permissions.py +0 -0
- {maxc_cli-0.9.3 → maxc_cli-0.9.4}/tests/test_state_portability.py +0 -0
|
@@ -13,7 +13,7 @@ from ..helpers import (
|
|
|
13
13
|
translate_odps_error,
|
|
14
14
|
)
|
|
15
15
|
from ..models import QueryResult
|
|
16
|
-
from ..utils import detect_operation, extract_table_names
|
|
16
|
+
from ..utils import detect_operation, extract_table_names, sql_keyword_tokens, sql_statements
|
|
17
17
|
from .auth import AuthMixin
|
|
18
18
|
from .catalog import CatalogMixin
|
|
19
19
|
from .data import DataMixin
|
|
@@ -75,6 +75,26 @@ def _raw_task_result_rows(task_results: 'dict[str, Any]') -> 'list[dict[str, Any
|
|
|
75
75
|
]
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _is_show_partitions(sql: 'str') -> 'bool':
|
|
79
|
+
statements = sql_statements(sql)
|
|
80
|
+
return len(statements) == 1 and sql_keyword_tokens(statements[0])[:2] == ["SHOW", "PARTITIONS"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _show_partition_rows(task_results: 'Any') -> 'list[dict[str, str]]':
|
|
84
|
+
"""SHOW PARTITIONS emits headerless lines, not a CSV table with a header."""
|
|
85
|
+
if not isinstance(task_results, dict):
|
|
86
|
+
raise ValueError("SHOW PARTITIONS returned invalid task results")
|
|
87
|
+
rows = []
|
|
88
|
+
for text in task_results.values():
|
|
89
|
+
if text is None:
|
|
90
|
+
continue
|
|
91
|
+
if not isinstance(text, str):
|
|
92
|
+
raise ValueError("SHOW PARTITIONS returned invalid task result text")
|
|
93
|
+
# Split only protocol line endings; Unicode separators may be values.
|
|
94
|
+
rows.extend({"partition": line.removesuffix("\r")} for line in text.split("\n") if line.removesuffix("\r"))
|
|
95
|
+
return rows
|
|
96
|
+
|
|
97
|
+
|
|
78
98
|
class OdpsBackend(
|
|
79
99
|
JobMixin, # JobMixin extends QueryMixin
|
|
80
100
|
CatalogMixin,
|
|
@@ -123,11 +143,12 @@ class OdpsBackend(
|
|
|
123
143
|
) -> 'QueryResult':
|
|
124
144
|
"""Convert ODPS instance to QueryResult."""
|
|
125
145
|
fallback_warnings: list[str] = []
|
|
146
|
+
show_partitions = _is_show_partitions(sql)
|
|
126
147
|
statement_operation = _write_operation(sql)
|
|
127
148
|
instance_is_select = getattr(instance, "_is_select", None)
|
|
128
149
|
confirmed_task_results = None
|
|
129
150
|
resultless_statement = statement_operation is not None and instance_is_select is not True
|
|
130
|
-
if not resultless_statement and instance_is_select is False:
|
|
151
|
+
if not show_partitions and not resultless_statement and instance_is_select is False:
|
|
131
152
|
try:
|
|
132
153
|
task_results = instance.get_task_results()
|
|
133
154
|
except Exception:
|
|
@@ -140,7 +161,23 @@ class OdpsBackend(
|
|
|
140
161
|
resultless_statement = not any(str(value or "").strip() for value in task_results.values())
|
|
141
162
|
if resultless_statement and sql.strip():
|
|
142
163
|
statement_operation = detect_operation(sql)
|
|
143
|
-
if
|
|
164
|
+
if show_partitions:
|
|
165
|
+
try:
|
|
166
|
+
# The generic CSV reader consumes the first partition as a
|
|
167
|
+
# header and raises on an empty body. Read this known textual
|
|
168
|
+
# result directly, retaining lookup failures as real errors.
|
|
169
|
+
all_rows = _show_partition_rows(instance.get_task_results())
|
|
170
|
+
except Exception as exc:
|
|
171
|
+
error = translate_odps_error(exc)
|
|
172
|
+
error.instance_id = instance.id
|
|
173
|
+
error.logview = self._safe_logview(instance)
|
|
174
|
+
raise error from exc
|
|
175
|
+
schema = [{"name": "partition", "type": "string", "comment": ""}]
|
|
176
|
+
rows = all_rows[offset : offset + max_rows]
|
|
177
|
+
total_rows = len(all_rows)
|
|
178
|
+
degraded_reason = None
|
|
179
|
+
resultless_statement = False
|
|
180
|
+
elif resultless_statement:
|
|
144
181
|
# Successful write statements have no tabular result. In
|
|
145
182
|
# particular, PyODPS's DDL fallback reader may try to iterate a
|
|
146
183
|
# missing task_results value and raise after the server job has
|
|
@@ -688,3 +688,113 @@ def test_select_marker_preserves_results_for_a_known_write_operation() -> None:
|
|
|
688
688
|
)
|
|
689
689
|
assert result.rows == [{"value": "1"}]
|
|
690
690
|
assert "result_kind" not in result.extra_metadata
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
@pytest.mark.parametrize("marker", [None, False, True])
|
|
694
|
+
@pytest.mark.parametrize("partitions", [[], ["ds=20261021"], ["ds=20261021", "ds=20261022"]])
|
|
695
|
+
def test_show_partitions_preserves_headerless_rows(marker, partitions):
|
|
696
|
+
raw = "\n".join(partitions) + ("\n" if partitions else "")
|
|
697
|
+
# Use the real SDK reader which caused the regression without making its
|
|
698
|
+
# current empty/headerless parsing bugs part of MaxC's permanent contract.
|
|
699
|
+
instance = _FakeInstance(reader=CsvRecordReader(schema=None, stream=raw), task_results={"SQLTask": raw})
|
|
700
|
+
instance._is_select = marker
|
|
701
|
+
result = _StubBackend()._instance_to_query_result(instance, project="test_project", max_rows=100, sql="SHOW PARTITIONS t", elapsed_ms=1)
|
|
702
|
+
assert result.schema == [{"name": "partition", "type": "string", "comment": ""}]
|
|
703
|
+
assert result.rows == [{"partition": p} for p in partitions]
|
|
704
|
+
assert result.total_rows == result.returned_rows == len(partitions)
|
|
705
|
+
assert result.has_more is False
|
|
706
|
+
assert result.extra_metadata.get("result_kind") != "statement"
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@pytest.mark.parametrize("offset, expected, has_more", [(0, ["ds=1"], True), (1, ["ds=2"], False), (2, [], False)])
|
|
710
|
+
def test_show_partitions_pages_each_partition(offset, expected, has_more):
|
|
711
|
+
result = _run(None, sql="SET odps.sql.type.vector.enable=true; /* scope */ SHOW\nPARTITIONS t", task_results={"SQLTask": "ds=1\r\nds=2\r\n"}, offset=offset, max_rows=1)
|
|
712
|
+
assert result.rows == [{"partition": p} for p in expected]
|
|
713
|
+
assert result.total_rows == 2
|
|
714
|
+
assert result.returned_rows == len(expected)
|
|
715
|
+
assert result.has_more is has_more
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
@pytest.mark.parametrize("task_results", [{}, {"SQLTask": None}, {"SQLTask": ""}])
|
|
719
|
+
def test_show_partitions_successful_empty_task_result_has_schema(task_results):
|
|
720
|
+
result = _run(None, sql="SHOW PARTITIONS t", task_results=task_results)
|
|
721
|
+
assert result.schema[0]["name"] == "partition"
|
|
722
|
+
assert result.rows == []
|
|
723
|
+
assert result.total_rows == 0
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def test_show_partitions_lookup_failure_is_not_empty_success():
|
|
727
|
+
with pytest.raises(Exception, match="get_task_results not stubbed"):
|
|
728
|
+
_run(None, sql="SHOW PARTITIONS t", raise_on_task_results=True)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
@pytest.mark.parametrize("task_results", [None, [], {"SQLTask": object()}])
|
|
732
|
+
def test_show_partitions_invalid_task_results_are_not_empty_success(task_results):
|
|
733
|
+
instance = _FakeInstance(reader=None)
|
|
734
|
+
instance.get_task_results = lambda: task_results
|
|
735
|
+
with pytest.raises(Exception, match="invalid task result"):
|
|
736
|
+
_StubBackend()._instance_to_query_result(instance, project="test_project", max_rows=100, sql="SHOW PARTITIONS t", elapsed_ms=1)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def test_partition_like_select_header_stays_a_real_csv_header():
|
|
740
|
+
result = _run(CsvRecordReader(schema=None, stream="ds=20261021\nvalue\n"), sql="SELECT value AS `ds=20261021` FROM t")
|
|
741
|
+
assert result.rows == [{"ds=20261021": "value"}]
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def test_show_partitions_keeps_unicode_separators_inside_partition_value():
|
|
745
|
+
partition = "ds=before\u2028after"
|
|
746
|
+
result = _run(None, sql="SHOW PARTITIONS t", task_results={"SQLTask": partition + "\n"})
|
|
747
|
+
assert result.rows == [{"partition": partition}]
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@pytest.mark.parametrize("partitions", [[], ["ds=1"], ["ds=1", "ds=2"]])
|
|
751
|
+
def test_completed_job_fetch_recovers_show_partitions_sql(partitions):
|
|
752
|
+
from types import SimpleNamespace
|
|
753
|
+
|
|
754
|
+
class _CompletedInstance(_FakeInstance):
|
|
755
|
+
def get_sql_query(self):
|
|
756
|
+
return "SHOW PARTITIONS t"
|
|
757
|
+
|
|
758
|
+
instance = _CompletedInstance(
|
|
759
|
+
reader=None, task_results={"SQLTask": "\n".join(partitions)},
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
class _JobBackend(_StubBackend):
|
|
763
|
+
fetch_job_result = JobMixin.fetch_job_result
|
|
764
|
+
_safe_sql = JobMixin._safe_sql
|
|
765
|
+
|
|
766
|
+
def _get_instance(self, job_id, **kwargs):
|
|
767
|
+
assert job_id == instance.id
|
|
768
|
+
return instance
|
|
769
|
+
|
|
770
|
+
def _instance_to_job_info(self, instance, **kwargs):
|
|
771
|
+
return SimpleNamespace(status="success")
|
|
772
|
+
|
|
773
|
+
def _rehydrate_sqlrt_result_instance(self, instance):
|
|
774
|
+
return instance
|
|
775
|
+
|
|
776
|
+
result = _JobBackend().fetch_job_result(instance.id, max_rows=1, offset=0)
|
|
777
|
+
assert result.sql_executed == "SHOW PARTITIONS t"
|
|
778
|
+
assert result.schema[0]["name"] == "partition"
|
|
779
|
+
assert result.rows == [{"partition": p} for p in partitions[:1]]
|
|
780
|
+
assert result.total_rows == len(partitions)
|
|
781
|
+
assert result.has_more is (len(partitions) > 1)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def test_show_partitions_permission_error_preserves_original_job():
|
|
785
|
+
from odps.errors import NoPermission
|
|
786
|
+
|
|
787
|
+
from maxc_cli.exceptions import PermissionDeniedError
|
|
788
|
+
|
|
789
|
+
class _DeniedInstance(_FakeInstance):
|
|
790
|
+
def get_task_results(self):
|
|
791
|
+
raise NoPermission("Access denied", request_id="show-partitions-request")
|
|
792
|
+
|
|
793
|
+
with pytest.raises(PermissionDeniedError) as caught:
|
|
794
|
+
_StubBackend()._instance_to_query_result(
|
|
795
|
+
_DeniedInstance(reader=None), project="test_project", max_rows=100,
|
|
796
|
+
sql="SHOW PARTITIONS t", elapsed_ms=1,
|
|
797
|
+
)
|
|
798
|
+
assert caught.value.error_code == "PERMISSION_DENIED"
|
|
799
|
+
assert caught.value.instance_id == _DeniedInstance.id
|
|
800
|
+
assert "show-partitions-request" in caught.value.suggestion
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|