truthound 1.0.8__py3-none-any.whl
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.
- truthound/__init__.py +162 -0
- truthound/adapters.py +100 -0
- truthound/api.py +365 -0
- truthound/audit/__init__.py +248 -0
- truthound/audit/core.py +967 -0
- truthound/audit/filters.py +620 -0
- truthound/audit/formatters.py +707 -0
- truthound/audit/logger.py +902 -0
- truthound/audit/middleware.py +571 -0
- truthound/audit/storage.py +1083 -0
- truthound/benchmark/__init__.py +123 -0
- truthound/benchmark/base.py +757 -0
- truthound/benchmark/comparison.py +635 -0
- truthound/benchmark/generators.py +706 -0
- truthound/benchmark/reporters.py +718 -0
- truthound/benchmark/runner.py +635 -0
- truthound/benchmark/scenarios.py +712 -0
- truthound/cache.py +252 -0
- truthound/checkpoint/__init__.py +136 -0
- truthound/checkpoint/actions/__init__.py +164 -0
- truthound/checkpoint/actions/base.py +324 -0
- truthound/checkpoint/actions/custom.py +234 -0
- truthound/checkpoint/actions/discord_notify.py +290 -0
- truthound/checkpoint/actions/email_notify.py +405 -0
- truthound/checkpoint/actions/github_action.py +406 -0
- truthound/checkpoint/actions/opsgenie.py +1499 -0
- truthound/checkpoint/actions/pagerduty.py +226 -0
- truthound/checkpoint/actions/slack_notify.py +233 -0
- truthound/checkpoint/actions/store_result.py +249 -0
- truthound/checkpoint/actions/teams_notify.py +1570 -0
- truthound/checkpoint/actions/telegram_notify.py +419 -0
- truthound/checkpoint/actions/update_docs.py +552 -0
- truthound/checkpoint/actions/webhook.py +293 -0
- truthound/checkpoint/analytics/__init__.py +147 -0
- truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
- truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
- truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
- truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
- truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
- truthound/checkpoint/analytics/analyzers/base.py +270 -0
- truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
- truthound/checkpoint/analytics/analyzers/trend.py +314 -0
- truthound/checkpoint/analytics/models.py +292 -0
- truthound/checkpoint/analytics/protocols.py +549 -0
- truthound/checkpoint/analytics/service.py +718 -0
- truthound/checkpoint/analytics/stores/__init__.py +16 -0
- truthound/checkpoint/analytics/stores/base.py +306 -0
- truthound/checkpoint/analytics/stores/memory_store.py +353 -0
- truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
- truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
- truthound/checkpoint/async_actions.py +794 -0
- truthound/checkpoint/async_base.py +708 -0
- truthound/checkpoint/async_checkpoint.py +617 -0
- truthound/checkpoint/async_runner.py +639 -0
- truthound/checkpoint/checkpoint.py +527 -0
- truthound/checkpoint/ci/__init__.py +61 -0
- truthound/checkpoint/ci/detector.py +355 -0
- truthound/checkpoint/ci/reporter.py +436 -0
- truthound/checkpoint/ci/templates.py +454 -0
- truthound/checkpoint/circuitbreaker/__init__.py +133 -0
- truthound/checkpoint/circuitbreaker/breaker.py +542 -0
- truthound/checkpoint/circuitbreaker/core.py +252 -0
- truthound/checkpoint/circuitbreaker/detection.py +459 -0
- truthound/checkpoint/circuitbreaker/middleware.py +389 -0
- truthound/checkpoint/circuitbreaker/registry.py +357 -0
- truthound/checkpoint/distributed/__init__.py +139 -0
- truthound/checkpoint/distributed/backends/__init__.py +35 -0
- truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
- truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
- truthound/checkpoint/distributed/backends/local_backend.py +397 -0
- truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
- truthound/checkpoint/distributed/base.py +774 -0
- truthound/checkpoint/distributed/orchestrator.py +765 -0
- truthound/checkpoint/distributed/protocols.py +842 -0
- truthound/checkpoint/distributed/registry.py +449 -0
- truthound/checkpoint/idempotency/__init__.py +120 -0
- truthound/checkpoint/idempotency/core.py +295 -0
- truthound/checkpoint/idempotency/fingerprint.py +454 -0
- truthound/checkpoint/idempotency/locking.py +604 -0
- truthound/checkpoint/idempotency/service.py +592 -0
- truthound/checkpoint/idempotency/stores.py +653 -0
- truthound/checkpoint/monitoring/__init__.py +134 -0
- truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
- truthound/checkpoint/monitoring/aggregators/base.py +372 -0
- truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
- truthound/checkpoint/monitoring/aggregators/window.py +493 -0
- truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
- truthound/checkpoint/monitoring/collectors/base.py +257 -0
- truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
- truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
- truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
- truthound/checkpoint/monitoring/events.py +410 -0
- truthound/checkpoint/monitoring/protocols.py +636 -0
- truthound/checkpoint/monitoring/service.py +578 -0
- truthound/checkpoint/monitoring/views/__init__.py +17 -0
- truthound/checkpoint/monitoring/views/base.py +172 -0
- truthound/checkpoint/monitoring/views/queue_view.py +220 -0
- truthound/checkpoint/monitoring/views/task_view.py +240 -0
- truthound/checkpoint/monitoring/views/worker_view.py +263 -0
- truthound/checkpoint/registry.py +337 -0
- truthound/checkpoint/runner.py +356 -0
- truthound/checkpoint/transaction/__init__.py +133 -0
- truthound/checkpoint/transaction/base.py +389 -0
- truthound/checkpoint/transaction/compensatable.py +537 -0
- truthound/checkpoint/transaction/coordinator.py +576 -0
- truthound/checkpoint/transaction/executor.py +622 -0
- truthound/checkpoint/transaction/idempotency.py +534 -0
- truthound/checkpoint/transaction/saga/__init__.py +143 -0
- truthound/checkpoint/transaction/saga/builder.py +584 -0
- truthound/checkpoint/transaction/saga/definition.py +515 -0
- truthound/checkpoint/transaction/saga/event_store.py +542 -0
- truthound/checkpoint/transaction/saga/patterns.py +833 -0
- truthound/checkpoint/transaction/saga/runner.py +718 -0
- truthound/checkpoint/transaction/saga/state_machine.py +793 -0
- truthound/checkpoint/transaction/saga/strategies.py +780 -0
- truthound/checkpoint/transaction/saga/testing.py +886 -0
- truthound/checkpoint/triggers/__init__.py +58 -0
- truthound/checkpoint/triggers/base.py +237 -0
- truthound/checkpoint/triggers/event.py +385 -0
- truthound/checkpoint/triggers/schedule.py +355 -0
- truthound/cli.py +2358 -0
- truthound/cli_modules/__init__.py +124 -0
- truthound/cli_modules/advanced/__init__.py +45 -0
- truthound/cli_modules/advanced/benchmark.py +343 -0
- truthound/cli_modules/advanced/docs.py +225 -0
- truthound/cli_modules/advanced/lineage.py +209 -0
- truthound/cli_modules/advanced/ml.py +320 -0
- truthound/cli_modules/advanced/realtime.py +196 -0
- truthound/cli_modules/checkpoint/__init__.py +46 -0
- truthound/cli_modules/checkpoint/init.py +114 -0
- truthound/cli_modules/checkpoint/list.py +71 -0
- truthound/cli_modules/checkpoint/run.py +159 -0
- truthound/cli_modules/checkpoint/validate.py +67 -0
- truthound/cli_modules/common/__init__.py +71 -0
- truthound/cli_modules/common/errors.py +414 -0
- truthound/cli_modules/common/options.py +419 -0
- truthound/cli_modules/common/output.py +507 -0
- truthound/cli_modules/common/protocol.py +552 -0
- truthound/cli_modules/core/__init__.py +48 -0
- truthound/cli_modules/core/check.py +123 -0
- truthound/cli_modules/core/compare.py +104 -0
- truthound/cli_modules/core/learn.py +57 -0
- truthound/cli_modules/core/mask.py +77 -0
- truthound/cli_modules/core/profile.py +65 -0
- truthound/cli_modules/core/scan.py +61 -0
- truthound/cli_modules/profiler/__init__.py +51 -0
- truthound/cli_modules/profiler/auto_profile.py +175 -0
- truthound/cli_modules/profiler/metadata.py +107 -0
- truthound/cli_modules/profiler/suite.py +283 -0
- truthound/cli_modules/registry.py +431 -0
- truthound/cli_modules/scaffolding/__init__.py +89 -0
- truthound/cli_modules/scaffolding/base.py +631 -0
- truthound/cli_modules/scaffolding/commands.py +545 -0
- truthound/cli_modules/scaffolding/plugins.py +1072 -0
- truthound/cli_modules/scaffolding/reporters.py +594 -0
- truthound/cli_modules/scaffolding/validators.py +1127 -0
- truthound/common/__init__.py +18 -0
- truthound/common/resilience/__init__.py +130 -0
- truthound/common/resilience/bulkhead.py +266 -0
- truthound/common/resilience/circuit_breaker.py +516 -0
- truthound/common/resilience/composite.py +332 -0
- truthound/common/resilience/config.py +292 -0
- truthound/common/resilience/protocols.py +217 -0
- truthound/common/resilience/rate_limiter.py +404 -0
- truthound/common/resilience/retry.py +341 -0
- truthound/datadocs/__init__.py +260 -0
- truthound/datadocs/base.py +571 -0
- truthound/datadocs/builder.py +761 -0
- truthound/datadocs/charts.py +764 -0
- truthound/datadocs/dashboard/__init__.py +63 -0
- truthound/datadocs/dashboard/app.py +576 -0
- truthound/datadocs/dashboard/components.py +584 -0
- truthound/datadocs/dashboard/state.py +240 -0
- truthound/datadocs/engine/__init__.py +46 -0
- truthound/datadocs/engine/context.py +376 -0
- truthound/datadocs/engine/pipeline.py +618 -0
- truthound/datadocs/engine/registry.py +469 -0
- truthound/datadocs/exporters/__init__.py +49 -0
- truthound/datadocs/exporters/base.py +198 -0
- truthound/datadocs/exporters/html.py +178 -0
- truthound/datadocs/exporters/json_exporter.py +253 -0
- truthound/datadocs/exporters/markdown.py +284 -0
- truthound/datadocs/exporters/pdf.py +392 -0
- truthound/datadocs/i18n/__init__.py +86 -0
- truthound/datadocs/i18n/catalog.py +960 -0
- truthound/datadocs/i18n/formatting.py +505 -0
- truthound/datadocs/i18n/loader.py +256 -0
- truthound/datadocs/i18n/plurals.py +378 -0
- truthound/datadocs/renderers/__init__.py +42 -0
- truthound/datadocs/renderers/base.py +401 -0
- truthound/datadocs/renderers/custom.py +342 -0
- truthound/datadocs/renderers/jinja.py +697 -0
- truthound/datadocs/sections.py +736 -0
- truthound/datadocs/styles.py +931 -0
- truthound/datadocs/themes/__init__.py +101 -0
- truthound/datadocs/themes/base.py +336 -0
- truthound/datadocs/themes/default.py +417 -0
- truthound/datadocs/themes/enterprise.py +419 -0
- truthound/datadocs/themes/loader.py +336 -0
- truthound/datadocs/themes.py +301 -0
- truthound/datadocs/transformers/__init__.py +57 -0
- truthound/datadocs/transformers/base.py +268 -0
- truthound/datadocs/transformers/enrichers.py +544 -0
- truthound/datadocs/transformers/filters.py +447 -0
- truthound/datadocs/transformers/i18n.py +468 -0
- truthound/datadocs/versioning/__init__.py +62 -0
- truthound/datadocs/versioning/diff.py +639 -0
- truthound/datadocs/versioning/storage.py +497 -0
- truthound/datadocs/versioning/version.py +358 -0
- truthound/datasources/__init__.py +223 -0
- truthound/datasources/_async_protocols.py +222 -0
- truthound/datasources/_protocols.py +159 -0
- truthound/datasources/adapters.py +428 -0
- truthound/datasources/async_base.py +599 -0
- truthound/datasources/async_factory.py +511 -0
- truthound/datasources/base.py +516 -0
- truthound/datasources/factory.py +433 -0
- truthound/datasources/nosql/__init__.py +47 -0
- truthound/datasources/nosql/base.py +487 -0
- truthound/datasources/nosql/elasticsearch.py +801 -0
- truthound/datasources/nosql/mongodb.py +636 -0
- truthound/datasources/pandas_optimized.py +582 -0
- truthound/datasources/pandas_source.py +216 -0
- truthound/datasources/polars_source.py +395 -0
- truthound/datasources/spark_source.py +479 -0
- truthound/datasources/sql/__init__.py +154 -0
- truthound/datasources/sql/base.py +710 -0
- truthound/datasources/sql/bigquery.py +410 -0
- truthound/datasources/sql/cloud_base.py +199 -0
- truthound/datasources/sql/databricks.py +471 -0
- truthound/datasources/sql/mysql.py +316 -0
- truthound/datasources/sql/oracle.py +427 -0
- truthound/datasources/sql/postgresql.py +321 -0
- truthound/datasources/sql/redshift.py +479 -0
- truthound/datasources/sql/snowflake.py +439 -0
- truthound/datasources/sql/sqlite.py +286 -0
- truthound/datasources/sql/sqlserver.py +437 -0
- truthound/datasources/streaming/__init__.py +47 -0
- truthound/datasources/streaming/base.py +350 -0
- truthound/datasources/streaming/kafka.py +670 -0
- truthound/decorators.py +98 -0
- truthound/docs/__init__.py +69 -0
- truthound/docs/extractor.py +971 -0
- truthound/docs/generator.py +601 -0
- truthound/docs/parser.py +1037 -0
- truthound/docs/renderer.py +999 -0
- truthound/drift/__init__.py +22 -0
- truthound/drift/compare.py +189 -0
- truthound/drift/detectors.py +464 -0
- truthound/drift/report.py +160 -0
- truthound/execution/__init__.py +65 -0
- truthound/execution/_protocols.py +324 -0
- truthound/execution/base.py +576 -0
- truthound/execution/distributed/__init__.py +179 -0
- truthound/execution/distributed/aggregations.py +731 -0
- truthound/execution/distributed/arrow_bridge.py +817 -0
- truthound/execution/distributed/base.py +550 -0
- truthound/execution/distributed/dask_engine.py +976 -0
- truthound/execution/distributed/mixins.py +766 -0
- truthound/execution/distributed/protocols.py +756 -0
- truthound/execution/distributed/ray_engine.py +1127 -0
- truthound/execution/distributed/registry.py +446 -0
- truthound/execution/distributed/spark_engine.py +1011 -0
- truthound/execution/distributed/validator_adapter.py +682 -0
- truthound/execution/pandas_engine.py +401 -0
- truthound/execution/polars_engine.py +497 -0
- truthound/execution/pushdown/__init__.py +230 -0
- truthound/execution/pushdown/ast.py +1550 -0
- truthound/execution/pushdown/builder.py +1550 -0
- truthound/execution/pushdown/dialects.py +1072 -0
- truthound/execution/pushdown/executor.py +829 -0
- truthound/execution/pushdown/optimizer.py +1041 -0
- truthound/execution/sql_engine.py +518 -0
- truthound/infrastructure/__init__.py +189 -0
- truthound/infrastructure/audit.py +1515 -0
- truthound/infrastructure/config.py +1133 -0
- truthound/infrastructure/encryption.py +1132 -0
- truthound/infrastructure/logging.py +1503 -0
- truthound/infrastructure/metrics.py +1220 -0
- truthound/lineage/__init__.py +89 -0
- truthound/lineage/base.py +746 -0
- truthound/lineage/impact_analysis.py +474 -0
- truthound/lineage/integrations/__init__.py +22 -0
- truthound/lineage/integrations/openlineage.py +548 -0
- truthound/lineage/tracker.py +512 -0
- truthound/lineage/visualization/__init__.py +33 -0
- truthound/lineage/visualization/protocols.py +145 -0
- truthound/lineage/visualization/renderers/__init__.py +20 -0
- truthound/lineage/visualization/renderers/cytoscape.py +329 -0
- truthound/lineage/visualization/renderers/d3.py +331 -0
- truthound/lineage/visualization/renderers/graphviz.py +276 -0
- truthound/lineage/visualization/renderers/mermaid.py +308 -0
- truthound/maskers.py +113 -0
- truthound/ml/__init__.py +124 -0
- truthound/ml/anomaly_models/__init__.py +31 -0
- truthound/ml/anomaly_models/ensemble.py +362 -0
- truthound/ml/anomaly_models/isolation_forest.py +444 -0
- truthound/ml/anomaly_models/statistical.py +392 -0
- truthound/ml/base.py +1178 -0
- truthound/ml/drift_detection/__init__.py +26 -0
- truthound/ml/drift_detection/concept.py +381 -0
- truthound/ml/drift_detection/distribution.py +361 -0
- truthound/ml/drift_detection/feature.py +442 -0
- truthound/ml/drift_detection/multivariate.py +495 -0
- truthound/ml/monitoring/__init__.py +88 -0
- truthound/ml/monitoring/alerting/__init__.py +33 -0
- truthound/ml/monitoring/alerting/handlers.py +427 -0
- truthound/ml/monitoring/alerting/rules.py +508 -0
- truthound/ml/monitoring/collectors/__init__.py +19 -0
- truthound/ml/monitoring/collectors/composite.py +105 -0
- truthound/ml/monitoring/collectors/drift.py +324 -0
- truthound/ml/monitoring/collectors/performance.py +179 -0
- truthound/ml/monitoring/collectors/quality.py +369 -0
- truthound/ml/monitoring/monitor.py +536 -0
- truthound/ml/monitoring/protocols.py +451 -0
- truthound/ml/monitoring/stores/__init__.py +15 -0
- truthound/ml/monitoring/stores/memory.py +201 -0
- truthound/ml/monitoring/stores/prometheus.py +296 -0
- truthound/ml/rule_learning/__init__.py +25 -0
- truthound/ml/rule_learning/constraint_miner.py +443 -0
- truthound/ml/rule_learning/pattern_learner.py +499 -0
- truthound/ml/rule_learning/profile_learner.py +462 -0
- truthound/multitenancy/__init__.py +326 -0
- truthound/multitenancy/core.py +852 -0
- truthound/multitenancy/integration.py +597 -0
- truthound/multitenancy/isolation.py +630 -0
- truthound/multitenancy/manager.py +770 -0
- truthound/multitenancy/middleware.py +765 -0
- truthound/multitenancy/quota.py +537 -0
- truthound/multitenancy/resolvers.py +603 -0
- truthound/multitenancy/storage.py +703 -0
- truthound/observability/__init__.py +307 -0
- truthound/observability/context.py +531 -0
- truthound/observability/instrumentation.py +611 -0
- truthound/observability/logging.py +887 -0
- truthound/observability/metrics.py +1157 -0
- truthound/observability/tracing/__init__.py +178 -0
- truthound/observability/tracing/baggage.py +310 -0
- truthound/observability/tracing/config.py +426 -0
- truthound/observability/tracing/exporter.py +787 -0
- truthound/observability/tracing/integration.py +1018 -0
- truthound/observability/tracing/otel/__init__.py +146 -0
- truthound/observability/tracing/otel/adapter.py +982 -0
- truthound/observability/tracing/otel/bridge.py +1177 -0
- truthound/observability/tracing/otel/compat.py +681 -0
- truthound/observability/tracing/otel/config.py +691 -0
- truthound/observability/tracing/otel/detection.py +327 -0
- truthound/observability/tracing/otel/protocols.py +426 -0
- truthound/observability/tracing/processor.py +561 -0
- truthound/observability/tracing/propagator.py +757 -0
- truthound/observability/tracing/provider.py +569 -0
- truthound/observability/tracing/resource.py +515 -0
- truthound/observability/tracing/sampler.py +487 -0
- truthound/observability/tracing/span.py +676 -0
- truthound/plugins/__init__.py +198 -0
- truthound/plugins/base.py +599 -0
- truthound/plugins/cli.py +680 -0
- truthound/plugins/dependencies/__init__.py +42 -0
- truthound/plugins/dependencies/graph.py +422 -0
- truthound/plugins/dependencies/resolver.py +417 -0
- truthound/plugins/discovery.py +379 -0
- truthound/plugins/docs/__init__.py +46 -0
- truthound/plugins/docs/extractor.py +444 -0
- truthound/plugins/docs/renderer.py +499 -0
- truthound/plugins/enterprise_manager.py +877 -0
- truthound/plugins/examples/__init__.py +19 -0
- truthound/plugins/examples/custom_validators.py +317 -0
- truthound/plugins/examples/slack_notifier.py +312 -0
- truthound/plugins/examples/xml_reporter.py +254 -0
- truthound/plugins/hooks.py +558 -0
- truthound/plugins/lifecycle/__init__.py +43 -0
- truthound/plugins/lifecycle/hot_reload.py +402 -0
- truthound/plugins/lifecycle/manager.py +371 -0
- truthound/plugins/manager.py +736 -0
- truthound/plugins/registry.py +338 -0
- truthound/plugins/security/__init__.py +93 -0
- truthound/plugins/security/exceptions.py +332 -0
- truthound/plugins/security/policies.py +348 -0
- truthound/plugins/security/protocols.py +643 -0
- truthound/plugins/security/sandbox/__init__.py +45 -0
- truthound/plugins/security/sandbox/context.py +158 -0
- truthound/plugins/security/sandbox/engines/__init__.py +19 -0
- truthound/plugins/security/sandbox/engines/container.py +379 -0
- truthound/plugins/security/sandbox/engines/noop.py +144 -0
- truthound/plugins/security/sandbox/engines/process.py +336 -0
- truthound/plugins/security/sandbox/factory.py +211 -0
- truthound/plugins/security/signing/__init__.py +57 -0
- truthound/plugins/security/signing/service.py +330 -0
- truthound/plugins/security/signing/trust_store.py +368 -0
- truthound/plugins/security/signing/verifier.py +459 -0
- truthound/plugins/versioning/__init__.py +41 -0
- truthound/plugins/versioning/constraints.py +297 -0
- truthound/plugins/versioning/resolver.py +329 -0
- truthound/profiler/__init__.py +1729 -0
- truthound/profiler/_lazy.py +452 -0
- truthound/profiler/ab_testing/__init__.py +80 -0
- truthound/profiler/ab_testing/analysis.py +449 -0
- truthound/profiler/ab_testing/base.py +257 -0
- truthound/profiler/ab_testing/experiment.py +395 -0
- truthound/profiler/ab_testing/tracking.py +368 -0
- truthound/profiler/auto_threshold.py +1170 -0
- truthound/profiler/base.py +579 -0
- truthound/profiler/cache_patterns.py +911 -0
- truthound/profiler/caching.py +1303 -0
- truthound/profiler/column_profiler.py +712 -0
- truthound/profiler/comparison.py +1007 -0
- truthound/profiler/custom_patterns.py +1170 -0
- truthound/profiler/dashboard/__init__.py +50 -0
- truthound/profiler/dashboard/app.py +476 -0
- truthound/profiler/dashboard/components.py +457 -0
- truthound/profiler/dashboard/config.py +72 -0
- truthound/profiler/distributed/__init__.py +83 -0
- truthound/profiler/distributed/base.py +281 -0
- truthound/profiler/distributed/dask_backend.py +498 -0
- truthound/profiler/distributed/local_backend.py +293 -0
- truthound/profiler/distributed/profiler.py +304 -0
- truthound/profiler/distributed/ray_backend.py +374 -0
- truthound/profiler/distributed/spark_backend.py +375 -0
- truthound/profiler/distributed.py +1366 -0
- truthound/profiler/enterprise_sampling.py +1065 -0
- truthound/profiler/errors.py +488 -0
- truthound/profiler/evolution/__init__.py +91 -0
- truthound/profiler/evolution/alerts.py +426 -0
- truthound/profiler/evolution/changes.py +206 -0
- truthound/profiler/evolution/compatibility.py +365 -0
- truthound/profiler/evolution/detector.py +372 -0
- truthound/profiler/evolution/protocols.py +121 -0
- truthound/profiler/generators/__init__.py +48 -0
- truthound/profiler/generators/base.py +384 -0
- truthound/profiler/generators/ml_rules.py +375 -0
- truthound/profiler/generators/pattern_rules.py +384 -0
- truthound/profiler/generators/schema_rules.py +267 -0
- truthound/profiler/generators/stats_rules.py +324 -0
- truthound/profiler/generators/suite_generator.py +857 -0
- truthound/profiler/i18n.py +1542 -0
- truthound/profiler/incremental.py +554 -0
- truthound/profiler/incremental_validation.py +1710 -0
- truthound/profiler/integration/__init__.py +73 -0
- truthound/profiler/integration/adapters.py +345 -0
- truthound/profiler/integration/context.py +371 -0
- truthound/profiler/integration/executor.py +527 -0
- truthound/profiler/integration/naming.py +75 -0
- truthound/profiler/integration/protocols.py +243 -0
- truthound/profiler/memory.py +1185 -0
- truthound/profiler/migration/__init__.py +60 -0
- truthound/profiler/migration/base.py +345 -0
- truthound/profiler/migration/manager.py +444 -0
- truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
- truthound/profiler/ml/__init__.py +73 -0
- truthound/profiler/ml/base.py +244 -0
- truthound/profiler/ml/classifier.py +507 -0
- truthound/profiler/ml/feature_extraction.py +604 -0
- truthound/profiler/ml/pretrained.py +448 -0
- truthound/profiler/ml_inference.py +1276 -0
- truthound/profiler/native_patterns.py +815 -0
- truthound/profiler/observability.py +1184 -0
- truthound/profiler/process_timeout.py +1566 -0
- truthound/profiler/progress.py +568 -0
- truthound/profiler/progress_callbacks.py +1734 -0
- truthound/profiler/quality.py +1345 -0
- truthound/profiler/resilience.py +1180 -0
- truthound/profiler/sampled_matcher.py +794 -0
- truthound/profiler/sampling.py +1288 -0
- truthound/profiler/scheduling/__init__.py +82 -0
- truthound/profiler/scheduling/protocols.py +214 -0
- truthound/profiler/scheduling/scheduler.py +474 -0
- truthound/profiler/scheduling/storage.py +457 -0
- truthound/profiler/scheduling/triggers.py +449 -0
- truthound/profiler/schema.py +603 -0
- truthound/profiler/streaming.py +685 -0
- truthound/profiler/streaming_patterns.py +1354 -0
- truthound/profiler/suite_cli.py +625 -0
- truthound/profiler/suite_config.py +789 -0
- truthound/profiler/suite_export.py +1268 -0
- truthound/profiler/table_profiler.py +547 -0
- truthound/profiler/timeout.py +565 -0
- truthound/profiler/validation.py +1532 -0
- truthound/profiler/visualization/__init__.py +118 -0
- truthound/profiler/visualization/base.py +346 -0
- truthound/profiler/visualization/generator.py +1259 -0
- truthound/profiler/visualization/plotly_renderer.py +811 -0
- truthound/profiler/visualization/renderers.py +669 -0
- truthound/profiler/visualization/sections.py +540 -0
- truthound/profiler/visualization.py +2122 -0
- truthound/profiler/yaml_validation.py +1151 -0
- truthound/py.typed +0 -0
- truthound/ratelimit/__init__.py +248 -0
- truthound/ratelimit/algorithms.py +1108 -0
- truthound/ratelimit/core.py +573 -0
- truthound/ratelimit/integration.py +532 -0
- truthound/ratelimit/limiter.py +663 -0
- truthound/ratelimit/middleware.py +700 -0
- truthound/ratelimit/policy.py +792 -0
- truthound/ratelimit/storage.py +763 -0
- truthound/rbac/__init__.py +340 -0
- truthound/rbac/core.py +976 -0
- truthound/rbac/integration.py +760 -0
- truthound/rbac/manager.py +1052 -0
- truthound/rbac/middleware.py +842 -0
- truthound/rbac/policy.py +954 -0
- truthound/rbac/storage.py +878 -0
- truthound/realtime/__init__.py +141 -0
- truthound/realtime/adapters/__init__.py +43 -0
- truthound/realtime/adapters/base.py +533 -0
- truthound/realtime/adapters/kafka.py +487 -0
- truthound/realtime/adapters/kinesis.py +479 -0
- truthound/realtime/adapters/mock.py +243 -0
- truthound/realtime/base.py +553 -0
- truthound/realtime/factory.py +382 -0
- truthound/realtime/incremental.py +660 -0
- truthound/realtime/processing/__init__.py +67 -0
- truthound/realtime/processing/exactly_once.py +575 -0
- truthound/realtime/processing/state.py +547 -0
- truthound/realtime/processing/windows.py +647 -0
- truthound/realtime/protocols.py +569 -0
- truthound/realtime/streaming.py +605 -0
- truthound/realtime/testing/__init__.py +32 -0
- truthound/realtime/testing/containers.py +615 -0
- truthound/realtime/testing/fixtures.py +484 -0
- truthound/report.py +280 -0
- truthound/reporters/__init__.py +46 -0
- truthound/reporters/_protocols.py +30 -0
- truthound/reporters/base.py +324 -0
- truthound/reporters/ci/__init__.py +66 -0
- truthound/reporters/ci/azure.py +436 -0
- truthound/reporters/ci/base.py +509 -0
- truthound/reporters/ci/bitbucket.py +567 -0
- truthound/reporters/ci/circleci.py +547 -0
- truthound/reporters/ci/detection.py +364 -0
- truthound/reporters/ci/factory.py +182 -0
- truthound/reporters/ci/github.py +388 -0
- truthound/reporters/ci/gitlab.py +471 -0
- truthound/reporters/ci/jenkins.py +525 -0
- truthound/reporters/console_reporter.py +299 -0
- truthound/reporters/factory.py +211 -0
- truthound/reporters/html_reporter.py +524 -0
- truthound/reporters/json_reporter.py +256 -0
- truthound/reporters/markdown_reporter.py +280 -0
- truthound/reporters/sdk/__init__.py +174 -0
- truthound/reporters/sdk/builder.py +558 -0
- truthound/reporters/sdk/mixins.py +1150 -0
- truthound/reporters/sdk/schema.py +1493 -0
- truthound/reporters/sdk/templates.py +666 -0
- truthound/reporters/sdk/testing.py +968 -0
- truthound/scanners.py +170 -0
- truthound/scheduling/__init__.py +122 -0
- truthound/scheduling/cron.py +1136 -0
- truthound/scheduling/presets.py +212 -0
- truthound/schema.py +275 -0
- truthound/secrets/__init__.py +173 -0
- truthound/secrets/base.py +618 -0
- truthound/secrets/cloud.py +682 -0
- truthound/secrets/integration.py +507 -0
- truthound/secrets/manager.py +633 -0
- truthound/secrets/oidc/__init__.py +172 -0
- truthound/secrets/oidc/base.py +902 -0
- truthound/secrets/oidc/credential_provider.py +623 -0
- truthound/secrets/oidc/exchangers.py +1001 -0
- truthound/secrets/oidc/github/__init__.py +110 -0
- truthound/secrets/oidc/github/claims.py +718 -0
- truthound/secrets/oidc/github/enhanced_provider.py +693 -0
- truthound/secrets/oidc/github/trust_policy.py +742 -0
- truthound/secrets/oidc/github/verification.py +723 -0
- truthound/secrets/oidc/github/workflow.py +691 -0
- truthound/secrets/oidc/providers.py +825 -0
- truthound/secrets/providers.py +506 -0
- truthound/secrets/resolver.py +495 -0
- truthound/stores/__init__.py +177 -0
- truthound/stores/backends/__init__.py +18 -0
- truthound/stores/backends/_protocols.py +340 -0
- truthound/stores/backends/azure_blob.py +530 -0
- truthound/stores/backends/concurrent_filesystem.py +915 -0
- truthound/stores/backends/connection_pool.py +1365 -0
- truthound/stores/backends/database.py +743 -0
- truthound/stores/backends/filesystem.py +538 -0
- truthound/stores/backends/gcs.py +399 -0
- truthound/stores/backends/memory.py +354 -0
- truthound/stores/backends/s3.py +434 -0
- truthound/stores/backpressure/__init__.py +84 -0
- truthound/stores/backpressure/base.py +375 -0
- truthound/stores/backpressure/circuit_breaker.py +434 -0
- truthound/stores/backpressure/monitor.py +376 -0
- truthound/stores/backpressure/strategies.py +677 -0
- truthound/stores/base.py +551 -0
- truthound/stores/batching/__init__.py +65 -0
- truthound/stores/batching/base.py +305 -0
- truthound/stores/batching/buffer.py +370 -0
- truthound/stores/batching/store.py +248 -0
- truthound/stores/batching/writer.py +521 -0
- truthound/stores/caching/__init__.py +60 -0
- truthound/stores/caching/backends.py +684 -0
- truthound/stores/caching/base.py +356 -0
- truthound/stores/caching/store.py +305 -0
- truthound/stores/compression/__init__.py +193 -0
- truthound/stores/compression/adaptive.py +694 -0
- truthound/stores/compression/base.py +514 -0
- truthound/stores/compression/pipeline.py +868 -0
- truthound/stores/compression/providers.py +672 -0
- truthound/stores/compression/streaming.py +832 -0
- truthound/stores/concurrency/__init__.py +81 -0
- truthound/stores/concurrency/atomic.py +556 -0
- truthound/stores/concurrency/index.py +775 -0
- truthound/stores/concurrency/locks.py +576 -0
- truthound/stores/concurrency/manager.py +482 -0
- truthound/stores/encryption/__init__.py +297 -0
- truthound/stores/encryption/base.py +952 -0
- truthound/stores/encryption/keys.py +1191 -0
- truthound/stores/encryption/pipeline.py +903 -0
- truthound/stores/encryption/providers.py +953 -0
- truthound/stores/encryption/streaming.py +950 -0
- truthound/stores/expectations.py +227 -0
- truthound/stores/factory.py +246 -0
- truthound/stores/migration/__init__.py +75 -0
- truthound/stores/migration/base.py +480 -0
- truthound/stores/migration/manager.py +347 -0
- truthound/stores/migration/registry.py +382 -0
- truthound/stores/migration/store.py +559 -0
- truthound/stores/observability/__init__.py +106 -0
- truthound/stores/observability/audit.py +718 -0
- truthound/stores/observability/config.py +270 -0
- truthound/stores/observability/factory.py +208 -0
- truthound/stores/observability/metrics.py +636 -0
- truthound/stores/observability/protocols.py +410 -0
- truthound/stores/observability/store.py +570 -0
- truthound/stores/observability/tracing.py +784 -0
- truthound/stores/replication/__init__.py +76 -0
- truthound/stores/replication/base.py +260 -0
- truthound/stores/replication/monitor.py +269 -0
- truthound/stores/replication/store.py +439 -0
- truthound/stores/replication/syncer.py +391 -0
- truthound/stores/results.py +359 -0
- truthound/stores/retention/__init__.py +77 -0
- truthound/stores/retention/base.py +378 -0
- truthound/stores/retention/policies.py +621 -0
- truthound/stores/retention/scheduler.py +279 -0
- truthound/stores/retention/store.py +526 -0
- truthound/stores/streaming/__init__.py +138 -0
- truthound/stores/streaming/base.py +801 -0
- truthound/stores/streaming/database.py +984 -0
- truthound/stores/streaming/filesystem.py +719 -0
- truthound/stores/streaming/reader.py +629 -0
- truthound/stores/streaming/s3.py +843 -0
- truthound/stores/streaming/writer.py +790 -0
- truthound/stores/tiering/__init__.py +108 -0
- truthound/stores/tiering/base.py +462 -0
- truthound/stores/tiering/manager.py +249 -0
- truthound/stores/tiering/policies.py +692 -0
- truthound/stores/tiering/store.py +526 -0
- truthound/stores/versioning/__init__.py +56 -0
- truthound/stores/versioning/base.py +376 -0
- truthound/stores/versioning/store.py +660 -0
- truthound/stores/versioning/strategies.py +353 -0
- truthound/types.py +56 -0
- truthound/validators/__init__.py +774 -0
- truthound/validators/aggregate/__init__.py +27 -0
- truthound/validators/aggregate/central.py +116 -0
- truthound/validators/aggregate/extremes.py +116 -0
- truthound/validators/aggregate/spread.py +118 -0
- truthound/validators/aggregate/sum.py +64 -0
- truthound/validators/aggregate/type.py +78 -0
- truthound/validators/anomaly/__init__.py +93 -0
- truthound/validators/anomaly/base.py +431 -0
- truthound/validators/anomaly/ml_based.py +1190 -0
- truthound/validators/anomaly/multivariate.py +647 -0
- truthound/validators/anomaly/statistical.py +599 -0
- truthound/validators/base.py +1089 -0
- truthound/validators/business_rule/__init__.py +46 -0
- truthound/validators/business_rule/base.py +147 -0
- truthound/validators/business_rule/checksum.py +509 -0
- truthound/validators/business_rule/financial.py +526 -0
- truthound/validators/cache.py +733 -0
- truthound/validators/completeness/__init__.py +39 -0
- truthound/validators/completeness/conditional.py +73 -0
- truthound/validators/completeness/default.py +98 -0
- truthound/validators/completeness/empty.py +103 -0
- truthound/validators/completeness/nan.py +337 -0
- truthound/validators/completeness/null.py +152 -0
- truthound/validators/cross_table/__init__.py +17 -0
- truthound/validators/cross_table/aggregate.py +333 -0
- truthound/validators/cross_table/row_count.py +122 -0
- truthound/validators/datetime/__init__.py +29 -0
- truthound/validators/datetime/format.py +78 -0
- truthound/validators/datetime/freshness.py +269 -0
- truthound/validators/datetime/order.py +73 -0
- truthound/validators/datetime/parseable.py +185 -0
- truthound/validators/datetime/range.py +202 -0
- truthound/validators/datetime/timezone.py +69 -0
- truthound/validators/distribution/__init__.py +49 -0
- truthound/validators/distribution/distribution.py +128 -0
- truthound/validators/distribution/monotonic.py +119 -0
- truthound/validators/distribution/outlier.py +178 -0
- truthound/validators/distribution/quantile.py +80 -0
- truthound/validators/distribution/range.py +254 -0
- truthound/validators/distribution/set.py +125 -0
- truthound/validators/distribution/statistical.py +459 -0
- truthound/validators/drift/__init__.py +79 -0
- truthound/validators/drift/base.py +427 -0
- truthound/validators/drift/multi_feature.py +401 -0
- truthound/validators/drift/numeric.py +395 -0
- truthound/validators/drift/psi.py +446 -0
- truthound/validators/drift/statistical.py +510 -0
- truthound/validators/enterprise.py +1658 -0
- truthound/validators/geospatial/__init__.py +80 -0
- truthound/validators/geospatial/base.py +97 -0
- truthound/validators/geospatial/boundary.py +238 -0
- truthound/validators/geospatial/coordinate.py +351 -0
- truthound/validators/geospatial/distance.py +399 -0
- truthound/validators/geospatial/polygon.py +665 -0
- truthound/validators/i18n/__init__.py +308 -0
- truthound/validators/i18n/bidi.py +571 -0
- truthound/validators/i18n/catalogs.py +570 -0
- truthound/validators/i18n/dialects.py +763 -0
- truthound/validators/i18n/extended_catalogs.py +549 -0
- truthound/validators/i18n/formatting.py +1434 -0
- truthound/validators/i18n/loader.py +1020 -0
- truthound/validators/i18n/messages.py +521 -0
- truthound/validators/i18n/plural.py +683 -0
- truthound/validators/i18n/protocols.py +855 -0
- truthound/validators/i18n/tms.py +1162 -0
- truthound/validators/localization/__init__.py +53 -0
- truthound/validators/localization/base.py +122 -0
- truthound/validators/localization/chinese.py +362 -0
- truthound/validators/localization/japanese.py +275 -0
- truthound/validators/localization/korean.py +524 -0
- truthound/validators/memory/__init__.py +94 -0
- truthound/validators/memory/approximate_knn.py +506 -0
- truthound/validators/memory/base.py +547 -0
- truthound/validators/memory/sgd_online.py +719 -0
- truthound/validators/memory/streaming_ecdf.py +753 -0
- truthound/validators/ml_feature/__init__.py +54 -0
- truthound/validators/ml_feature/base.py +249 -0
- truthound/validators/ml_feature/correlation.py +299 -0
- truthound/validators/ml_feature/leakage.py +344 -0
- truthound/validators/ml_feature/null_impact.py +270 -0
- truthound/validators/ml_feature/scale.py +264 -0
- truthound/validators/multi_column/__init__.py +89 -0
- truthound/validators/multi_column/arithmetic.py +284 -0
- truthound/validators/multi_column/base.py +231 -0
- truthound/validators/multi_column/comparison.py +273 -0
- truthound/validators/multi_column/consistency.py +312 -0
- truthound/validators/multi_column/statistical.py +299 -0
- truthound/validators/optimization/__init__.py +164 -0
- truthound/validators/optimization/aggregation.py +563 -0
- truthound/validators/optimization/covariance.py +556 -0
- truthound/validators/optimization/geo.py +626 -0
- truthound/validators/optimization/graph.py +587 -0
- truthound/validators/optimization/orchestrator.py +970 -0
- truthound/validators/optimization/profiling.py +1312 -0
- truthound/validators/privacy/__init__.py +223 -0
- truthound/validators/privacy/base.py +635 -0
- truthound/validators/privacy/ccpa.py +670 -0
- truthound/validators/privacy/gdpr.py +728 -0
- truthound/validators/privacy/global_patterns.py +604 -0
- truthound/validators/privacy/plugins.py +867 -0
- truthound/validators/profiling/__init__.py +52 -0
- truthound/validators/profiling/base.py +175 -0
- truthound/validators/profiling/cardinality.py +312 -0
- truthound/validators/profiling/entropy.py +391 -0
- truthound/validators/profiling/frequency.py +455 -0
- truthound/validators/pushdown_support.py +660 -0
- truthound/validators/query/__init__.py +91 -0
- truthound/validators/query/aggregate.py +346 -0
- truthound/validators/query/base.py +246 -0
- truthound/validators/query/column.py +249 -0
- truthound/validators/query/expression.py +274 -0
- truthound/validators/query/result.py +323 -0
- truthound/validators/query/row_count.py +264 -0
- truthound/validators/referential/__init__.py +80 -0
- truthound/validators/referential/base.py +395 -0
- truthound/validators/referential/cascade.py +391 -0
- truthound/validators/referential/circular.py +563 -0
- truthound/validators/referential/foreign_key.py +624 -0
- truthound/validators/referential/orphan.py +485 -0
- truthound/validators/registry.py +112 -0
- truthound/validators/schema/__init__.py +41 -0
- truthound/validators/schema/column_count.py +142 -0
- truthound/validators/schema/column_exists.py +80 -0
- truthound/validators/schema/column_order.py +82 -0
- truthound/validators/schema/column_pair.py +85 -0
- truthound/validators/schema/column_pair_set.py +195 -0
- truthound/validators/schema/column_type.py +94 -0
- truthound/validators/schema/multi_column.py +53 -0
- truthound/validators/schema/multi_column_aggregate.py +175 -0
- truthound/validators/schema/referential.py +274 -0
- truthound/validators/schema/table_schema.py +91 -0
- truthound/validators/schema_validator.py +219 -0
- truthound/validators/sdk/__init__.py +250 -0
- truthound/validators/sdk/builder.py +680 -0
- truthound/validators/sdk/decorators.py +474 -0
- truthound/validators/sdk/enterprise/__init__.py +211 -0
- truthound/validators/sdk/enterprise/docs.py +725 -0
- truthound/validators/sdk/enterprise/fuzzing.py +659 -0
- truthound/validators/sdk/enterprise/licensing.py +709 -0
- truthound/validators/sdk/enterprise/manager.py +543 -0
- truthound/validators/sdk/enterprise/resources.py +628 -0
- truthound/validators/sdk/enterprise/sandbox.py +766 -0
- truthound/validators/sdk/enterprise/signing.py +603 -0
- truthound/validators/sdk/enterprise/templates.py +865 -0
- truthound/validators/sdk/enterprise/versioning.py +659 -0
- truthound/validators/sdk/templates.py +757 -0
- truthound/validators/sdk/testing.py +807 -0
- truthound/validators/security/__init__.py +181 -0
- truthound/validators/security/redos/__init__.py +182 -0
- truthound/validators/security/redos/core.py +861 -0
- truthound/validators/security/redos/cpu_monitor.py +593 -0
- truthound/validators/security/redos/cve_database.py +791 -0
- truthound/validators/security/redos/ml/__init__.py +155 -0
- truthound/validators/security/redos/ml/base.py +785 -0
- truthound/validators/security/redos/ml/datasets.py +618 -0
- truthound/validators/security/redos/ml/features.py +359 -0
- truthound/validators/security/redos/ml/models.py +1000 -0
- truthound/validators/security/redos/ml/predictor.py +507 -0
- truthound/validators/security/redos/ml/storage.py +632 -0
- truthound/validators/security/redos/ml/training.py +571 -0
- truthound/validators/security/redos/ml_analyzer.py +937 -0
- truthound/validators/security/redos/optimizer.py +674 -0
- truthound/validators/security/redos/profiler.py +682 -0
- truthound/validators/security/redos/re2_engine.py +709 -0
- truthound/validators/security/redos.py +886 -0
- truthound/validators/security/sql_security.py +1247 -0
- truthound/validators/streaming/__init__.py +126 -0
- truthound/validators/streaming/base.py +292 -0
- truthound/validators/streaming/completeness.py +210 -0
- truthound/validators/streaming/mixin.py +575 -0
- truthound/validators/streaming/range.py +308 -0
- truthound/validators/streaming/sources.py +846 -0
- truthound/validators/string/__init__.py +57 -0
- truthound/validators/string/casing.py +158 -0
- truthound/validators/string/charset.py +96 -0
- truthound/validators/string/format.py +501 -0
- truthound/validators/string/json.py +77 -0
- truthound/validators/string/json_schema.py +184 -0
- truthound/validators/string/length.py +104 -0
- truthound/validators/string/like_pattern.py +237 -0
- truthound/validators/string/regex.py +202 -0
- truthound/validators/string/regex_extended.py +435 -0
- truthound/validators/table/__init__.py +88 -0
- truthound/validators/table/base.py +78 -0
- truthound/validators/table/column_count.py +198 -0
- truthound/validators/table/freshness.py +362 -0
- truthound/validators/table/row_count.py +251 -0
- truthound/validators/table/schema.py +333 -0
- truthound/validators/table/size.py +285 -0
- truthound/validators/timeout/__init__.py +102 -0
- truthound/validators/timeout/advanced/__init__.py +247 -0
- truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
- truthound/validators/timeout/advanced/prediction.py +773 -0
- truthound/validators/timeout/advanced/priority.py +618 -0
- truthound/validators/timeout/advanced/redis_backend.py +770 -0
- truthound/validators/timeout/advanced/retry.py +721 -0
- truthound/validators/timeout/advanced/sampling.py +788 -0
- truthound/validators/timeout/advanced/sla.py +661 -0
- truthound/validators/timeout/advanced/telemetry.py +804 -0
- truthound/validators/timeout/cascade.py +477 -0
- truthound/validators/timeout/deadline.py +657 -0
- truthound/validators/timeout/degradation.py +525 -0
- truthound/validators/timeout/distributed.py +597 -0
- truthound/validators/timeseries/__init__.py +89 -0
- truthound/validators/timeseries/base.py +326 -0
- truthound/validators/timeseries/completeness.py +617 -0
- truthound/validators/timeseries/gap.py +485 -0
- truthound/validators/timeseries/monotonic.py +310 -0
- truthound/validators/timeseries/seasonality.py +422 -0
- truthound/validators/timeseries/trend.py +510 -0
- truthound/validators/uniqueness/__init__.py +59 -0
- truthound/validators/uniqueness/approximate.py +475 -0
- truthound/validators/uniqueness/distinct_values.py +253 -0
- truthound/validators/uniqueness/duplicate.py +118 -0
- truthound/validators/uniqueness/primary_key.py +140 -0
- truthound/validators/uniqueness/unique.py +191 -0
- truthound/validators/uniqueness/within_record.py +599 -0
- truthound/validators/utils.py +756 -0
- truthound-1.0.8.dist-info/METADATA +474 -0
- truthound-1.0.8.dist-info/RECORD +877 -0
- truthound-1.0.8.dist-info/WHEEL +4 -0
- truthound-1.0.8.dist-info/entry_points.txt +2 -0
- truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,1550 @@
|
|
|
1
|
+
"""Fluent Query Builder for SQL Pushdown.
|
|
2
|
+
|
|
3
|
+
This module provides a fluent API for building SQL queries using
|
|
4
|
+
the AST nodes. It offers a more intuitive interface for constructing
|
|
5
|
+
complex queries.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from truthound.execution.pushdown import (
|
|
9
|
+
... QueryBuilder,
|
|
10
|
+
... col,
|
|
11
|
+
... func,
|
|
12
|
+
... and_,
|
|
13
|
+
... or_,
|
|
14
|
+
... )
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Simple query
|
|
17
|
+
>>> query = (
|
|
18
|
+
... QueryBuilder("users")
|
|
19
|
+
... .select("name", "email", col("age"))
|
|
20
|
+
... .where(col("age") > 18)
|
|
21
|
+
... .order_by("name")
|
|
22
|
+
... .limit(100)
|
|
23
|
+
... )
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Complex query with aggregation
|
|
26
|
+
>>> query = (
|
|
27
|
+
... QueryBuilder("orders")
|
|
28
|
+
... .select(
|
|
29
|
+
... col("customer_id"),
|
|
30
|
+
... func.sum("amount").alias("total"),
|
|
31
|
+
... func.count("*").alias("order_count"),
|
|
32
|
+
... )
|
|
33
|
+
... .where(col("status") == "completed")
|
|
34
|
+
... .group_by("customer_id")
|
|
35
|
+
... .having(func.sum("amount") > 1000)
|
|
36
|
+
... .order_by(col("total").desc())
|
|
37
|
+
... )
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from typing import Any, Callable, Sequence, Union
|
|
44
|
+
|
|
45
|
+
from truthound.execution.pushdown.ast import (
|
|
46
|
+
# Base
|
|
47
|
+
Expression,
|
|
48
|
+
SQLNode,
|
|
49
|
+
# Literals
|
|
50
|
+
Literal,
|
|
51
|
+
NullLiteral,
|
|
52
|
+
BooleanLiteral,
|
|
53
|
+
# Identifiers
|
|
54
|
+
Column,
|
|
55
|
+
Table,
|
|
56
|
+
Alias,
|
|
57
|
+
Star,
|
|
58
|
+
# Operators
|
|
59
|
+
ComparisonOp,
|
|
60
|
+
LogicalOp,
|
|
61
|
+
ArithmeticOp,
|
|
62
|
+
UnaryOp,
|
|
63
|
+
JoinType,
|
|
64
|
+
SortOrder,
|
|
65
|
+
NullsPosition,
|
|
66
|
+
FrameType,
|
|
67
|
+
FrameBoundType,
|
|
68
|
+
SetOperation,
|
|
69
|
+
# Expressions
|
|
70
|
+
BinaryExpression,
|
|
71
|
+
UnaryExpression,
|
|
72
|
+
InExpression,
|
|
73
|
+
BetweenExpression,
|
|
74
|
+
ExistsExpression,
|
|
75
|
+
SubqueryExpression,
|
|
76
|
+
CastExpression,
|
|
77
|
+
WhenClause,
|
|
78
|
+
CaseExpression,
|
|
79
|
+
FunctionCall,
|
|
80
|
+
AggregateFunction,
|
|
81
|
+
FrameBound,
|
|
82
|
+
WindowSpec,
|
|
83
|
+
WindowFunction,
|
|
84
|
+
# Clauses
|
|
85
|
+
SelectItem,
|
|
86
|
+
FromClause,
|
|
87
|
+
JoinClause,
|
|
88
|
+
WhereClause,
|
|
89
|
+
GroupByClause,
|
|
90
|
+
HavingClause,
|
|
91
|
+
OrderByItem,
|
|
92
|
+
OrderByClause,
|
|
93
|
+
LimitClause,
|
|
94
|
+
OffsetClause,
|
|
95
|
+
CTEClause,
|
|
96
|
+
# Statements
|
|
97
|
+
SelectStatement,
|
|
98
|
+
SetOperationStatement,
|
|
99
|
+
)
|
|
100
|
+
from truthound.execution.pushdown.dialects import (
|
|
101
|
+
SQLDialect,
|
|
102
|
+
get_dialect_generator,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Type alias for expression-like values
|
|
107
|
+
ExprLike = Union[Expression, str, int, float, bool, None]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# Expression Builders
|
|
112
|
+
# =============================================================================
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _to_expr(value: ExprLike) -> Expression:
|
|
116
|
+
"""Convert a value to an Expression.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
value: Value to convert.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Expression representing the value.
|
|
123
|
+
"""
|
|
124
|
+
if isinstance(value, Expression):
|
|
125
|
+
return value
|
|
126
|
+
if isinstance(value, str):
|
|
127
|
+
# Check if it's a column reference (simple heuristic)
|
|
128
|
+
if "." in value or value.isidentifier():
|
|
129
|
+
return col(value)
|
|
130
|
+
return Literal(value)
|
|
131
|
+
if value is None:
|
|
132
|
+
return NullLiteral()
|
|
133
|
+
if isinstance(value, bool):
|
|
134
|
+
return BooleanLiteral(value)
|
|
135
|
+
return Literal(value)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ExpressionBuilder(Expression):
|
|
139
|
+
"""Builder for creating SQL expressions with a fluent API.
|
|
140
|
+
|
|
141
|
+
This class wraps an Expression and provides additional methods
|
|
142
|
+
for building complex expressions.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, expr: Expression) -> None:
|
|
146
|
+
"""Initialize expression builder.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
expr: The underlying expression.
|
|
150
|
+
"""
|
|
151
|
+
self._expr = expr
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def expression(self) -> Expression:
|
|
155
|
+
"""Get the underlying expression."""
|
|
156
|
+
return self._expr
|
|
157
|
+
|
|
158
|
+
def accept(self, visitor: Any) -> Any:
|
|
159
|
+
"""Accept a visitor."""
|
|
160
|
+
return self._expr.accept(visitor)
|
|
161
|
+
|
|
162
|
+
def children(self) -> Sequence[SQLNode]:
|
|
163
|
+
"""Get child nodes."""
|
|
164
|
+
return self._expr.children()
|
|
165
|
+
|
|
166
|
+
# Delegate comparison operators to underlying expression
|
|
167
|
+
def __eq__(self, other: Any) -> BinaryExpression: # type: ignore[override]
|
|
168
|
+
return self._expr.__eq__(other)
|
|
169
|
+
|
|
170
|
+
def __ne__(self, other: Any) -> BinaryExpression: # type: ignore[override]
|
|
171
|
+
return self._expr.__ne__(other)
|
|
172
|
+
|
|
173
|
+
def __lt__(self, other: Any) -> BinaryExpression:
|
|
174
|
+
return self._expr.__lt__(other)
|
|
175
|
+
|
|
176
|
+
def __le__(self, other: Any) -> BinaryExpression:
|
|
177
|
+
return self._expr.__le__(other)
|
|
178
|
+
|
|
179
|
+
def __gt__(self, other: Any) -> BinaryExpression:
|
|
180
|
+
return self._expr.__gt__(other)
|
|
181
|
+
|
|
182
|
+
def __ge__(self, other: Any) -> BinaryExpression:
|
|
183
|
+
return self._expr.__ge__(other)
|
|
184
|
+
|
|
185
|
+
def __add__(self, other: Any) -> BinaryExpression:
|
|
186
|
+
return self._expr.__add__(other)
|
|
187
|
+
|
|
188
|
+
def __sub__(self, other: Any) -> BinaryExpression:
|
|
189
|
+
return self._expr.__sub__(other)
|
|
190
|
+
|
|
191
|
+
def __mul__(self, other: Any) -> BinaryExpression:
|
|
192
|
+
return self._expr.__mul__(other)
|
|
193
|
+
|
|
194
|
+
def __truediv__(self, other: Any) -> BinaryExpression:
|
|
195
|
+
return self._expr.__truediv__(other)
|
|
196
|
+
|
|
197
|
+
def __mod__(self, other: Any) -> BinaryExpression:
|
|
198
|
+
return self._expr.__mod__(other)
|
|
199
|
+
|
|
200
|
+
def __and__(self, other: Expression) -> BinaryExpression:
|
|
201
|
+
return self._expr.__and__(other)
|
|
202
|
+
|
|
203
|
+
def __or__(self, other: Expression) -> BinaryExpression:
|
|
204
|
+
return self._expr.__or__(other)
|
|
205
|
+
|
|
206
|
+
def __invert__(self) -> UnaryExpression:
|
|
207
|
+
return self._expr.__invert__()
|
|
208
|
+
|
|
209
|
+
def __neg__(self) -> UnaryExpression:
|
|
210
|
+
return self._expr.__neg__()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# =============================================================================
|
|
214
|
+
# Column Builder
|
|
215
|
+
# =============================================================================
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def col(name: str, table: str | None = None) -> Column:
|
|
219
|
+
"""Create a column reference.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
name: Column name. Can include table prefix (e.g., "users.id").
|
|
223
|
+
table: Optional table name.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Column expression.
|
|
227
|
+
|
|
228
|
+
Examples:
|
|
229
|
+
>>> col("name")
|
|
230
|
+
Column(name)
|
|
231
|
+
>>> col("users.name")
|
|
232
|
+
Column(users.name)
|
|
233
|
+
>>> col("name", "users")
|
|
234
|
+
Column(users.name)
|
|
235
|
+
"""
|
|
236
|
+
if "." in name and table is None:
|
|
237
|
+
parts = name.split(".")
|
|
238
|
+
if len(parts) == 2:
|
|
239
|
+
return Column(parts[1], parts[0])
|
|
240
|
+
elif len(parts) == 3:
|
|
241
|
+
return Column(parts[2], parts[1], parts[0])
|
|
242
|
+
return Column(name, table)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def literal(value: Any) -> Literal:
|
|
246
|
+
"""Create a literal value.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
value: The literal value.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Literal expression.
|
|
253
|
+
"""
|
|
254
|
+
if value is None:
|
|
255
|
+
return NullLiteral() # type: ignore
|
|
256
|
+
return Literal(value)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def star(table: str | None = None) -> Star:
|
|
260
|
+
"""Create a * expression.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
table: Optional table name for table.* syntax.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Star expression.
|
|
267
|
+
"""
|
|
268
|
+
return Star(table)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# =============================================================================
|
|
272
|
+
# Function Builder
|
|
273
|
+
# =============================================================================
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class FunctionBuilder:
|
|
277
|
+
"""Builder for SQL function calls.
|
|
278
|
+
|
|
279
|
+
Provides shortcuts for common SQL functions.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
>>> func.count("*")
|
|
283
|
+
COUNT(*)
|
|
284
|
+
>>> func.sum("amount")
|
|
285
|
+
SUM(amount)
|
|
286
|
+
>>> func.avg("price").over(partition_by="category")
|
|
287
|
+
AVG(price) OVER (PARTITION BY category)
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __call__(self, name: str, *args: ExprLike) -> FunctionCall:
|
|
291
|
+
"""Create a generic function call.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
name: Function name.
|
|
295
|
+
args: Function arguments.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
FunctionCall expression.
|
|
299
|
+
"""
|
|
300
|
+
exprs = [_to_expr(a) for a in args]
|
|
301
|
+
return FunctionCall(name, exprs)
|
|
302
|
+
|
|
303
|
+
# -------------------------------------------------------------------------
|
|
304
|
+
# Aggregate Functions
|
|
305
|
+
# -------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
def count(
|
|
308
|
+
self,
|
|
309
|
+
column: ExprLike = "*",
|
|
310
|
+
distinct: bool = False,
|
|
311
|
+
) -> AggregateFunction:
|
|
312
|
+
"""COUNT aggregate function.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
column: Column to count. Use "*" for COUNT(*).
|
|
316
|
+
distinct: Whether to count distinct values.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
AggregateFunction.
|
|
320
|
+
"""
|
|
321
|
+
if column == "*":
|
|
322
|
+
return AggregateFunction("COUNT", None, distinct=distinct)
|
|
323
|
+
return AggregateFunction("COUNT", _to_expr(column), distinct=distinct)
|
|
324
|
+
|
|
325
|
+
def count_distinct(self, column: ExprLike) -> AggregateFunction:
|
|
326
|
+
"""COUNT(DISTINCT column) aggregate function."""
|
|
327
|
+
return self.count(column, distinct=True)
|
|
328
|
+
|
|
329
|
+
def sum(self, column: ExprLike, distinct: bool = False) -> AggregateFunction:
|
|
330
|
+
"""SUM aggregate function."""
|
|
331
|
+
return AggregateFunction("SUM", _to_expr(column), distinct=distinct)
|
|
332
|
+
|
|
333
|
+
def avg(self, column: ExprLike, distinct: bool = False) -> AggregateFunction:
|
|
334
|
+
"""AVG aggregate function."""
|
|
335
|
+
return AggregateFunction("AVG", _to_expr(column), distinct=distinct)
|
|
336
|
+
|
|
337
|
+
def min(self, column: ExprLike) -> AggregateFunction:
|
|
338
|
+
"""MIN aggregate function."""
|
|
339
|
+
return AggregateFunction("MIN", _to_expr(column))
|
|
340
|
+
|
|
341
|
+
def max(self, column: ExprLike) -> AggregateFunction:
|
|
342
|
+
"""MAX aggregate function."""
|
|
343
|
+
return AggregateFunction("MAX", _to_expr(column))
|
|
344
|
+
|
|
345
|
+
def stddev(self, column: ExprLike) -> AggregateFunction:
|
|
346
|
+
"""STDDEV aggregate function."""
|
|
347
|
+
return AggregateFunction("STDDEV", _to_expr(column))
|
|
348
|
+
|
|
349
|
+
def stddev_pop(self, column: ExprLike) -> AggregateFunction:
|
|
350
|
+
"""STDDEV_POP aggregate function."""
|
|
351
|
+
return AggregateFunction("STDDEV_POP", _to_expr(column))
|
|
352
|
+
|
|
353
|
+
def stddev_samp(self, column: ExprLike) -> AggregateFunction:
|
|
354
|
+
"""STDDEV_SAMP aggregate function."""
|
|
355
|
+
return AggregateFunction("STDDEV_SAMP", _to_expr(column))
|
|
356
|
+
|
|
357
|
+
def variance(self, column: ExprLike) -> AggregateFunction:
|
|
358
|
+
"""VARIANCE aggregate function."""
|
|
359
|
+
return AggregateFunction("VARIANCE", _to_expr(column))
|
|
360
|
+
|
|
361
|
+
def var_pop(self, column: ExprLike) -> AggregateFunction:
|
|
362
|
+
"""VAR_POP aggregate function."""
|
|
363
|
+
return AggregateFunction("VAR_POP", _to_expr(column))
|
|
364
|
+
|
|
365
|
+
def var_samp(self, column: ExprLike) -> AggregateFunction:
|
|
366
|
+
"""VAR_SAMP aggregate function."""
|
|
367
|
+
return AggregateFunction("VAR_SAMP", _to_expr(column))
|
|
368
|
+
|
|
369
|
+
def array_agg(
|
|
370
|
+
self,
|
|
371
|
+
column: ExprLike,
|
|
372
|
+
distinct: bool = False,
|
|
373
|
+
order_by: Sequence[OrderByItem] | None = None,
|
|
374
|
+
) -> AggregateFunction:
|
|
375
|
+
"""ARRAY_AGG aggregate function."""
|
|
376
|
+
return AggregateFunction(
|
|
377
|
+
"ARRAY_AGG", _to_expr(column), distinct=distinct, order_by=order_by
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def string_agg(
|
|
381
|
+
self,
|
|
382
|
+
column: ExprLike,
|
|
383
|
+
separator: str = ",",
|
|
384
|
+
order_by: Sequence[OrderByItem] | None = None,
|
|
385
|
+
) -> FunctionCall:
|
|
386
|
+
"""STRING_AGG (or GROUP_CONCAT) function."""
|
|
387
|
+
return FunctionCall("STRING_AGG", [_to_expr(column), Literal(separator)])
|
|
388
|
+
|
|
389
|
+
def group_concat(
|
|
390
|
+
self,
|
|
391
|
+
column: ExprLike,
|
|
392
|
+
separator: str = ",",
|
|
393
|
+
) -> FunctionCall:
|
|
394
|
+
"""GROUP_CONCAT function (MySQL style)."""
|
|
395
|
+
return FunctionCall("GROUP_CONCAT", [_to_expr(column)])
|
|
396
|
+
|
|
397
|
+
def listagg(
|
|
398
|
+
self,
|
|
399
|
+
column: ExprLike,
|
|
400
|
+
separator: str = ",",
|
|
401
|
+
) -> FunctionCall:
|
|
402
|
+
"""LISTAGG function (Oracle style)."""
|
|
403
|
+
return FunctionCall("LISTAGG", [_to_expr(column), Literal(separator)])
|
|
404
|
+
|
|
405
|
+
# -------------------------------------------------------------------------
|
|
406
|
+
# Window Functions
|
|
407
|
+
# -------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
def row_number(self) -> AggregateFunction:
|
|
410
|
+
"""ROW_NUMBER window function."""
|
|
411
|
+
return AggregateFunction("ROW_NUMBER", None)
|
|
412
|
+
|
|
413
|
+
def rank(self) -> AggregateFunction:
|
|
414
|
+
"""RANK window function."""
|
|
415
|
+
return AggregateFunction("RANK", None)
|
|
416
|
+
|
|
417
|
+
def dense_rank(self) -> AggregateFunction:
|
|
418
|
+
"""DENSE_RANK window function."""
|
|
419
|
+
return AggregateFunction("DENSE_RANK", None)
|
|
420
|
+
|
|
421
|
+
def ntile(self, n: int) -> FunctionCall:
|
|
422
|
+
"""NTILE window function."""
|
|
423
|
+
return FunctionCall("NTILE", [Literal(n)])
|
|
424
|
+
|
|
425
|
+
def percent_rank(self) -> AggregateFunction:
|
|
426
|
+
"""PERCENT_RANK window function."""
|
|
427
|
+
return AggregateFunction("PERCENT_RANK", None)
|
|
428
|
+
|
|
429
|
+
def cume_dist(self) -> AggregateFunction:
|
|
430
|
+
"""CUME_DIST window function."""
|
|
431
|
+
return AggregateFunction("CUME_DIST", None)
|
|
432
|
+
|
|
433
|
+
def lead(
|
|
434
|
+
self,
|
|
435
|
+
column: ExprLike,
|
|
436
|
+
offset: int = 1,
|
|
437
|
+
default: ExprLike | None = None,
|
|
438
|
+
) -> FunctionCall:
|
|
439
|
+
"""LEAD window function."""
|
|
440
|
+
args = [_to_expr(column), Literal(offset)]
|
|
441
|
+
if default is not None:
|
|
442
|
+
args.append(_to_expr(default))
|
|
443
|
+
return FunctionCall("LEAD", args)
|
|
444
|
+
|
|
445
|
+
def lag(
|
|
446
|
+
self,
|
|
447
|
+
column: ExprLike,
|
|
448
|
+
offset: int = 1,
|
|
449
|
+
default: ExprLike | None = None,
|
|
450
|
+
) -> FunctionCall:
|
|
451
|
+
"""LAG window function."""
|
|
452
|
+
args = [_to_expr(column), Literal(offset)]
|
|
453
|
+
if default is not None:
|
|
454
|
+
args.append(_to_expr(default))
|
|
455
|
+
return FunctionCall("LAG", args)
|
|
456
|
+
|
|
457
|
+
def first_value(self, column: ExprLike) -> FunctionCall:
|
|
458
|
+
"""FIRST_VALUE window function."""
|
|
459
|
+
return FunctionCall("FIRST_VALUE", [_to_expr(column)])
|
|
460
|
+
|
|
461
|
+
def last_value(self, column: ExprLike) -> FunctionCall:
|
|
462
|
+
"""LAST_VALUE window function."""
|
|
463
|
+
return FunctionCall("LAST_VALUE", [_to_expr(column)])
|
|
464
|
+
|
|
465
|
+
def nth_value(self, column: ExprLike, n: int) -> FunctionCall:
|
|
466
|
+
"""NTH_VALUE window function."""
|
|
467
|
+
return FunctionCall("NTH_VALUE", [_to_expr(column), Literal(n)])
|
|
468
|
+
|
|
469
|
+
# -------------------------------------------------------------------------
|
|
470
|
+
# String Functions
|
|
471
|
+
# -------------------------------------------------------------------------
|
|
472
|
+
|
|
473
|
+
def length(self, column: ExprLike) -> FunctionCall:
|
|
474
|
+
"""LENGTH function."""
|
|
475
|
+
return FunctionCall("LENGTH", [_to_expr(column)])
|
|
476
|
+
|
|
477
|
+
def upper(self, column: ExprLike) -> FunctionCall:
|
|
478
|
+
"""UPPER function."""
|
|
479
|
+
return FunctionCall("UPPER", [_to_expr(column)])
|
|
480
|
+
|
|
481
|
+
def lower(self, column: ExprLike) -> FunctionCall:
|
|
482
|
+
"""LOWER function."""
|
|
483
|
+
return FunctionCall("LOWER", [_to_expr(column)])
|
|
484
|
+
|
|
485
|
+
def trim(self, column: ExprLike) -> FunctionCall:
|
|
486
|
+
"""TRIM function."""
|
|
487
|
+
return FunctionCall("TRIM", [_to_expr(column)])
|
|
488
|
+
|
|
489
|
+
def ltrim(self, column: ExprLike) -> FunctionCall:
|
|
490
|
+
"""LTRIM function."""
|
|
491
|
+
return FunctionCall("LTRIM", [_to_expr(column)])
|
|
492
|
+
|
|
493
|
+
def rtrim(self, column: ExprLike) -> FunctionCall:
|
|
494
|
+
"""RTRIM function."""
|
|
495
|
+
return FunctionCall("RTRIM", [_to_expr(column)])
|
|
496
|
+
|
|
497
|
+
def substr(
|
|
498
|
+
self,
|
|
499
|
+
column: ExprLike,
|
|
500
|
+
start: int,
|
|
501
|
+
length: int | None = None,
|
|
502
|
+
) -> FunctionCall:
|
|
503
|
+
"""SUBSTR/SUBSTRING function."""
|
|
504
|
+
args = [_to_expr(column), Literal(start)]
|
|
505
|
+
if length is not None:
|
|
506
|
+
args.append(Literal(length))
|
|
507
|
+
return FunctionCall("SUBSTRING", args)
|
|
508
|
+
|
|
509
|
+
def concat(self, *args: ExprLike) -> FunctionCall:
|
|
510
|
+
"""CONCAT function."""
|
|
511
|
+
exprs = [_to_expr(a) for a in args]
|
|
512
|
+
return FunctionCall("CONCAT", exprs)
|
|
513
|
+
|
|
514
|
+
def replace(
|
|
515
|
+
self,
|
|
516
|
+
column: ExprLike,
|
|
517
|
+
old: str,
|
|
518
|
+
new: str,
|
|
519
|
+
) -> FunctionCall:
|
|
520
|
+
"""REPLACE function."""
|
|
521
|
+
return FunctionCall(
|
|
522
|
+
"REPLACE", [_to_expr(column), Literal(old), Literal(new)]
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
def regexp_replace(
|
|
526
|
+
self,
|
|
527
|
+
column: ExprLike,
|
|
528
|
+
pattern: str,
|
|
529
|
+
replacement: str,
|
|
530
|
+
) -> FunctionCall:
|
|
531
|
+
"""REGEXP_REPLACE function."""
|
|
532
|
+
return FunctionCall(
|
|
533
|
+
"REGEXP_REPLACE", [_to_expr(column), Literal(pattern), Literal(replacement)]
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
def split(self, column: ExprLike, delimiter: str) -> FunctionCall:
|
|
537
|
+
"""SPLIT function."""
|
|
538
|
+
return FunctionCall("SPLIT", [_to_expr(column), Literal(delimiter)])
|
|
539
|
+
|
|
540
|
+
# -------------------------------------------------------------------------
|
|
541
|
+
# Date/Time Functions
|
|
542
|
+
# -------------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
def now(self) -> FunctionCall:
|
|
545
|
+
"""NOW/CURRENT_TIMESTAMP function."""
|
|
546
|
+
return FunctionCall("NOW", [])
|
|
547
|
+
|
|
548
|
+
def current_date(self) -> FunctionCall:
|
|
549
|
+
"""CURRENT_DATE function."""
|
|
550
|
+
return FunctionCall("CURRENT_DATE", [])
|
|
551
|
+
|
|
552
|
+
def current_timestamp(self) -> FunctionCall:
|
|
553
|
+
"""CURRENT_TIMESTAMP function."""
|
|
554
|
+
return FunctionCall("CURRENT_TIMESTAMP", [])
|
|
555
|
+
|
|
556
|
+
def date_trunc(self, part: str, column: ExprLike) -> FunctionCall:
|
|
557
|
+
"""DATE_TRUNC function."""
|
|
558
|
+
return FunctionCall("DATE_TRUNC", [Literal(part), _to_expr(column)])
|
|
559
|
+
|
|
560
|
+
def date_part(self, part: str, column: ExprLike) -> FunctionCall:
|
|
561
|
+
"""DATE_PART/EXTRACT function."""
|
|
562
|
+
return FunctionCall("DATE_PART", [Literal(part), _to_expr(column)])
|
|
563
|
+
|
|
564
|
+
def extract(self, part: str, column: ExprLike) -> FunctionCall:
|
|
565
|
+
"""EXTRACT function."""
|
|
566
|
+
return FunctionCall("EXTRACT", [Literal(part), _to_expr(column)])
|
|
567
|
+
|
|
568
|
+
def date_add(
|
|
569
|
+
self,
|
|
570
|
+
column: ExprLike,
|
|
571
|
+
interval: int,
|
|
572
|
+
unit: str = "DAY",
|
|
573
|
+
) -> FunctionCall:
|
|
574
|
+
"""DATE_ADD function."""
|
|
575
|
+
return FunctionCall("DATE_ADD", [_to_expr(column), Literal(interval)])
|
|
576
|
+
|
|
577
|
+
def date_diff(
|
|
578
|
+
self,
|
|
579
|
+
end: ExprLike,
|
|
580
|
+
start: ExprLike,
|
|
581
|
+
unit: str = "DAY",
|
|
582
|
+
) -> FunctionCall:
|
|
583
|
+
"""DATEDIFF function."""
|
|
584
|
+
return FunctionCall("DATEDIFF", [_to_expr(end), _to_expr(start)])
|
|
585
|
+
|
|
586
|
+
# -------------------------------------------------------------------------
|
|
587
|
+
# Null Handling Functions
|
|
588
|
+
# -------------------------------------------------------------------------
|
|
589
|
+
|
|
590
|
+
def coalesce(self, *args: ExprLike) -> FunctionCall:
|
|
591
|
+
"""COALESCE function."""
|
|
592
|
+
exprs = [_to_expr(a) for a in args]
|
|
593
|
+
return FunctionCall("COALESCE", exprs)
|
|
594
|
+
|
|
595
|
+
def nullif(self, expr1: ExprLike, expr2: ExprLike) -> FunctionCall:
|
|
596
|
+
"""NULLIF function."""
|
|
597
|
+
return FunctionCall("NULLIF", [_to_expr(expr1), _to_expr(expr2)])
|
|
598
|
+
|
|
599
|
+
def ifnull(self, column: ExprLike, default: ExprLike) -> FunctionCall:
|
|
600
|
+
"""IFNULL/NVL function."""
|
|
601
|
+
return FunctionCall("IFNULL", [_to_expr(column), _to_expr(default)])
|
|
602
|
+
|
|
603
|
+
# -------------------------------------------------------------------------
|
|
604
|
+
# Math Functions
|
|
605
|
+
# -------------------------------------------------------------------------
|
|
606
|
+
|
|
607
|
+
def abs(self, column: ExprLike) -> FunctionCall:
|
|
608
|
+
"""ABS function."""
|
|
609
|
+
return FunctionCall("ABS", [_to_expr(column)])
|
|
610
|
+
|
|
611
|
+
def round(self, column: ExprLike, decimals: int = 0) -> FunctionCall:
|
|
612
|
+
"""ROUND function."""
|
|
613
|
+
return FunctionCall("ROUND", [_to_expr(column), Literal(decimals)])
|
|
614
|
+
|
|
615
|
+
def floor(self, column: ExprLike) -> FunctionCall:
|
|
616
|
+
"""FLOOR function."""
|
|
617
|
+
return FunctionCall("FLOOR", [_to_expr(column)])
|
|
618
|
+
|
|
619
|
+
def ceil(self, column: ExprLike) -> FunctionCall:
|
|
620
|
+
"""CEIL function."""
|
|
621
|
+
return FunctionCall("CEIL", [_to_expr(column)])
|
|
622
|
+
|
|
623
|
+
def power(self, column: ExprLike, exponent: ExprLike) -> FunctionCall:
|
|
624
|
+
"""POWER function."""
|
|
625
|
+
return FunctionCall("POWER", [_to_expr(column), _to_expr(exponent)])
|
|
626
|
+
|
|
627
|
+
def sqrt(self, column: ExprLike) -> FunctionCall:
|
|
628
|
+
"""SQRT function."""
|
|
629
|
+
return FunctionCall("SQRT", [_to_expr(column)])
|
|
630
|
+
|
|
631
|
+
def log(self, column: ExprLike) -> FunctionCall:
|
|
632
|
+
"""LOG function (natural logarithm)."""
|
|
633
|
+
return FunctionCall("LN", [_to_expr(column)])
|
|
634
|
+
|
|
635
|
+
def log10(self, column: ExprLike) -> FunctionCall:
|
|
636
|
+
"""LOG10 function."""
|
|
637
|
+
return FunctionCall("LOG10", [_to_expr(column)])
|
|
638
|
+
|
|
639
|
+
def exp(self, column: ExprLike) -> FunctionCall:
|
|
640
|
+
"""EXP function."""
|
|
641
|
+
return FunctionCall("EXP", [_to_expr(column)])
|
|
642
|
+
|
|
643
|
+
# -------------------------------------------------------------------------
|
|
644
|
+
# Conditional Functions
|
|
645
|
+
# -------------------------------------------------------------------------
|
|
646
|
+
|
|
647
|
+
def if_(
|
|
648
|
+
self,
|
|
649
|
+
condition: Expression,
|
|
650
|
+
true_value: ExprLike,
|
|
651
|
+
false_value: ExprLike,
|
|
652
|
+
) -> FunctionCall:
|
|
653
|
+
"""IF function (or CASE-based equivalent)."""
|
|
654
|
+
return FunctionCall(
|
|
655
|
+
"IF", [condition, _to_expr(true_value), _to_expr(false_value)]
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
# Singleton function builder instance
|
|
660
|
+
func = FunctionBuilder()
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
# =============================================================================
|
|
664
|
+
# Logical Operators
|
|
665
|
+
# =============================================================================
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def and_(*conditions: Expression) -> Expression:
|
|
669
|
+
"""Create an AND expression from multiple conditions.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
conditions: Conditions to AND together.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
Combined AND expression.
|
|
676
|
+
|
|
677
|
+
Example:
|
|
678
|
+
>>> and_(col("age") > 18, col("status") == "active")
|
|
679
|
+
(age > 18 AND status = 'active')
|
|
680
|
+
"""
|
|
681
|
+
if not conditions:
|
|
682
|
+
raise ValueError("and_() requires at least one condition")
|
|
683
|
+
if len(conditions) == 1:
|
|
684
|
+
return conditions[0]
|
|
685
|
+
|
|
686
|
+
result = conditions[0]
|
|
687
|
+
for cond in conditions[1:]:
|
|
688
|
+
result = BinaryExpression(result, LogicalOp.AND, cond)
|
|
689
|
+
return result
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def or_(*conditions: Expression) -> Expression:
|
|
693
|
+
"""Create an OR expression from multiple conditions.
|
|
694
|
+
|
|
695
|
+
Args:
|
|
696
|
+
conditions: Conditions to OR together.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Combined OR expression.
|
|
700
|
+
|
|
701
|
+
Example:
|
|
702
|
+
>>> or_(col("status") == "active", col("status") == "pending")
|
|
703
|
+
(status = 'active' OR status = 'pending')
|
|
704
|
+
"""
|
|
705
|
+
if not conditions:
|
|
706
|
+
raise ValueError("or_() requires at least one condition")
|
|
707
|
+
if len(conditions) == 1:
|
|
708
|
+
return conditions[0]
|
|
709
|
+
|
|
710
|
+
result = conditions[0]
|
|
711
|
+
for cond in conditions[1:]:
|
|
712
|
+
result = BinaryExpression(result, LogicalOp.OR, cond)
|
|
713
|
+
return result
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def not_(condition: Expression) -> UnaryExpression:
|
|
717
|
+
"""Create a NOT expression.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
condition: Condition to negate.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
NOT expression.
|
|
724
|
+
"""
|
|
725
|
+
return UnaryExpression(UnaryOp.NOT, condition)
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def exists(subquery: "QueryBuilder | SelectStatement") -> ExistsExpression:
|
|
729
|
+
"""Create an EXISTS expression.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
subquery: Subquery to check.
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
EXISTS expression.
|
|
736
|
+
"""
|
|
737
|
+
if isinstance(subquery, QueryBuilder):
|
|
738
|
+
subquery = subquery.build()
|
|
739
|
+
return ExistsExpression(subquery)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def not_exists(subquery: "QueryBuilder | SelectStatement") -> ExistsExpression:
|
|
743
|
+
"""Create a NOT EXISTS expression.
|
|
744
|
+
|
|
745
|
+
Args:
|
|
746
|
+
subquery: Subquery to check.
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
NOT EXISTS expression.
|
|
750
|
+
"""
|
|
751
|
+
if isinstance(subquery, QueryBuilder):
|
|
752
|
+
subquery = subquery.build()
|
|
753
|
+
return ExistsExpression(subquery, negated=True)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def cast(column: ExprLike, target_type: str) -> CastExpression:
|
|
757
|
+
"""Create a CAST expression.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
column: Expression to cast.
|
|
761
|
+
target_type: Target SQL type.
|
|
762
|
+
|
|
763
|
+
Returns:
|
|
764
|
+
CAST expression.
|
|
765
|
+
"""
|
|
766
|
+
return CastExpression(_to_expr(column), target_type)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
# =============================================================================
|
|
770
|
+
# CASE Expression Builder
|
|
771
|
+
# =============================================================================
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
class CaseBuilder:
|
|
775
|
+
"""Builder for CASE expressions.
|
|
776
|
+
|
|
777
|
+
Example:
|
|
778
|
+
>>> case(
|
|
779
|
+
... when(col("status") == "active", "Active"),
|
|
780
|
+
... when(col("status") == "pending", "Pending"),
|
|
781
|
+
... else_="Unknown",
|
|
782
|
+
... )
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
def __init__(self, operand: Expression | None = None) -> None:
|
|
786
|
+
"""Initialize CASE builder.
|
|
787
|
+
|
|
788
|
+
Args:
|
|
789
|
+
operand: Optional operand for simple CASE.
|
|
790
|
+
"""
|
|
791
|
+
self._operand = operand
|
|
792
|
+
self._when_clauses: list[WhenClause] = []
|
|
793
|
+
self._else_result: Expression | None = None
|
|
794
|
+
|
|
795
|
+
def when(self, condition: ExprLike, result: ExprLike) -> "CaseBuilder":
|
|
796
|
+
"""Add a WHEN clause.
|
|
797
|
+
|
|
798
|
+
Args:
|
|
799
|
+
condition: WHEN condition.
|
|
800
|
+
result: THEN result.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
Self for chaining.
|
|
804
|
+
"""
|
|
805
|
+
self._when_clauses.append(
|
|
806
|
+
WhenClause(_to_expr(condition), _to_expr(result))
|
|
807
|
+
)
|
|
808
|
+
return self
|
|
809
|
+
|
|
810
|
+
def else_(self, result: ExprLike) -> "CaseBuilder":
|
|
811
|
+
"""Add an ELSE clause.
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
result: ELSE result.
|
|
815
|
+
|
|
816
|
+
Returns:
|
|
817
|
+
Self for chaining.
|
|
818
|
+
"""
|
|
819
|
+
self._else_result = _to_expr(result)
|
|
820
|
+
return self
|
|
821
|
+
|
|
822
|
+
def build(self) -> CaseExpression:
|
|
823
|
+
"""Build the CASE expression.
|
|
824
|
+
|
|
825
|
+
Returns:
|
|
826
|
+
CaseExpression.
|
|
827
|
+
"""
|
|
828
|
+
return CaseExpression(
|
|
829
|
+
when_clauses=self._when_clauses,
|
|
830
|
+
operand=self._operand,
|
|
831
|
+
else_result=self._else_result,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def case(*args: WhenClause, operand: Expression | None = None, else_: ExprLike | None = None) -> CaseExpression:
|
|
836
|
+
"""Create a CASE expression.
|
|
837
|
+
|
|
838
|
+
Args:
|
|
839
|
+
args: WHEN clauses (use when() to create).
|
|
840
|
+
operand: Optional operand for simple CASE.
|
|
841
|
+
else_: Optional ELSE result.
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
844
|
+
CaseExpression.
|
|
845
|
+
"""
|
|
846
|
+
return CaseExpression(
|
|
847
|
+
when_clauses=args,
|
|
848
|
+
operand=operand,
|
|
849
|
+
else_result=_to_expr(else_) if else_ is not None else None,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def when(condition: ExprLike, result: ExprLike) -> WhenClause:
|
|
854
|
+
"""Create a WHEN clause for CASE expression.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
condition: WHEN condition.
|
|
858
|
+
result: THEN result.
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
WhenClause.
|
|
862
|
+
"""
|
|
863
|
+
return WhenClause(_to_expr(condition), _to_expr(result))
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
# =============================================================================
|
|
867
|
+
# Window Specification Builder
|
|
868
|
+
# =============================================================================
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
class WindowBuilder:
|
|
872
|
+
"""Builder for window specifications.
|
|
873
|
+
|
|
874
|
+
Example:
|
|
875
|
+
>>> window().partition_by("department").order_by("salary", desc=True)
|
|
876
|
+
"""
|
|
877
|
+
|
|
878
|
+
def __init__(self) -> None:
|
|
879
|
+
"""Initialize window builder."""
|
|
880
|
+
self._partition_by: list[Expression] = []
|
|
881
|
+
self._order_by: list[OrderByItem] = []
|
|
882
|
+
self._frame_type: FrameType | None = None
|
|
883
|
+
self._frame_start: FrameBound | None = None
|
|
884
|
+
self._frame_end: FrameBound | None = None
|
|
885
|
+
self._name: str | None = None
|
|
886
|
+
|
|
887
|
+
def partition_by(self, *columns: ExprLike) -> "WindowBuilder":
|
|
888
|
+
"""Add PARTITION BY clause.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
columns: Columns to partition by.
|
|
892
|
+
|
|
893
|
+
Returns:
|
|
894
|
+
Self for chaining.
|
|
895
|
+
"""
|
|
896
|
+
self._partition_by.extend(_to_expr(c) for c in columns)
|
|
897
|
+
return self
|
|
898
|
+
|
|
899
|
+
def order_by(
|
|
900
|
+
self,
|
|
901
|
+
column: ExprLike,
|
|
902
|
+
desc: bool = False,
|
|
903
|
+
descending: bool | None = None,
|
|
904
|
+
nulls: NullsPosition | None = None,
|
|
905
|
+
) -> "WindowBuilder":
|
|
906
|
+
"""Add ORDER BY clause.
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
column: Column to order by.
|
|
910
|
+
desc: Whether to sort descending (short form).
|
|
911
|
+
descending: Whether to sort descending (long form, alias for desc).
|
|
912
|
+
nulls: Position of nulls.
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
Self for chaining.
|
|
916
|
+
"""
|
|
917
|
+
is_descending = descending if descending is not None else desc
|
|
918
|
+
order = SortOrder.DESC if is_descending else SortOrder.ASC
|
|
919
|
+
self._order_by.append(OrderByItem(_to_expr(column), order, nulls))
|
|
920
|
+
return self
|
|
921
|
+
|
|
922
|
+
def rows(self) -> "WindowBuilder":
|
|
923
|
+
"""Use ROWS frame type."""
|
|
924
|
+
self._frame_type = FrameType.ROWS
|
|
925
|
+
return self
|
|
926
|
+
|
|
927
|
+
def range(self) -> "WindowBuilder":
|
|
928
|
+
"""Use RANGE frame type."""
|
|
929
|
+
self._frame_type = FrameType.RANGE
|
|
930
|
+
return self
|
|
931
|
+
|
|
932
|
+
def unbounded_preceding(self) -> "WindowBuilder":
|
|
933
|
+
"""Set frame start to UNBOUNDED PRECEDING."""
|
|
934
|
+
self._frame_start = FrameBound(FrameBoundType.UNBOUNDED_PRECEDING)
|
|
935
|
+
return self
|
|
936
|
+
|
|
937
|
+
def preceding(self, n: int) -> "WindowBuilder":
|
|
938
|
+
"""Set frame start to N PRECEDING."""
|
|
939
|
+
self._frame_start = FrameBound(FrameBoundType.PRECEDING, n)
|
|
940
|
+
return self
|
|
941
|
+
|
|
942
|
+
def current_row(self) -> "WindowBuilder":
|
|
943
|
+
"""Set frame start/end to CURRENT ROW."""
|
|
944
|
+
if self._frame_start is None:
|
|
945
|
+
self._frame_start = FrameBound(FrameBoundType.CURRENT_ROW)
|
|
946
|
+
else:
|
|
947
|
+
self._frame_end = FrameBound(FrameBoundType.CURRENT_ROW)
|
|
948
|
+
return self
|
|
949
|
+
|
|
950
|
+
def following(self, n: int) -> "WindowBuilder":
|
|
951
|
+
"""Set frame end to N FOLLOWING."""
|
|
952
|
+
self._frame_end = FrameBound(FrameBoundType.FOLLOWING, n)
|
|
953
|
+
return self
|
|
954
|
+
|
|
955
|
+
def unbounded_following(self) -> "WindowBuilder":
|
|
956
|
+
"""Set frame end to UNBOUNDED FOLLOWING."""
|
|
957
|
+
self._frame_end = FrameBound(FrameBoundType.UNBOUNDED_FOLLOWING)
|
|
958
|
+
return self
|
|
959
|
+
|
|
960
|
+
def between(
|
|
961
|
+
self,
|
|
962
|
+
start: FrameBound,
|
|
963
|
+
end: FrameBound,
|
|
964
|
+
) -> "WindowBuilder":
|
|
965
|
+
"""Set frame BETWEEN bounds."""
|
|
966
|
+
self._frame_start = start
|
|
967
|
+
self._frame_end = end
|
|
968
|
+
return self
|
|
969
|
+
|
|
970
|
+
def name(self, window_name: str) -> "WindowBuilder":
|
|
971
|
+
"""Set window name."""
|
|
972
|
+
self._name = window_name
|
|
973
|
+
return self
|
|
974
|
+
|
|
975
|
+
def build(self) -> WindowSpec:
|
|
976
|
+
"""Build the window specification.
|
|
977
|
+
|
|
978
|
+
Returns:
|
|
979
|
+
WindowSpec.
|
|
980
|
+
"""
|
|
981
|
+
return WindowSpec(
|
|
982
|
+
partition_by=self._partition_by or None,
|
|
983
|
+
order_by=self._order_by or None,
|
|
984
|
+
frame_type=self._frame_type,
|
|
985
|
+
frame_start=self._frame_start,
|
|
986
|
+
frame_end=self._frame_end,
|
|
987
|
+
name=self._name,
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def window() -> WindowBuilder:
|
|
992
|
+
"""Create a window specification builder.
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
WindowBuilder.
|
|
996
|
+
"""
|
|
997
|
+
return WindowBuilder()
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
# =============================================================================
|
|
1001
|
+
# Query Builder
|
|
1002
|
+
# =============================================================================
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
class QueryBuilder:
|
|
1006
|
+
"""Fluent builder for SELECT queries.
|
|
1007
|
+
|
|
1008
|
+
This class provides a fluent API for building SQL queries,
|
|
1009
|
+
which are compiled into AST nodes.
|
|
1010
|
+
|
|
1011
|
+
Example:
|
|
1012
|
+
>>> query = (
|
|
1013
|
+
... QueryBuilder("users")
|
|
1014
|
+
... .select("id", "name", "email")
|
|
1015
|
+
... .where(col("age") > 18)
|
|
1016
|
+
... .order_by("name")
|
|
1017
|
+
... .limit(100)
|
|
1018
|
+
... )
|
|
1019
|
+
>>> sql = query.to_sql(SQLDialect.POSTGRESQL)
|
|
1020
|
+
"""
|
|
1021
|
+
|
|
1022
|
+
def __init__(
|
|
1023
|
+
self,
|
|
1024
|
+
table: str | Table | "QueryBuilder" | None = None,
|
|
1025
|
+
schema: str | None = None,
|
|
1026
|
+
alias: str | None = None,
|
|
1027
|
+
) -> None:
|
|
1028
|
+
"""Initialize query builder.
|
|
1029
|
+
|
|
1030
|
+
Args:
|
|
1031
|
+
table: Table name, Table object, or subquery.
|
|
1032
|
+
schema: Optional schema name.
|
|
1033
|
+
alias: Optional table alias.
|
|
1034
|
+
"""
|
|
1035
|
+
self._from_source: Table | SelectStatement | JoinClause | None = None
|
|
1036
|
+
self._select_items: list[SelectItem | Expression] = []
|
|
1037
|
+
self._where_conditions: list[Expression] = []
|
|
1038
|
+
self._group_by_exprs: list[Expression] = []
|
|
1039
|
+
self._having_conditions: list[Expression] = []
|
|
1040
|
+
self._order_by_items: list[OrderByItem] = []
|
|
1041
|
+
self._limit_value: int | Expression | None = None
|
|
1042
|
+
self._offset_value: int | Expression | None = None
|
|
1043
|
+
self._distinct: bool = False
|
|
1044
|
+
self._ctes: list[CTEClause] = []
|
|
1045
|
+
self._group_by_rollup: bool = False
|
|
1046
|
+
self._group_by_cube: bool = False
|
|
1047
|
+
self._grouping_sets: list[tuple[Expression, ...]] | None = None
|
|
1048
|
+
self._windows: dict[str, WindowSpec] = {}
|
|
1049
|
+
|
|
1050
|
+
if table is not None:
|
|
1051
|
+
if isinstance(table, Table):
|
|
1052
|
+
self._from_source = table
|
|
1053
|
+
elif isinstance(table, QueryBuilder):
|
|
1054
|
+
self._from_source = table.build()
|
|
1055
|
+
else:
|
|
1056
|
+
self._from_source = Table(table, schema=schema, alias=alias)
|
|
1057
|
+
|
|
1058
|
+
def with_cte(
|
|
1059
|
+
self,
|
|
1060
|
+
name: str,
|
|
1061
|
+
query: "QueryBuilder | SelectStatement",
|
|
1062
|
+
columns: Sequence[str] | None = None,
|
|
1063
|
+
recursive: bool = False,
|
|
1064
|
+
) -> "QueryBuilder":
|
|
1065
|
+
"""Add a Common Table Expression (CTE).
|
|
1066
|
+
|
|
1067
|
+
Args:
|
|
1068
|
+
name: CTE name.
|
|
1069
|
+
query: CTE query.
|
|
1070
|
+
columns: Optional column names.
|
|
1071
|
+
recursive: Whether this is a recursive CTE.
|
|
1072
|
+
|
|
1073
|
+
Returns:
|
|
1074
|
+
Self for chaining.
|
|
1075
|
+
"""
|
|
1076
|
+
if isinstance(query, QueryBuilder):
|
|
1077
|
+
query = query.build()
|
|
1078
|
+
self._ctes.append(CTEClause(name, query, columns, recursive))
|
|
1079
|
+
return self
|
|
1080
|
+
|
|
1081
|
+
def select(self, *columns: ExprLike | Star) -> "QueryBuilder":
|
|
1082
|
+
"""Set SELECT columns.
|
|
1083
|
+
|
|
1084
|
+
Args:
|
|
1085
|
+
columns: Columns or expressions to select.
|
|
1086
|
+
|
|
1087
|
+
Returns:
|
|
1088
|
+
Self for chaining.
|
|
1089
|
+
"""
|
|
1090
|
+
for c in columns:
|
|
1091
|
+
if isinstance(c, str):
|
|
1092
|
+
if c == "*":
|
|
1093
|
+
self._select_items.append(Star())
|
|
1094
|
+
else:
|
|
1095
|
+
self._select_items.append(col(c))
|
|
1096
|
+
elif isinstance(c, (Expression, Star)):
|
|
1097
|
+
self._select_items.append(c)
|
|
1098
|
+
else:
|
|
1099
|
+
self._select_items.append(Literal(c))
|
|
1100
|
+
return self
|
|
1101
|
+
|
|
1102
|
+
def select_all(self) -> "QueryBuilder":
|
|
1103
|
+
"""Select all columns (SELECT *).
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
Self for chaining.
|
|
1107
|
+
"""
|
|
1108
|
+
self._select_items.append(Star())
|
|
1109
|
+
return self
|
|
1110
|
+
|
|
1111
|
+
def distinct(self, value: bool = True) -> "QueryBuilder":
|
|
1112
|
+
"""Enable SELECT DISTINCT.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
value: Whether to enable DISTINCT.
|
|
1116
|
+
|
|
1117
|
+
Returns:
|
|
1118
|
+
Self for chaining.
|
|
1119
|
+
"""
|
|
1120
|
+
self._distinct = value
|
|
1121
|
+
return self
|
|
1122
|
+
|
|
1123
|
+
def from_(
|
|
1124
|
+
self,
|
|
1125
|
+
table: str | Table | "QueryBuilder",
|
|
1126
|
+
schema: str | None = None,
|
|
1127
|
+
alias: str | None = None,
|
|
1128
|
+
) -> "QueryBuilder":
|
|
1129
|
+
"""Set FROM clause.
|
|
1130
|
+
|
|
1131
|
+
Args:
|
|
1132
|
+
table: Table name, Table object, or subquery.
|
|
1133
|
+
schema: Optional schema name.
|
|
1134
|
+
alias: Optional table alias.
|
|
1135
|
+
|
|
1136
|
+
Returns:
|
|
1137
|
+
Self for chaining.
|
|
1138
|
+
"""
|
|
1139
|
+
if isinstance(table, Table):
|
|
1140
|
+
self._from_source = table
|
|
1141
|
+
elif isinstance(table, QueryBuilder):
|
|
1142
|
+
self._from_source = table.build()
|
|
1143
|
+
else:
|
|
1144
|
+
self._from_source = Table(table, schema=schema, alias=alias)
|
|
1145
|
+
return self
|
|
1146
|
+
|
|
1147
|
+
def join(
|
|
1148
|
+
self,
|
|
1149
|
+
table: str | Table | "QueryBuilder",
|
|
1150
|
+
on: Expression | None = None,
|
|
1151
|
+
using: Sequence[str] | None = None,
|
|
1152
|
+
join_type: JoinType = JoinType.INNER,
|
|
1153
|
+
alias: str | None = None,
|
|
1154
|
+
) -> "QueryBuilder":
|
|
1155
|
+
"""Add a JOIN clause.
|
|
1156
|
+
|
|
1157
|
+
Args:
|
|
1158
|
+
table: Table to join.
|
|
1159
|
+
on: Join condition.
|
|
1160
|
+
using: USING columns.
|
|
1161
|
+
join_type: Type of join.
|
|
1162
|
+
alias: Optional table alias.
|
|
1163
|
+
|
|
1164
|
+
Returns:
|
|
1165
|
+
Self for chaining.
|
|
1166
|
+
"""
|
|
1167
|
+
if isinstance(table, str):
|
|
1168
|
+
right: Table | SelectStatement = Table(table, alias=alias)
|
|
1169
|
+
elif isinstance(table, QueryBuilder):
|
|
1170
|
+
right = table.build()
|
|
1171
|
+
else:
|
|
1172
|
+
right = table
|
|
1173
|
+
|
|
1174
|
+
if self._from_source is None:
|
|
1175
|
+
raise ValueError("Cannot JOIN without a FROM clause")
|
|
1176
|
+
|
|
1177
|
+
self._from_source = JoinClause(
|
|
1178
|
+
self._from_source,
|
|
1179
|
+
right,
|
|
1180
|
+
join_type,
|
|
1181
|
+
on,
|
|
1182
|
+
using,
|
|
1183
|
+
)
|
|
1184
|
+
return self
|
|
1185
|
+
|
|
1186
|
+
def inner_join(
|
|
1187
|
+
self,
|
|
1188
|
+
table: str | Table | "QueryBuilder",
|
|
1189
|
+
on: Expression | None = None,
|
|
1190
|
+
using: Sequence[str] | None = None,
|
|
1191
|
+
alias: str | None = None,
|
|
1192
|
+
) -> "QueryBuilder":
|
|
1193
|
+
"""Add an INNER JOIN."""
|
|
1194
|
+
return self.join(table, on, using, JoinType.INNER, alias)
|
|
1195
|
+
|
|
1196
|
+
def left_join(
|
|
1197
|
+
self,
|
|
1198
|
+
table: str | Table | "QueryBuilder",
|
|
1199
|
+
on: Expression | None = None,
|
|
1200
|
+
using: Sequence[str] | None = None,
|
|
1201
|
+
alias: str | None = None,
|
|
1202
|
+
) -> "QueryBuilder":
|
|
1203
|
+
"""Add a LEFT JOIN."""
|
|
1204
|
+
return self.join(table, on, using, JoinType.LEFT, alias)
|
|
1205
|
+
|
|
1206
|
+
def right_join(
|
|
1207
|
+
self,
|
|
1208
|
+
table: str | Table | "QueryBuilder",
|
|
1209
|
+
on: Expression | None = None,
|
|
1210
|
+
using: Sequence[str] | None = None,
|
|
1211
|
+
alias: str | None = None,
|
|
1212
|
+
) -> "QueryBuilder":
|
|
1213
|
+
"""Add a RIGHT JOIN."""
|
|
1214
|
+
return self.join(table, on, using, JoinType.RIGHT, alias)
|
|
1215
|
+
|
|
1216
|
+
def full_join(
|
|
1217
|
+
self,
|
|
1218
|
+
table: str | Table | "QueryBuilder",
|
|
1219
|
+
on: Expression | None = None,
|
|
1220
|
+
using: Sequence[str] | None = None,
|
|
1221
|
+
alias: str | None = None,
|
|
1222
|
+
) -> "QueryBuilder":
|
|
1223
|
+
"""Add a FULL OUTER JOIN."""
|
|
1224
|
+
return self.join(table, on, using, JoinType.FULL_OUTER, alias)
|
|
1225
|
+
|
|
1226
|
+
def cross_join(
|
|
1227
|
+
self,
|
|
1228
|
+
table: str | Table | "QueryBuilder",
|
|
1229
|
+
alias: str | None = None,
|
|
1230
|
+
) -> "QueryBuilder":
|
|
1231
|
+
"""Add a CROSS JOIN."""
|
|
1232
|
+
return self.join(table, None, None, JoinType.CROSS, alias)
|
|
1233
|
+
|
|
1234
|
+
def where(self, *conditions: Expression) -> "QueryBuilder":
|
|
1235
|
+
"""Add WHERE conditions (ANDed together).
|
|
1236
|
+
|
|
1237
|
+
Args:
|
|
1238
|
+
conditions: Filter conditions.
|
|
1239
|
+
|
|
1240
|
+
Returns:
|
|
1241
|
+
Self for chaining.
|
|
1242
|
+
"""
|
|
1243
|
+
self._where_conditions.extend(conditions)
|
|
1244
|
+
return self
|
|
1245
|
+
|
|
1246
|
+
def or_where(self, *conditions: Expression) -> "QueryBuilder":
|
|
1247
|
+
"""Add WHERE conditions with OR (ORed with existing conditions).
|
|
1248
|
+
|
|
1249
|
+
Args:
|
|
1250
|
+
conditions: Filter conditions.
|
|
1251
|
+
|
|
1252
|
+
Returns:
|
|
1253
|
+
Self for chaining.
|
|
1254
|
+
"""
|
|
1255
|
+
if self._where_conditions:
|
|
1256
|
+
existing = and_(*self._where_conditions)
|
|
1257
|
+
new = and_(*conditions)
|
|
1258
|
+
self._where_conditions = [or_(existing, new)]
|
|
1259
|
+
else:
|
|
1260
|
+
self._where_conditions.extend(conditions)
|
|
1261
|
+
return self
|
|
1262
|
+
|
|
1263
|
+
def group_by(self, *columns: ExprLike) -> "QueryBuilder":
|
|
1264
|
+
"""Add GROUP BY clause.
|
|
1265
|
+
|
|
1266
|
+
Args:
|
|
1267
|
+
columns: Columns to group by.
|
|
1268
|
+
|
|
1269
|
+
Returns:
|
|
1270
|
+
Self for chaining.
|
|
1271
|
+
"""
|
|
1272
|
+
self._group_by_exprs.extend(_to_expr(c) for c in columns)
|
|
1273
|
+
return self
|
|
1274
|
+
|
|
1275
|
+
def group_by_rollup(self, *columns: ExprLike) -> "QueryBuilder":
|
|
1276
|
+
"""Add GROUP BY with ROLLUP.
|
|
1277
|
+
|
|
1278
|
+
Args:
|
|
1279
|
+
columns: Columns to group by.
|
|
1280
|
+
|
|
1281
|
+
Returns:
|
|
1282
|
+
Self for chaining.
|
|
1283
|
+
"""
|
|
1284
|
+
self._group_by_exprs.extend(_to_expr(c) for c in columns)
|
|
1285
|
+
self._group_by_rollup = True
|
|
1286
|
+
return self
|
|
1287
|
+
|
|
1288
|
+
def group_by_cube(self, *columns: ExprLike) -> "QueryBuilder":
|
|
1289
|
+
"""Add GROUP BY with CUBE.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
columns: Columns to group by.
|
|
1293
|
+
|
|
1294
|
+
Returns:
|
|
1295
|
+
Self for chaining.
|
|
1296
|
+
"""
|
|
1297
|
+
self._group_by_exprs.extend(_to_expr(c) for c in columns)
|
|
1298
|
+
self._group_by_cube = True
|
|
1299
|
+
return self
|
|
1300
|
+
|
|
1301
|
+
def grouping_sets(
|
|
1302
|
+
self,
|
|
1303
|
+
*sets: Sequence[ExprLike],
|
|
1304
|
+
) -> "QueryBuilder":
|
|
1305
|
+
"""Add GROUPING SETS.
|
|
1306
|
+
|
|
1307
|
+
Args:
|
|
1308
|
+
sets: Grouping sets.
|
|
1309
|
+
|
|
1310
|
+
Returns:
|
|
1311
|
+
Self for chaining.
|
|
1312
|
+
"""
|
|
1313
|
+
self._grouping_sets = [
|
|
1314
|
+
tuple(_to_expr(c) for c in s)
|
|
1315
|
+
for s in sets
|
|
1316
|
+
]
|
|
1317
|
+
return self
|
|
1318
|
+
|
|
1319
|
+
def having(self, *conditions: Expression) -> "QueryBuilder":
|
|
1320
|
+
"""Add HAVING conditions.
|
|
1321
|
+
|
|
1322
|
+
Args:
|
|
1323
|
+
conditions: Filter conditions for groups.
|
|
1324
|
+
|
|
1325
|
+
Returns:
|
|
1326
|
+
Self for chaining.
|
|
1327
|
+
"""
|
|
1328
|
+
self._having_conditions.extend(conditions)
|
|
1329
|
+
return self
|
|
1330
|
+
|
|
1331
|
+
def order_by(
|
|
1332
|
+
self,
|
|
1333
|
+
column: ExprLike | OrderByItem,
|
|
1334
|
+
desc: bool = False,
|
|
1335
|
+
descending: bool | None = None,
|
|
1336
|
+
nulls: NullsPosition | None = None,
|
|
1337
|
+
) -> "QueryBuilder":
|
|
1338
|
+
"""Add ORDER BY clause.
|
|
1339
|
+
|
|
1340
|
+
Args:
|
|
1341
|
+
column: Column or expression to order by.
|
|
1342
|
+
desc: Whether to sort descending (short form).
|
|
1343
|
+
descending: Whether to sort descending (long form, alias for desc).
|
|
1344
|
+
nulls: Position of nulls (NULLS FIRST/LAST).
|
|
1345
|
+
|
|
1346
|
+
Returns:
|
|
1347
|
+
Self for chaining.
|
|
1348
|
+
|
|
1349
|
+
Example:
|
|
1350
|
+
>>> query.order_by("name") # ASC
|
|
1351
|
+
>>> query.order_by("age", desc=True) # DESC
|
|
1352
|
+
>>> query.order_by("created_at", descending=True) # DESC (alias)
|
|
1353
|
+
"""
|
|
1354
|
+
if isinstance(column, OrderByItem):
|
|
1355
|
+
self._order_by_items.append(column)
|
|
1356
|
+
else:
|
|
1357
|
+
# Support both 'desc' and 'descending' parameters
|
|
1358
|
+
is_descending = descending if descending is not None else desc
|
|
1359
|
+
order = SortOrder.DESC if is_descending else SortOrder.ASC
|
|
1360
|
+
self._order_by_items.append(OrderByItem(_to_expr(column), order, nulls))
|
|
1361
|
+
return self
|
|
1362
|
+
|
|
1363
|
+
def limit(self, count: int | Expression) -> "QueryBuilder":
|
|
1364
|
+
"""Set LIMIT clause.
|
|
1365
|
+
|
|
1366
|
+
Args:
|
|
1367
|
+
count: Maximum number of rows.
|
|
1368
|
+
|
|
1369
|
+
Returns:
|
|
1370
|
+
Self for chaining.
|
|
1371
|
+
"""
|
|
1372
|
+
self._limit_value = count
|
|
1373
|
+
return self
|
|
1374
|
+
|
|
1375
|
+
def offset(self, offset: int | Expression) -> "QueryBuilder":
|
|
1376
|
+
"""Set OFFSET clause.
|
|
1377
|
+
|
|
1378
|
+
Args:
|
|
1379
|
+
offset: Number of rows to skip.
|
|
1380
|
+
|
|
1381
|
+
Returns:
|
|
1382
|
+
Self for chaining.
|
|
1383
|
+
"""
|
|
1384
|
+
self._offset_value = offset
|
|
1385
|
+
return self
|
|
1386
|
+
|
|
1387
|
+
def window(self, name: str, spec: WindowSpec | WindowBuilder) -> "QueryBuilder":
|
|
1388
|
+
"""Define a named window.
|
|
1389
|
+
|
|
1390
|
+
Args:
|
|
1391
|
+
name: Window name.
|
|
1392
|
+
spec: Window specification.
|
|
1393
|
+
|
|
1394
|
+
Returns:
|
|
1395
|
+
Self for chaining.
|
|
1396
|
+
"""
|
|
1397
|
+
if isinstance(spec, WindowBuilder):
|
|
1398
|
+
spec = spec.build()
|
|
1399
|
+
self._windows[name] = spec
|
|
1400
|
+
return self
|
|
1401
|
+
|
|
1402
|
+
def union(
|
|
1403
|
+
self,
|
|
1404
|
+
other: "QueryBuilder | SelectStatement",
|
|
1405
|
+
all: bool = False,
|
|
1406
|
+
) -> "SetOperationBuilder":
|
|
1407
|
+
"""Create a UNION with another query.
|
|
1408
|
+
|
|
1409
|
+
Args:
|
|
1410
|
+
other: Other query.
|
|
1411
|
+
all: Whether to use UNION ALL.
|
|
1412
|
+
|
|
1413
|
+
Returns:
|
|
1414
|
+
SetOperationBuilder for further operations.
|
|
1415
|
+
"""
|
|
1416
|
+
operation = SetOperation.UNION_ALL if all else SetOperation.UNION
|
|
1417
|
+
return SetOperationBuilder(self.build(), operation, other)
|
|
1418
|
+
|
|
1419
|
+
def intersect(
|
|
1420
|
+
self,
|
|
1421
|
+
other: "QueryBuilder | SelectStatement",
|
|
1422
|
+
all: bool = False,
|
|
1423
|
+
) -> "SetOperationBuilder":
|
|
1424
|
+
"""Create an INTERSECT with another query."""
|
|
1425
|
+
operation = SetOperation.INTERSECT_ALL if all else SetOperation.INTERSECT
|
|
1426
|
+
return SetOperationBuilder(self.build(), operation, other)
|
|
1427
|
+
|
|
1428
|
+
def except_(
|
|
1429
|
+
self,
|
|
1430
|
+
other: "QueryBuilder | SelectStatement",
|
|
1431
|
+
all: bool = False,
|
|
1432
|
+
) -> "SetOperationBuilder":
|
|
1433
|
+
"""Create an EXCEPT with another query."""
|
|
1434
|
+
operation = SetOperation.EXCEPT_ALL if all else SetOperation.EXCEPT
|
|
1435
|
+
return SetOperationBuilder(self.build(), operation, other)
|
|
1436
|
+
|
|
1437
|
+
def build(self) -> SelectStatement:
|
|
1438
|
+
"""Build the SelectStatement AST node.
|
|
1439
|
+
|
|
1440
|
+
Returns:
|
|
1441
|
+
SelectStatement.
|
|
1442
|
+
"""
|
|
1443
|
+
# Handle empty select - default to *
|
|
1444
|
+
select_items = self._select_items if self._select_items else [Star()]
|
|
1445
|
+
|
|
1446
|
+
# Build FROM clause
|
|
1447
|
+
from_clause = None
|
|
1448
|
+
if self._from_source is not None:
|
|
1449
|
+
from_clause = FromClause(self._from_source)
|
|
1450
|
+
|
|
1451
|
+
# Build WHERE clause
|
|
1452
|
+
where_clause = None
|
|
1453
|
+
if self._where_conditions:
|
|
1454
|
+
if len(self._where_conditions) == 1:
|
|
1455
|
+
where_clause = WhereClause(self._where_conditions[0])
|
|
1456
|
+
else:
|
|
1457
|
+
where_clause = WhereClause(and_(*self._where_conditions))
|
|
1458
|
+
|
|
1459
|
+
# Build GROUP BY clause
|
|
1460
|
+
group_by_clause = None
|
|
1461
|
+
if self._group_by_exprs or self._grouping_sets:
|
|
1462
|
+
group_by_clause = GroupByClause(
|
|
1463
|
+
expressions=self._group_by_exprs,
|
|
1464
|
+
with_rollup=self._group_by_rollup,
|
|
1465
|
+
with_cube=self._group_by_cube,
|
|
1466
|
+
grouping_sets=self._grouping_sets,
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
# Build HAVING clause
|
|
1470
|
+
having_clause = None
|
|
1471
|
+
if self._having_conditions:
|
|
1472
|
+
if len(self._having_conditions) == 1:
|
|
1473
|
+
having_clause = HavingClause(self._having_conditions[0])
|
|
1474
|
+
else:
|
|
1475
|
+
having_clause = HavingClause(and_(*self._having_conditions))
|
|
1476
|
+
|
|
1477
|
+
# Build ORDER BY clause
|
|
1478
|
+
order_by_clause = None
|
|
1479
|
+
if self._order_by_items:
|
|
1480
|
+
order_by_clause = OrderByClause(self._order_by_items)
|
|
1481
|
+
|
|
1482
|
+
# Build LIMIT/OFFSET clauses
|
|
1483
|
+
limit_clause = None
|
|
1484
|
+
if self._limit_value is not None:
|
|
1485
|
+
limit_clause = LimitClause(self._limit_value)
|
|
1486
|
+
|
|
1487
|
+
offset_clause = None
|
|
1488
|
+
if self._offset_value is not None:
|
|
1489
|
+
offset_clause = OffsetClause(self._offset_value)
|
|
1490
|
+
|
|
1491
|
+
return SelectStatement(
|
|
1492
|
+
select_items=select_items,
|
|
1493
|
+
from_clause=from_clause,
|
|
1494
|
+
where_clause=where_clause,
|
|
1495
|
+
group_by_clause=group_by_clause,
|
|
1496
|
+
having_clause=having_clause,
|
|
1497
|
+
order_by_clause=order_by_clause,
|
|
1498
|
+
limit_clause=limit_clause,
|
|
1499
|
+
offset_clause=offset_clause,
|
|
1500
|
+
distinct=self._distinct,
|
|
1501
|
+
ctes=self._ctes if self._ctes else None,
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
def to_sql(self, dialect: SQLDialect = SQLDialect.GENERIC) -> str:
|
|
1505
|
+
"""Generate SQL string for the specified dialect.
|
|
1506
|
+
|
|
1507
|
+
Args:
|
|
1508
|
+
dialect: SQL dialect.
|
|
1509
|
+
|
|
1510
|
+
Returns:
|
|
1511
|
+
SQL string.
|
|
1512
|
+
"""
|
|
1513
|
+
generator = get_dialect_generator(dialect)
|
|
1514
|
+
return generator.generate(self.build())
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
class SetOperationBuilder:
|
|
1518
|
+
"""Builder for set operations (UNION, INTERSECT, EXCEPT)."""
|
|
1519
|
+
|
|
1520
|
+
def __init__(
|
|
1521
|
+
self,
|
|
1522
|
+
left: SelectStatement,
|
|
1523
|
+
operation: SetOperation,
|
|
1524
|
+
right: "QueryBuilder | SelectStatement",
|
|
1525
|
+
) -> None:
|
|
1526
|
+
"""Initialize set operation builder."""
|
|
1527
|
+
if isinstance(right, QueryBuilder):
|
|
1528
|
+
right = right.build()
|
|
1529
|
+
self._statement = SetOperationStatement(left, right, operation)
|
|
1530
|
+
|
|
1531
|
+
def union(
|
|
1532
|
+
self,
|
|
1533
|
+
other: "QueryBuilder | SelectStatement",
|
|
1534
|
+
all: bool = False,
|
|
1535
|
+
) -> "SetOperationBuilder":
|
|
1536
|
+
"""Add another UNION."""
|
|
1537
|
+
operation = SetOperation.UNION_ALL if all else SetOperation.UNION
|
|
1538
|
+
if isinstance(other, QueryBuilder):
|
|
1539
|
+
other = other.build()
|
|
1540
|
+
self._statement = SetOperationStatement(self._statement, other, operation)
|
|
1541
|
+
return self
|
|
1542
|
+
|
|
1543
|
+
def build(self) -> SetOperationStatement:
|
|
1544
|
+
"""Build the set operation statement."""
|
|
1545
|
+
return self._statement
|
|
1546
|
+
|
|
1547
|
+
def to_sql(self, dialect: SQLDialect = SQLDialect.GENERIC) -> str:
|
|
1548
|
+
"""Generate SQL string."""
|
|
1549
|
+
generator = get_dialect_generator(dialect)
|
|
1550
|
+
return generator.generate(self.build())
|