@datarecce/ui 0.1.30 → 0.1.31
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.
- package/dist/api.d.mts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/components.d.mts +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/hooks.d.mts +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/{index-BNUP2V_N.d.ts → index-B9lSPJTi.d.ts} +184 -2
- package/dist/index-B9lSPJTi.d.ts.map +1 -0
- package/dist/{index-DOPZuhD8.d.mts → index-IIXVIoOL.d.mts} +253 -71
- package/dist/index-IIXVIoOL.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +4 -0
- package/dist/theme.d.mts +2 -185
- package/dist/theme.d.ts +2 -185
- package/dist/types.d.mts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -2
- package/recce-source/.editorconfig +26 -0
- package/recce-source/.flake8 +37 -0
- package/recce-source/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
- package/recce-source/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/recce-source/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/recce-source/.github/PULL_REQUEST_TEMPLATE.md +21 -0
- package/recce-source/.github/copilot-instructions.md +331 -0
- package/recce-source/.github/instructions/backend-instructions.md +541 -0
- package/recce-source/.github/instructions/frontend-instructions.md +317 -0
- package/recce-source/.github/workflows/build-statics.yaml +72 -0
- package/recce-source/.github/workflows/bump.yaml +48 -0
- package/recce-source/.github/workflows/integration-tests-cloud.yaml +92 -0
- package/recce-source/.github/workflows/integration-tests-sqlmesh.yaml +33 -0
- package/recce-source/.github/workflows/integration-tests.yaml +52 -0
- package/recce-source/.github/workflows/nightly.yaml +246 -0
- package/recce-source/.github/workflows/release.yaml +196 -0
- package/recce-source/.github/workflows/tests-js.yaml +58 -0
- package/recce-source/.github/workflows/tests-python.yaml +128 -0
- package/recce-source/.pre-commit-config.yaml +26 -0
- package/recce-source/CLAUDE.md +483 -0
- package/recce-source/CODE_OF_CONDUCT.md +128 -0
- package/recce-source/CONTRIBUTING.md +107 -0
- package/recce-source/LICENSE +201 -0
- package/recce-source/Makefile +126 -0
- package/recce-source/README.md +182 -0
- package/recce-source/RECCE_CLOUD.md +81 -0
- package/recce-source/SECURITY.md +25 -0
- package/recce-source/docs/PACKAGING.md +340 -0
- package/recce-source/docs/README.md +1 -0
- package/recce-source/integration_tests/dbt/dbt_project.yml +26 -0
- package/recce-source/integration_tests/dbt/models/customers.sql +69 -0
- package/recce-source/integration_tests/dbt/models/docs.md +14 -0
- package/recce-source/integration_tests/dbt/models/orders.sql +56 -0
- package/recce-source/integration_tests/dbt/models/schema.yml +82 -0
- package/recce-source/integration_tests/dbt/models/staging/schema.yml +31 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_customers.sql +22 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_orders.sql +23 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_payments.sql +25 -0
- package/recce-source/integration_tests/dbt/packages.yml +7 -0
- package/recce-source/integration_tests/dbt/profiles.yml +8 -0
- package/recce-source/integration_tests/dbt/seeds/raw_customers.csv +101 -0
- package/recce-source/integration_tests/dbt/seeds/raw_orders.csv +100 -0
- package/recce-source/integration_tests/dbt/seeds/raw_payments.csv +114 -0
- package/recce-source/integration_tests/dbt/seeds/raw_statuses.csv +5 -0
- package/recce-source/integration_tests/dbt/smoke_test.sh +72 -0
- package/recce-source/integration_tests/dbt/smoke_test_cloud.sh +71 -0
- package/recce-source/integration_tests/sqlmesh/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/audits/assert_item_price_above_zero.sql +9 -0
- package/recce-source/integration_tests/sqlmesh/audits/items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/audits/order_items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/config.py +171 -0
- package/recce-source/integration_tests/sqlmesh/helper.py +20 -0
- package/recce-source/integration_tests/sqlmesh/hooks/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.py +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.sql +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/utils.py +11 -0
- package/recce-source/integration_tests/sqlmesh/metrics/metrics.sql +25 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_by_day.sql +41 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_lifetime.sql +60 -0
- package/recce-source/integration_tests/sqlmesh/models/customers.sql +32 -0
- package/recce-source/integration_tests/sqlmesh/models/items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/marketing.sql +15 -0
- package/recce-source/integration_tests/sqlmesh/models/order_items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/orders.py +70 -0
- package/recce-source/integration_tests/sqlmesh/models/raw_marketing.py +62 -0
- package/recce-source/integration_tests/sqlmesh/models/top_waiters.sql +23 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_as_customer_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_names.sql +10 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_revenue_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiters.py +62 -0
- package/recce-source/integration_tests/sqlmesh/prep_env.sh +16 -0
- package/recce-source/integration_tests/sqlmesh/schema.yaml +5 -0
- package/recce-source/integration_tests/sqlmesh/seeds/waiter_names.csv +11 -0
- package/recce-source/integration_tests/sqlmesh/test_server.sh +29 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_customer_revenue_by_day.yaml +63 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_order_items.yaml +72 -0
- package/recce-source/js/.editorconfig +27 -0
- package/recce-source/js/.env.development +5 -0
- package/recce-source/js/.husky/pre-commit +29 -0
- package/recce-source/js/.nvmrc +1 -0
- package/recce-source/js/README.md +39 -0
- package/recce-source/js/app/(mainComponents)/DisplayModeToggle.tsx +65 -0
- package/recce-source/js/app/(mainComponents)/NavBar.tsx +228 -0
- package/recce-source/js/app/(mainComponents)/RecceVersionBadge.tsx +107 -0
- package/recce-source/js/app/(mainComponents)/TopBar.tsx +252 -0
- package/recce-source/js/app/@lineage/default.tsx +20 -0
- package/recce-source/js/app/@lineage/page.tsx +14 -0
- package/recce-source/js/app/MainLayout.tsx +170 -0
- package/recce-source/js/app/Providers.tsx +49 -0
- package/recce-source/js/app/checks/page.tsx +296 -0
- package/recce-source/js/app/error.tsx +93 -0
- package/recce-source/js/app/favicon.ico +0 -0
- package/recce-source/js/app/global-error.tsx +115 -0
- package/recce-source/js/app/global.css +82 -0
- package/recce-source/js/app/layout.tsx +48 -0
- package/recce-source/js/app/lineage/page.tsx +15 -0
- package/recce-source/js/app/page.tsx +12 -0
- package/recce-source/js/app/query/page.tsx +8 -0
- package/recce-source/js/biome.json +313 -0
- package/recce-source/js/jest.config.js +34 -0
- package/recce-source/js/jest.globals.d.ts +32 -0
- package/recce-source/js/jest.setup.js +91 -0
- package/recce-source/js/next.config.js +16 -0
- package/recce-source/js/package-lock.json +13843 -0
- package/recce-source/js/package.json +123 -0
- package/recce-source/js/pnpm-lock.yaml +9235 -0
- package/recce-source/js/pnpm-workspace.yaml +6 -0
- package/recce-source/js/postcss.config.js +5 -0
- package/recce-source/js/public/auth_callback.html +68 -0
- package/recce-source/js/public/imgs/feedback/thumbs-down.png +0 -0
- package/recce-source/js/public/imgs/feedback/thumbs-up.png +0 -0
- package/recce-source/js/public/imgs/reload-image.svg +4 -0
- package/recce-source/js/public/logo/recce-logo-white.png +0 -0
- package/recce-source/js/src/components/AuthModal/AuthModal.tsx +202 -0
- package/recce-source/js/src/components/app/AvatarDropdown.tsx +159 -0
- package/recce-source/js/src/components/app/EnvInfo.tsx +357 -0
- package/recce-source/js/src/components/app/Filename.tsx +388 -0
- package/recce-source/js/src/components/app/SetupConnectionPopover.tsx +91 -0
- package/recce-source/js/src/components/app/StateExporter.tsx +57 -0
- package/recce-source/js/src/components/app/StateImporter.tsx +198 -0
- package/recce-source/js/src/components/app/StateSharing.tsx +145 -0
- package/recce-source/js/src/components/app/StateSynchronizer.tsx +205 -0
- package/recce-source/js/src/components/charts/HistogramChart.tsx +291 -0
- package/recce-source/js/src/components/charts/SquareIcon.tsx +51 -0
- package/recce-source/js/src/components/charts/TopKSummaryList.tsx +457 -0
- package/recce-source/js/src/components/charts/chartTheme.ts +74 -0
- package/recce-source/js/src/components/check/CheckBreadcrumb.tsx +97 -0
- package/recce-source/js/src/components/check/CheckDescription.tsx +134 -0
- package/recce-source/js/src/components/check/CheckDetail.tsx +797 -0
- package/recce-source/js/src/components/check/CheckEmptyState.tsx +84 -0
- package/recce-source/js/src/components/check/CheckList.tsx +320 -0
- package/recce-source/js/src/components/check/LineageDiffView.tsx +32 -0
- package/recce-source/js/src/components/check/PresetCheckTemplateView.tsx +48 -0
- package/recce-source/js/src/components/check/SchemaDiffView.tsx +290 -0
- package/recce-source/js/src/components/check/check.ts +25 -0
- package/recce-source/js/src/components/check/timeline/CheckTimeline.tsx +163 -0
- package/recce-source/js/src/components/check/timeline/CommentInput.tsx +84 -0
- package/recce-source/js/src/components/check/timeline/TimelineEvent.tsx +468 -0
- package/recce-source/js/src/components/check/timeline/index.ts +12 -0
- package/recce-source/js/src/components/check/utils.ts +12 -0
- package/recce-source/js/src/components/data-grid/ScreenshotDataGrid.tsx +333 -0
- package/recce-source/js/src/components/data-grid/agGridStyles.css +55 -0
- package/recce-source/js/src/components/data-grid/agGridTheme.ts +43 -0
- package/recce-source/js/src/components/editor/CodeEditor.tsx +107 -0
- package/recce-source/js/src/components/editor/DiffEditor.tsx +162 -0
- package/recce-source/js/src/components/editor/index.ts +12 -0
- package/recce-source/js/src/components/errorboundary/ErrorBoundary.tsx +87 -0
- package/recce-source/js/src/components/histogram/HistogramDiffForm.tsx +147 -0
- package/recce-source/js/src/components/histogram/HistogramDiffResultView.tsx +63 -0
- package/recce-source/js/src/components/icons/index.tsx +142 -0
- package/recce-source/js/src/components/lineage/ActionControl.tsx +63 -0
- package/recce-source/js/src/components/lineage/ActionTag.tsx +141 -0
- package/recce-source/js/src/components/lineage/ChangeStatusLegend.tsx +46 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageControl.tsx +327 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageLegend.tsx +57 -0
- package/recce-source/js/src/components/lineage/GraphColumnNode.tsx +199 -0
- package/recce-source/js/src/components/lineage/GraphEdge.tsx +59 -0
- package/recce-source/js/src/components/lineage/GraphNode.tsx +555 -0
- package/recce-source/js/src/components/lineage/LineagePage.tsx +10 -0
- package/recce-source/js/src/components/lineage/LineageView.tsx +1384 -0
- package/recce-source/js/src/components/lineage/LineageViewContext.tsx +86 -0
- package/recce-source/js/src/components/lineage/LineageViewContextMenu.tsx +637 -0
- package/recce-source/js/src/components/lineage/LineageViewNotification.tsx +64 -0
- package/recce-source/js/src/components/lineage/LineageViewTopBar.tsx +596 -0
- package/recce-source/js/src/components/lineage/NodeSqlView.tsx +136 -0
- package/recce-source/js/src/components/lineage/NodeTag.tsx +278 -0
- package/recce-source/js/src/components/lineage/NodeView.tsx +642 -0
- package/recce-source/js/src/components/lineage/SandboxView.tsx +436 -0
- package/recce-source/js/src/components/lineage/ServerDisconnectedModalContent.tsx +105 -0
- package/recce-source/js/src/components/lineage/SetupConnectionBanner.tsx +52 -0
- package/recce-source/js/src/components/lineage/SingleEnvironmentQueryView.tsx +152 -0
- package/recce-source/js/src/components/lineage/graph.test.ts +31 -0
- package/recce-source/js/src/components/lineage/graph.ts +58 -0
- package/recce-source/js/src/components/lineage/lineage.test.ts +169 -0
- package/recce-source/js/src/components/lineage/lineage.ts +521 -0
- package/recce-source/js/src/components/lineage/styles.css +42 -0
- package/recce-source/js/src/components/lineage/styles.tsx +165 -0
- package/recce-source/js/src/components/lineage/useMultiNodesAction.ts +352 -0
- package/recce-source/js/src/components/lineage/useValueDiffAlertDialog.tsx +108 -0
- package/recce-source/js/src/components/onboarding-guide/Notification.tsx +62 -0
- package/recce-source/js/src/components/profile/ProfileDiffForm.tsx +134 -0
- package/recce-source/js/src/components/profile/ProfileDiffResultView.tsx +245 -0
- package/recce-source/js/src/components/query/ChangedOnlyCheckbox.tsx +29 -0
- package/recce-source/js/src/components/query/DiffText.tsx +120 -0
- package/recce-source/js/src/components/query/QueryDiffResultView.tsx +470 -0
- package/recce-source/js/src/components/query/QueryForm.tsx +80 -0
- package/recce-source/js/src/components/query/QueryPage.tsx +282 -0
- package/recce-source/js/src/components/query/QueryResultView.tsx +180 -0
- package/recce-source/js/src/components/query/SetupConnectionGuide.tsx +57 -0
- package/recce-source/js/src/components/query/SqlEditor.tsx +245 -0
- package/recce-source/js/src/components/query/ToggleSwitch.tsx +84 -0
- package/recce-source/js/src/components/query/styles.css +21 -0
- package/recce-source/js/src/components/routing/DirectUrlAccess.test.tsx +428 -0
- package/recce-source/js/src/components/routing/LineageStatePreservation.test.tsx +311 -0
- package/recce-source/js/src/components/routing/Navigation.test.tsx +256 -0
- package/recce-source/js/src/components/rowcount/RowCountDiffResultView.tsx +109 -0
- package/recce-source/js/src/components/rowcount/delta.ts +11 -0
- package/recce-source/js/src/components/run/RunList.tsx +303 -0
- package/recce-source/js/src/components/run/RunModal.tsx +191 -0
- package/recce-source/js/src/components/run/RunPage.tsx +26 -0
- package/recce-source/js/src/components/run/RunResultPane.tsx +454 -0
- package/recce-source/js/src/components/run/RunStatusAndDate.tsx +106 -0
- package/recce-source/js/src/components/run/RunToolbar.tsx +70 -0
- package/recce-source/js/src/components/run/RunView.tsx +196 -0
- package/recce-source/js/src/components/run/registry.ts +214 -0
- package/recce-source/js/src/components/run/types.ts +14 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.test.tsx +169 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.tsx +198 -0
- package/recce-source/js/src/components/schema/SchemaView.tsx +337 -0
- package/recce-source/js/src/components/schema/schemaDiff.ts +32 -0
- package/recce-source/js/src/components/schema/style.css +134 -0
- package/recce-source/js/src/components/screenshot/ScreenshotBox.tsx +39 -0
- package/recce-source/js/src/components/shared/HistoryToggle.tsx +35 -0
- package/recce-source/js/src/components/split/Split.tsx +40 -0
- package/recce-source/js/src/components/split/styles.css +24 -0
- package/recce-source/js/src/components/summary/ChangeSummary.tsx +264 -0
- package/recce-source/js/src/components/summary/SchemaSummary.tsx +123 -0
- package/recce-source/js/src/components/summary/SummaryView.tsx +29 -0
- package/recce-source/js/src/components/timeout/IdleTimeoutBadge.tsx +48 -0
- package/recce-source/js/src/components/top-k/TopKDiffForm.tsx +58 -0
- package/recce-source/js/src/components/top-k/TopKDiffResultView.tsx +73 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnGroupHeader.tsx +228 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnHeader.tsx +113 -0
- package/recce-source/js/src/components/ui/dataGrid/defaultRenderCell.tsx +72 -0
- package/recce-source/js/src/components/ui/dataGrid/index.ts +23 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.test.tsx +607 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.tsx +211 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.test.tsx +452 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.tsx +142 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.test.tsx +178 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.tsx +275 -0
- package/recce-source/js/src/components/ui/markdown/ExternalLinkConfirmDialog.tsx +134 -0
- package/recce-source/js/src/components/ui/markdown/MarkdownContent.tsx +364 -0
- package/recce-source/js/src/components/ui/mui/index.ts +13 -0
- package/recce-source/js/src/components/ui/mui-provider.tsx +67 -0
- package/recce-source/js/src/components/ui/mui-theme.ts +1039 -0
- package/recce-source/js/src/components/ui/mui-utils.ts +113 -0
- package/recce-source/js/src/components/ui/toaster.tsx +288 -0
- package/recce-source/js/src/components/valuediff/ValueDiffDetailResultView.tsx +217 -0
- package/recce-source/js/src/components/valuediff/ValueDiffForm.tsx +246 -0
- package/recce-source/js/src/components/valuediff/ValueDiffResultView.tsx +82 -0
- package/recce-source/js/src/components/valuediff/shared.ts +33 -0
- package/recce-source/js/src/constants/tooltipMessage.ts +3 -0
- package/recce-source/js/src/constants/urls.ts +1 -0
- package/recce-source/js/src/lib/UrlHash.ts +12 -0
- package/recce-source/js/src/lib/api/adhocQuery.ts +70 -0
- package/recce-source/js/src/lib/api/axiosClient.ts +9 -0
- package/recce-source/js/src/lib/api/cacheKeys.ts +13 -0
- package/recce-source/js/src/lib/api/checkEvents.ts +252 -0
- package/recce-source/js/src/lib/api/checks.ts +129 -0
- package/recce-source/js/src/lib/api/cll.ts +53 -0
- package/recce-source/js/src/lib/api/connectToCloud.ts +13 -0
- package/recce-source/js/src/lib/api/flag.ts +37 -0
- package/recce-source/js/src/lib/api/info.ts +198 -0
- package/recce-source/js/src/lib/api/instanceInfo.ts +25 -0
- package/recce-source/js/src/lib/api/keepAlive.ts +108 -0
- package/recce-source/js/src/lib/api/lineagecheck.ts +35 -0
- package/recce-source/js/src/lib/api/localStorageKeys.ts +7 -0
- package/recce-source/js/src/lib/api/models.ts +59 -0
- package/recce-source/js/src/lib/api/profile.ts +65 -0
- package/recce-source/js/src/lib/api/rowcount.ts +19 -0
- package/recce-source/js/src/lib/api/runs.ts +174 -0
- package/recce-source/js/src/lib/api/schemacheck.ts +31 -0
- package/recce-source/js/src/lib/api/select.ts +25 -0
- package/recce-source/js/src/lib/api/sessionStorageKeys.ts +8 -0
- package/recce-source/js/src/lib/api/state.ts +117 -0
- package/recce-source/js/src/lib/api/track.ts +281 -0
- package/recce-source/js/src/lib/api/types.ts +284 -0
- package/recce-source/js/src/lib/api/user.ts +42 -0
- package/recce-source/js/src/lib/api/valuediff.ts +46 -0
- package/recce-source/js/src/lib/api/version.ts +40 -0
- package/recce-source/js/src/lib/const.ts +9 -0
- package/recce-source/js/src/lib/dataGrid/crossFunctionConsistency.test.ts +626 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.test.ts +2140 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.ts +397 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.test.ts +132 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.ts +126 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.test.ts +1627 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.ts +140 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataGrid.ts +67 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.test.ts +142 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.ts +71 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.test.ts +258 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.ts +153 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.test.ts +951 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.ts +221 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.test.ts +395 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.ts +184 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.test.ts +884 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.ts +113 -0
- package/recce-source/js/src/lib/dataGrid/index.ts +51 -0
- package/recce-source/js/src/lib/dataGrid/propertyBased.test.ts +858 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.test.ts +482 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.ts +345 -0
- package/recce-source/js/src/lib/dataGrid/shared/dataTypeEdgeCases.test.ts +698 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.test.tsx +820 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.tsx +277 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.test.ts +785 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.ts +370 -0
- package/recce-source/js/src/lib/dataGrid/shared/index.ts +81 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.test.ts +909 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.ts +325 -0
- package/recce-source/js/src/lib/dataGrid/shared/simpleColumnBuilder.tsx +240 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.test.tsx +719 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.tsx +231 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.test.ts +559 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.ts +367 -0
- package/recce-source/js/src/lib/dataGrid/warehouseNamingConventions.test.ts +1117 -0
- package/recce-source/js/src/lib/formatSelect.ts +50 -0
- package/recce-source/js/src/lib/hooks/ApiConfigContext.tsx +181 -0
- package/recce-source/js/src/lib/hooks/IdleTimeoutContext.tsx +177 -0
- package/recce-source/js/src/lib/hooks/LineageGraphContext.tsx +512 -0
- package/recce-source/js/src/lib/hooks/RecceActionContext.tsx +269 -0
- package/recce-source/js/src/lib/hooks/RecceCheckContext.tsx +33 -0
- package/recce-source/js/src/lib/hooks/RecceContextProvider.tsx +54 -0
- package/recce-source/js/src/lib/hooks/RecceInstanceContext.tsx +129 -0
- package/recce-source/js/src/lib/hooks/RecceQueryContext.tsx +98 -0
- package/recce-source/js/src/lib/hooks/RecceShareStateContext.tsx +59 -0
- package/recce-source/js/src/lib/hooks/ScreenShot.tsx +399 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.test.ts +211 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.ts +200 -0
- package/recce-source/js/src/lib/hooks/useCheckEvents.ts +99 -0
- package/recce-source/js/src/lib/hooks/useCheckToast.tsx +14 -0
- package/recce-source/js/src/lib/hooks/useClipBoardToast.tsx +27 -0
- package/recce-source/js/src/lib/hooks/useCountdownToast.tsx +102 -0
- package/recce-source/js/src/lib/hooks/useFeedbackCollectionToast.tsx +130 -0
- package/recce-source/js/src/lib/hooks/useGuideToast.tsx +45 -0
- package/recce-source/js/src/lib/hooks/useIdleDetection.tsx +185 -0
- package/recce-source/js/src/lib/hooks/useModelColumns.tsx +113 -0
- package/recce-source/js/src/lib/hooks/useRecceInstanceInfo.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRecceServerFlag.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRun.tsx +89 -0
- package/recce-source/js/src/lib/hooks/useThemeColors.ts +115 -0
- package/recce-source/js/src/lib/mergeKeys.test.ts +89 -0
- package/recce-source/js/src/lib/mergeKeys.ts +86 -0
- package/recce-source/js/src/lib/result/ResultErrorFallback.tsx +9 -0
- package/recce-source/js/src/lib/utils/formatTime.ts +84 -0
- package/recce-source/js/src/lib/utils/urls.ts +16 -0
- package/recce-source/js/src/utils/DropdownValuesInput.tsx +297 -0
- package/recce-source/js/src/utils/formatters.tsx +237 -0
- package/recce-source/js/src/utils/transforms.ts +81 -0
- package/recce-source/js/tsconfig.json +47 -0
- package/recce-source/macros/README.md +8 -0
- package/recce-source/macros/recce_athena.sql +73 -0
- package/recce-source/pyproject.toml +109 -0
- package/recce-source/recce/VERSION +1 -0
- package/recce-source/recce/__init__.py +84 -0
- package/recce-source/recce/adapter/__init__.py +0 -0
- package/recce-source/recce/adapter/base.py +109 -0
- package/recce-source/recce/adapter/dbt_adapter/__init__.py +1699 -0
- package/recce-source/recce/adapter/dbt_adapter/dbt_version.py +42 -0
- package/recce-source/recce/adapter/sqlmesh_adapter.py +141 -0
- package/recce-source/recce/apis/__init__.py +0 -0
- package/recce-source/recce/apis/check_api.py +203 -0
- package/recce-source/recce/apis/check_events_api.py +353 -0
- package/recce-source/recce/apis/check_func.py +130 -0
- package/recce-source/recce/apis/run_api.py +130 -0
- package/recce-source/recce/apis/run_func.py +258 -0
- package/recce-source/recce/artifact.py +266 -0
- package/recce-source/recce/cli.py +1846 -0
- package/recce-source/recce/config.py +127 -0
- package/recce-source/recce/connect_to_cloud.py +138 -0
- package/recce-source/recce/core.py +334 -0
- package/recce-source/recce/diff.py +26 -0
- package/recce-source/recce/event/CONFIG +1 -0
- package/recce-source/recce/event/SENTRY_DNS +1 -0
- package/recce-source/recce/event/__init__.py +304 -0
- package/recce-source/recce/event/collector.py +184 -0
- package/recce-source/recce/event/track.py +158 -0
- package/recce-source/recce/exceptions.py +21 -0
- package/recce-source/recce/git.py +77 -0
- package/recce-source/recce/github.py +222 -0
- package/recce-source/recce/mcp_server.py +861 -0
- package/recce-source/recce/models/__init__.py +6 -0
- package/recce-source/recce/models/check.py +473 -0
- package/recce-source/recce/models/run.py +46 -0
- package/recce-source/recce/models/types.py +218 -0
- package/recce-source/recce/pull_request.py +124 -0
- package/recce-source/recce/run.py +390 -0
- package/recce-source/recce/server.py +877 -0
- package/recce-source/recce/state/__init__.py +31 -0
- package/recce-source/recce/state/cloud.py +644 -0
- package/recce-source/recce/state/const.py +26 -0
- package/recce-source/recce/state/local.py +56 -0
- package/recce-source/recce/state/state.py +119 -0
- package/recce-source/recce/state/state_loader.py +174 -0
- package/recce-source/recce/summary.py +575 -0
- package/recce-source/recce/tasks/__init__.py +23 -0
- package/recce-source/recce/tasks/core.py +134 -0
- package/recce-source/recce/tasks/dataframe.py +170 -0
- package/recce-source/recce/tasks/histogram.py +433 -0
- package/recce-source/recce/tasks/lineage.py +19 -0
- package/recce-source/recce/tasks/profile.py +298 -0
- package/recce-source/recce/tasks/query.py +450 -0
- package/recce-source/recce/tasks/rowcount.py +277 -0
- package/recce-source/recce/tasks/schema.py +65 -0
- package/recce-source/recce/tasks/top_k.py +172 -0
- package/recce-source/recce/tasks/utils.py +147 -0
- package/recce-source/recce/tasks/valuediff.py +497 -0
- package/recce-source/recce/util/__init__.py +4 -0
- package/recce-source/recce/util/api_token.py +80 -0
- package/recce-source/recce/util/breaking.py +330 -0
- package/recce-source/recce/util/cache.py +25 -0
- package/recce-source/recce/util/cll.py +355 -0
- package/recce-source/recce/util/cloud/__init__.py +15 -0
- package/recce-source/recce/util/cloud/base.py +115 -0
- package/recce-source/recce/util/cloud/check_events.py +190 -0
- package/recce-source/recce/util/cloud/checks.py +242 -0
- package/recce-source/recce/util/io.py +120 -0
- package/recce-source/recce/util/lineage.py +83 -0
- package/recce-source/recce/util/logger.py +25 -0
- package/recce-source/recce/util/onboarding_state.py +45 -0
- package/recce-source/recce/util/perf_tracking.py +85 -0
- package/recce-source/recce/util/pydantic_model.py +22 -0
- package/recce-source/recce/util/recce_cloud.py +454 -0
- package/recce-source/recce/util/singleton.py +18 -0
- package/recce-source/recce/util/startup_perf.py +121 -0
- package/recce-source/recce/yaml/__init__.py +58 -0
- package/recce-source/recce_cloud/README.md +780 -0
- package/recce-source/recce_cloud/VERSION +1 -0
- package/recce-source/recce_cloud/__init__.py +24 -0
- package/recce-source/recce_cloud/api/__init__.py +17 -0
- package/recce-source/recce_cloud/api/base.py +132 -0
- package/recce-source/recce_cloud/api/client.py +186 -0
- package/recce-source/recce_cloud/api/exceptions.py +26 -0
- package/recce-source/recce_cloud/api/factory.py +63 -0
- package/recce-source/recce_cloud/api/github.py +106 -0
- package/recce-source/recce_cloud/api/gitlab.py +111 -0
- package/recce-source/recce_cloud/artifact.py +57 -0
- package/recce-source/recce_cloud/ci_providers/__init__.py +9 -0
- package/recce-source/recce_cloud/ci_providers/base.py +82 -0
- package/recce-source/recce_cloud/ci_providers/detector.py +147 -0
- package/recce-source/recce_cloud/ci_providers/github_actions.py +136 -0
- package/recce-source/recce_cloud/ci_providers/gitlab_ci.py +130 -0
- package/recce-source/recce_cloud/cli.py +434 -0
- package/recce-source/recce_cloud/download.py +230 -0
- package/recce-source/recce_cloud/hatch_build.py +20 -0
- package/recce-source/recce_cloud/pyproject.toml +49 -0
- package/recce-source/recce_cloud/upload.py +214 -0
- package/recce-source/test.py +0 -0
- package/recce-source/tests/__init__.py +0 -0
- package/recce-source/tests/adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/conftest.py +17 -0
- package/recce-source/tests/adapter/dbt_adapter/dbt_test_helper.py +298 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_adapter.py +25 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_cll.py +717 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/dbt_project.yml +4 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/manifest.json +1 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/package-lock.yml +8 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/packages.yml +7 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/profiles.yml +6 -0
- package/recce-source/tests/adapter/dbt_adapter/test_selector.py +205 -0
- package/recce-source/tests/apis/__init__.py +0 -0
- package/recce-source/tests/apis/row_count_diff.json +59 -0
- package/recce-source/tests/apis/test_check_events_api.py +615 -0
- package/recce-source/tests/apis/test_run_func.py +433 -0
- package/recce-source/tests/catalog.json +527 -0
- package/recce-source/tests/data/manifest/base/catalog.json +1 -0
- package/recce-source/tests/data/manifest/base/manifest.json +1 -0
- package/recce-source/tests/data/manifest/pr2/catalog.json +1 -0
- package/recce-source/tests/data/manifest/pr2/manifest.json +1 -0
- package/recce-source/tests/manifest.json +10655 -0
- package/recce-source/tests/models/__init__.py +0 -0
- package/recce-source/tests/models/test_check.py +731 -0
- package/recce-source/tests/models/test_run_models.py +295 -0
- package/recce-source/tests/recce_cloud/__init__.py +0 -0
- package/recce-source/tests/recce_cloud/test_ci_providers.py +351 -0
- package/recce-source/tests/recce_cloud/test_cli.py +735 -0
- package/recce-source/tests/recce_cloud/test_client.py +379 -0
- package/recce-source/tests/recce_cloud/test_platform_clients.py +483 -0
- package/recce-source/tests/recce_state.json +1 -0
- package/recce-source/tests/state/test_cloud.py +719 -0
- package/recce-source/tests/state/test_local.py +164 -0
- package/recce-source/tests/state/test_state_loader.py +211 -0
- package/recce-source/tests/tasks/__init__.py +0 -0
- package/recce-source/tests/tasks/conftest.py +4 -0
- package/recce-source/tests/tasks/test_histogram.py +129 -0
- package/recce-source/tests/tasks/test_lineage.py +55 -0
- package/recce-source/tests/tasks/test_preset_checks.py +64 -0
- package/recce-source/tests/tasks/test_profile.py +397 -0
- package/recce-source/tests/tasks/test_query.py +528 -0
- package/recce-source/tests/tasks/test_row_count.py +133 -0
- package/recce-source/tests/tasks/test_schema.py +122 -0
- package/recce-source/tests/tasks/test_top_k.py +77 -0
- package/recce-source/tests/tasks/test_utils.py +439 -0
- package/recce-source/tests/tasks/test_valuediff.py +361 -0
- package/recce-source/tests/test_cli.py +236 -0
- package/recce-source/tests/test_cli_mcp_optional.py +45 -0
- package/recce-source/tests/test_cloud_listing_cli.py +324 -0
- package/recce-source/tests/test_config.py +43 -0
- package/recce-source/tests/test_connect_to_cloud.py +82 -0
- package/recce-source/tests/test_core.py +174 -0
- package/recce-source/tests/test_dbt.py +36 -0
- package/recce-source/tests/test_mcp_server.py +505 -0
- package/recce-source/tests/test_pull_request.py +130 -0
- package/recce-source/tests/test_server.py +202 -0
- package/recce-source/tests/test_server_lifespan.py +138 -0
- package/recce-source/tests/test_summary.py +73 -0
- package/recce-source/tests/util/__init__.py +0 -0
- package/recce-source/tests/util/cloud/__init__.py +0 -0
- package/recce-source/tests/util/cloud/test_check_events.py +255 -0
- package/recce-source/tests/util/cloud/test_checks.py +204 -0
- package/recce-source/tests/util/test_api_token.py +119 -0
- package/recce-source/tests/util/test_breaking.py +1427 -0
- package/recce-source/tests/util/test_cll.py +706 -0
- package/recce-source/tests/util/test_lineage.py +122 -0
- package/recce-source/tests/util/test_onboarding_state.py +84 -0
- package/recce-source/tests/util/test_recce_cloud.py +231 -0
- package/recce-source/tox.ini +40 -0
- package/recce-source/uv.lock +3928 -0
- package/src/api/index.ts +32 -0
- package/src/components/index.ts +154 -0
- package/src/global.d.ts +14 -0
- package/src/hooks/index.ts +56 -0
- package/src/index.ts +17 -0
- package/src/lib/hooks/RouteConfigContext.ts +139 -0
- package/src/lib/hooks/useAppRouter.ts +240 -0
- package/src/mui-augmentation.d.ts +139 -0
- package/src/theme/index.ts +13 -0
- package/src/theme.ts +23 -0
- package/src/types/index.ts +23 -0
- package/dist/index-BNUP2V_N.d.ts.map +0 -1
- package/dist/index-DOPZuhD8.d.mts.map +0 -1
- package/dist/theme.d.mts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from typing import List, Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from recce.core import default_context
|
|
6
|
+
from recce.models import Check
|
|
7
|
+
from recce.tasks import Task
|
|
8
|
+
from recce.tasks.core import CheckValidator, TaskResultDiffer
|
|
9
|
+
from recce.tasks.query import QueryMixin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RowCountParams(BaseModel):
|
|
13
|
+
node_names: Optional[list[str]] = None
|
|
14
|
+
node_ids: Optional[list[str]] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RowCountTask(Task, QueryMixin):
|
|
18
|
+
def __init__(self, params: dict):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.params = RowCountParams(**params) if params is not None else RowCountParams()
|
|
21
|
+
self.connection = None
|
|
22
|
+
|
|
23
|
+
def _query_row_count(self, dbt_adapter, model_name, base=False):
|
|
24
|
+
node = dbt_adapter.find_node_by_name(model_name, base=base)
|
|
25
|
+
if node is None:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
if node.resource_type != "model" and node.resource_type != "snapshot":
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
if node.config and node.config.materialized not in ["table", "view", "incremental", "snapshot"]:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
relation = dbt_adapter.create_relation(model_name, base=base)
|
|
35
|
+
if relation is None:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
sql_template = r"select count(*) from {{ relation }}"
|
|
39
|
+
sql = dbt_adapter.generate_sql(sql_template, context=dict(relation=relation))
|
|
40
|
+
_, table = dbt_adapter.execute(sql, fetch=True)
|
|
41
|
+
return int(table[0][0]) if table[0][0] is not None else 0
|
|
42
|
+
|
|
43
|
+
def execute(self):
|
|
44
|
+
result = {}
|
|
45
|
+
|
|
46
|
+
dbt_adapter = default_context().adapter
|
|
47
|
+
|
|
48
|
+
query_candidates = []
|
|
49
|
+
if self.params.node_ids or self.params.node_names:
|
|
50
|
+
for node_id in self.params.node_ids or []:
|
|
51
|
+
name = dbt_adapter.get_node_name_by_id(node_id)
|
|
52
|
+
if name:
|
|
53
|
+
query_candidates.append(name)
|
|
54
|
+
for node in self.params.node_names or []:
|
|
55
|
+
query_candidates.append(node)
|
|
56
|
+
else:
|
|
57
|
+
|
|
58
|
+
def countable(unique_id):
|
|
59
|
+
return unique_id.startswith("model") or unique_id.startswith("snapshot") or unique_id.startswith("seed")
|
|
60
|
+
|
|
61
|
+
node_ids = dbt_adapter.select_nodes(
|
|
62
|
+
select=self.params.select,
|
|
63
|
+
exclude=self.params.exclude,
|
|
64
|
+
packages=self.params.packages,
|
|
65
|
+
view_mode=self.params.view_mode,
|
|
66
|
+
)
|
|
67
|
+
node_ids = list(filter(countable, node_ids))
|
|
68
|
+
for node_id in node_ids:
|
|
69
|
+
name = dbt_adapter.get_node_name_by_id(node_id)
|
|
70
|
+
if name:
|
|
71
|
+
query_candidates.append(name)
|
|
72
|
+
|
|
73
|
+
# Query row count for nodes that are not cached
|
|
74
|
+
with dbt_adapter.connection_named("query"):
|
|
75
|
+
self.connection = dbt_adapter.get_thread_connection()
|
|
76
|
+
completed = 0
|
|
77
|
+
total = len(query_candidates)
|
|
78
|
+
for node in query_candidates:
|
|
79
|
+
self.update_progress(message=f"Query: {node} [{completed}/{total}]", percentage=completed / total)
|
|
80
|
+
|
|
81
|
+
row_count = self._query_row_count(dbt_adapter, node, base=False)
|
|
82
|
+
self.check_cancel()
|
|
83
|
+
result[node] = {
|
|
84
|
+
"curr": row_count,
|
|
85
|
+
}
|
|
86
|
+
completed += 1
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def cancel(self):
|
|
91
|
+
super().cancel()
|
|
92
|
+
if self.connection:
|
|
93
|
+
self.close_connection(self.connection)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class RowCountDiffParams(BaseModel):
|
|
97
|
+
node_names: Optional[list[str]] = None
|
|
98
|
+
node_ids: Optional[list[str]] = None
|
|
99
|
+
select: Optional[str] = None
|
|
100
|
+
exclude: Optional[str] = None
|
|
101
|
+
packages: Optional[list[str]] = None
|
|
102
|
+
view_mode: Optional[Literal["all", "changed_models"]] = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RowCountDiffTask(Task, QueryMixin):
|
|
106
|
+
def __init__(self, params: dict):
|
|
107
|
+
super().__init__()
|
|
108
|
+
self.params = RowCountDiffParams(**params) if params is not None else RowCountDiffParams()
|
|
109
|
+
self.connection = None
|
|
110
|
+
|
|
111
|
+
def _query_row_count(self, dbt_adapter, model_name, base=False):
|
|
112
|
+
node = dbt_adapter.find_node_by_name(model_name, base=base)
|
|
113
|
+
if node is None:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
if node.resource_type != "model" and node.resource_type != "snapshot":
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
if node.config and node.config.materialized not in ["table", "view", "incremental", "snapshot"]:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
relation = dbt_adapter.create_relation(model_name, base=base)
|
|
123
|
+
if relation is None:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
sql_template = r"select count(*) from {{ relation }}"
|
|
127
|
+
sql = dbt_adapter.generate_sql(sql_template, context=dict(relation=relation))
|
|
128
|
+
_, table = dbt_adapter.execute(sql, fetch=True)
|
|
129
|
+
return int(table[0][0]) if table[0][0] is not None else 0
|
|
130
|
+
|
|
131
|
+
def execute_dbt(self):
|
|
132
|
+
result = {}
|
|
133
|
+
|
|
134
|
+
dbt_adapter = default_context().adapter
|
|
135
|
+
|
|
136
|
+
query_candidates = []
|
|
137
|
+
if self.params.node_ids or self.params.node_names:
|
|
138
|
+
for node_id in self.params.node_ids or []:
|
|
139
|
+
name = dbt_adapter.get_node_name_by_id(node_id)
|
|
140
|
+
if name:
|
|
141
|
+
query_candidates.append(name)
|
|
142
|
+
for node in self.params.node_names or []:
|
|
143
|
+
query_candidates.append(node)
|
|
144
|
+
else:
|
|
145
|
+
|
|
146
|
+
def countable(unique_id):
|
|
147
|
+
return unique_id.startswith("model") or unique_id.startswith("snapshot") or unique_id.startswith("seed")
|
|
148
|
+
|
|
149
|
+
node_ids = dbt_adapter.select_nodes(
|
|
150
|
+
select=self.params.select,
|
|
151
|
+
exclude=self.params.exclude,
|
|
152
|
+
packages=self.params.packages,
|
|
153
|
+
view_mode=self.params.view_mode,
|
|
154
|
+
)
|
|
155
|
+
node_ids = list(filter(countable, node_ids))
|
|
156
|
+
for node_id in node_ids:
|
|
157
|
+
name = dbt_adapter.get_node_name_by_id(node_id)
|
|
158
|
+
if name:
|
|
159
|
+
query_candidates.append(name)
|
|
160
|
+
|
|
161
|
+
# Query row count for nodes that are not cached
|
|
162
|
+
with dbt_adapter.connection_named("query"):
|
|
163
|
+
self.connection = dbt_adapter.get_thread_connection()
|
|
164
|
+
completed = 0
|
|
165
|
+
total = len(query_candidates)
|
|
166
|
+
for node in query_candidates:
|
|
167
|
+
self.update_progress(message=f"Diff: {node} [{completed}/{total}]", percentage=completed / total)
|
|
168
|
+
|
|
169
|
+
base_row_count = self._query_row_count(dbt_adapter, node, base=True)
|
|
170
|
+
self.check_cancel()
|
|
171
|
+
curr_row_count = self._query_row_count(dbt_adapter, node, base=False)
|
|
172
|
+
self.check_cancel()
|
|
173
|
+
result[node] = {
|
|
174
|
+
"base": base_row_count,
|
|
175
|
+
"curr": curr_row_count,
|
|
176
|
+
}
|
|
177
|
+
completed += 1
|
|
178
|
+
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
def execute_sqlmesh(self):
|
|
182
|
+
result = {}
|
|
183
|
+
|
|
184
|
+
query_candidates = []
|
|
185
|
+
|
|
186
|
+
for node_id in self.node_ids or []:
|
|
187
|
+
query_candidates.append(node_id)
|
|
188
|
+
for node_name in self.params.node_names or []:
|
|
189
|
+
query_candidates.append(node_name)
|
|
190
|
+
|
|
191
|
+
from recce.adapter.sqlmesh_adapter import SqlmeshAdapter
|
|
192
|
+
|
|
193
|
+
sqlmesh_adapter: SqlmeshAdapter = default_context().adapter
|
|
194
|
+
|
|
195
|
+
for name in query_candidates:
|
|
196
|
+
base_row_count = None
|
|
197
|
+
curr_row_count = None
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
df, _ = sqlmesh_adapter.fetchdf_with_limit(f"select count(*) from {name}", base=True)
|
|
201
|
+
base_row_count = int(df.iloc[0, 0])
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
self.check_cancel()
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
df, _ = sqlmesh_adapter.fetchdf_with_limit(f"select count(*) from {name}", base=False)
|
|
208
|
+
curr_row_count = int(df.iloc[0, 0])
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
self.check_cancel()
|
|
212
|
+
result[name] = {
|
|
213
|
+
"base": base_row_count,
|
|
214
|
+
"curr": curr_row_count,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
def execute(self):
|
|
220
|
+
context = default_context()
|
|
221
|
+
if context.adapter_type == "dbt":
|
|
222
|
+
return self.execute_dbt()
|
|
223
|
+
else:
|
|
224
|
+
return self.execute_sqlmesh()
|
|
225
|
+
|
|
226
|
+
def cancel(self):
|
|
227
|
+
super().cancel()
|
|
228
|
+
if self.connection:
|
|
229
|
+
self.close_connection(self.connection)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RowCountDiffResultDiffer(TaskResultDiffer):
|
|
233
|
+
def _check_result_changed_fn(self, result):
|
|
234
|
+
base = {}
|
|
235
|
+
current = {}
|
|
236
|
+
|
|
237
|
+
for node, row_counts in result.items():
|
|
238
|
+
base[node] = row_counts["base"]
|
|
239
|
+
current[node] = row_counts["curr"]
|
|
240
|
+
|
|
241
|
+
return TaskResultDiffer.diff(base, current)
|
|
242
|
+
|
|
243
|
+
def _get_related_node_ids(self) -> Union[List[str], None]:
|
|
244
|
+
"""
|
|
245
|
+
Get the related node ids.
|
|
246
|
+
Should be implemented by subclass.
|
|
247
|
+
"""
|
|
248
|
+
params = self.run.params
|
|
249
|
+
if params.get("model"):
|
|
250
|
+
return [TaskResultDiffer.get_node_id_by_name(params.get("model"))]
|
|
251
|
+
elif params.get("node_names"):
|
|
252
|
+
names = params.get("node_names", [])
|
|
253
|
+
return [TaskResultDiffer.get_node_id_by_name(name) for name in names]
|
|
254
|
+
elif params.get("node_ids"):
|
|
255
|
+
return params.get("node_ids", [])
|
|
256
|
+
else:
|
|
257
|
+
return TaskResultDiffer.get_node_ids_by_selector(
|
|
258
|
+
select=params.get("select"),
|
|
259
|
+
exclude=params.get("exclude"),
|
|
260
|
+
packages=params.get("packages"),
|
|
261
|
+
view_mode=params.get("view_mode"),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def _get_changed_nodes(self) -> Union[List[str], None]:
|
|
265
|
+
if self.changes:
|
|
266
|
+
# Both affected_root_keys of deepdiff v7 (OrderedSet) and v8 (SetOrdered) are iterable
|
|
267
|
+
# Convert to list directly
|
|
268
|
+
return list(self.changes.affected_root_keys)
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class RowCountDiffCheckValidator(CheckValidator):
|
|
273
|
+
def validate_check(self, check: Check):
|
|
274
|
+
try:
|
|
275
|
+
RowCountDiffParams(**check.params)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
raise ValueError(f"Invalid params: str{e}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import List, Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from recce.models import Check
|
|
6
|
+
from recce.tasks.core import CheckValidator, TaskResultDiffer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SchemaDiffResultDiffer:
|
|
10
|
+
related_node_ids: List[str] = None
|
|
11
|
+
|
|
12
|
+
def __init__(self, check, base_lineage, curr_lineage):
|
|
13
|
+
self.check = check
|
|
14
|
+
self.related_node_ids = self._get_related_node_ids()
|
|
15
|
+
self.changes = self._check_result_changed_fn(base_lineage, curr_lineage)
|
|
16
|
+
self.changed_nodes = self._get_changed_nodes()
|
|
17
|
+
|
|
18
|
+
def _get_related_node_ids(self) -> Union[List[str], None]:
|
|
19
|
+
params = self.check.params
|
|
20
|
+
if params.get("node_id"):
|
|
21
|
+
return params.get("node_id") if isinstance(params.get("node_id"), list) else [params.get("node_id")]
|
|
22
|
+
else:
|
|
23
|
+
return TaskResultDiffer.get_node_ids_by_selector(
|
|
24
|
+
select=params.get("select"),
|
|
25
|
+
exclude=params.get("exclude"),
|
|
26
|
+
packages=params.get("packages"),
|
|
27
|
+
view_mode=params.get("view_mode"),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def _check_result_changed_fn(self, base_lineage, curr_lineage):
|
|
31
|
+
base = {}
|
|
32
|
+
current = {}
|
|
33
|
+
base_nodes = base_lineage.get("nodes", {})
|
|
34
|
+
curr_nodes = curr_lineage.get("nodes", {})
|
|
35
|
+
for node_id in self.related_node_ids:
|
|
36
|
+
node = curr_nodes.get(node_id) or base_nodes.get(node_id)
|
|
37
|
+
if not node:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
node_name = node.get("name")
|
|
41
|
+
base[node_name] = base_nodes.get(node_id, {}).get("columns", {})
|
|
42
|
+
current[node_name] = curr_nodes.get(node_id, {}).get("columns", {})
|
|
43
|
+
|
|
44
|
+
return TaskResultDiffer.diff(base, current)
|
|
45
|
+
|
|
46
|
+
def _get_changed_nodes(self) -> Union[List[str], None]:
|
|
47
|
+
if self.changes:
|
|
48
|
+
# Both affected_root_keys of deepdiff v7 (OrderedSet) and v8 (SetOrdered) are iterable
|
|
49
|
+
# Convert to list directly
|
|
50
|
+
return list(self.changes.affected_root_keys)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SchemaDiffParams(BaseModel):
|
|
55
|
+
node_id: Optional[Union[str, List[str]]] = None
|
|
56
|
+
select: Optional[str] = None
|
|
57
|
+
exclude: Optional[str] = None
|
|
58
|
+
packages: Optional[list[str]] = None
|
|
59
|
+
view_mode: Optional[Literal["all", "changed_models"]] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SchemaDiffCheckValidator(CheckValidator):
|
|
63
|
+
def validate_check(self, check: Check):
|
|
64
|
+
SchemaDiffParams(**check.params)
|
|
65
|
+
SchemaDiffParams(**check.view_options)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from recce.core import default_context
|
|
6
|
+
from recce.models import Check
|
|
7
|
+
from recce.tasks import Task
|
|
8
|
+
from recce.tasks.core import CheckValidator, TaskResultDiffer
|
|
9
|
+
from recce.tasks.query import QueryMixin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TopKDiffParams(BaseModel):
|
|
13
|
+
model: str
|
|
14
|
+
column_name: str
|
|
15
|
+
k: Optional[int] = 10
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TopKDiffTask(Task, QueryMixin):
|
|
19
|
+
def __init__(self, params):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.params = TopKDiffParams(**params)
|
|
22
|
+
self.connection = None
|
|
23
|
+
|
|
24
|
+
def _query_row_count_diff(self, dbt_adapter, base_relation, curr_relation, column):
|
|
25
|
+
"""
|
|
26
|
+
Query the row count of the base and current relations
|
|
27
|
+
|
|
28
|
+
:return: [base_total, base_valids, curr_total, curr_valids]
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
sql_template = r"""
|
|
32
|
+
select count(*), count({{column}}) from {{ base_relation }}
|
|
33
|
+
UNION ALL
|
|
34
|
+
select count(*), count({{column}}) from {{ curr_relation }}
|
|
35
|
+
"""
|
|
36
|
+
sql = dbt_adapter.generate_sql(
|
|
37
|
+
sql_template,
|
|
38
|
+
context=dict(
|
|
39
|
+
base_relation=base_relation,
|
|
40
|
+
curr_relation=curr_relation,
|
|
41
|
+
column=column,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
_, table = dbt_adapter.execute(sql, fetch=True)
|
|
45
|
+
|
|
46
|
+
result = (table[0][0], table[0][1], table[1][0], table[1][1])
|
|
47
|
+
|
|
48
|
+
return (int(v) if v is not None else 0 for v in result)
|
|
49
|
+
|
|
50
|
+
def _query_top_k(self, dbt_adapter, base_relation, curr_relation, column, k):
|
|
51
|
+
sql_template = r"""
|
|
52
|
+
WITH
|
|
53
|
+
BASE_CAT as (
|
|
54
|
+
select
|
|
55
|
+
coalesce(cast({{column}} as {{ dbt.type_string() }}), '__null__') as category,
|
|
56
|
+
count(*) as c
|
|
57
|
+
from {{base_relation}}
|
|
58
|
+
{% if not include_null %}
|
|
59
|
+
where {{column}} is not null
|
|
60
|
+
{% endif %}
|
|
61
|
+
group by 1
|
|
62
|
+
),
|
|
63
|
+
CURR_CAT as (
|
|
64
|
+
select
|
|
65
|
+
coalesce(cast({{column}} as {{ dbt.type_string() }}), '__null__') as category,
|
|
66
|
+
count(*) as c
|
|
67
|
+
from {{curr_relation}}
|
|
68
|
+
{% if not include_null %}
|
|
69
|
+
where {{column}} is not null
|
|
70
|
+
{% endif %}
|
|
71
|
+
group by 1
|
|
72
|
+
)
|
|
73
|
+
select
|
|
74
|
+
coalesce(CURR_CAT.category, BASE_CAT.category) as category,
|
|
75
|
+
coalesce(BASE_CAT.c, 0) as base_count,
|
|
76
|
+
coalesce(CURR_CAT.c, 0) as curr_count
|
|
77
|
+
from CURR_CAT
|
|
78
|
+
full outer join BASE_CAT
|
|
79
|
+
on CURR_CAT.category = BASE_CAT.category
|
|
80
|
+
order by curr_count desc, base_count desc
|
|
81
|
+
limit {{k}}
|
|
82
|
+
"""
|
|
83
|
+
sql = dbt_adapter.generate_sql(
|
|
84
|
+
sql_template,
|
|
85
|
+
context=dict(
|
|
86
|
+
base_relation=base_relation,
|
|
87
|
+
curr_relation=curr_relation,
|
|
88
|
+
column=column,
|
|
89
|
+
k=k,
|
|
90
|
+
include_null=False,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
_, table = dbt_adapter.execute(sql, fetch=True)
|
|
94
|
+
|
|
95
|
+
categories = []
|
|
96
|
+
base_counts = []
|
|
97
|
+
curr_counts = []
|
|
98
|
+
|
|
99
|
+
for row in table:
|
|
100
|
+
categories.append(row[0] if row[0] != "__null__" else None)
|
|
101
|
+
base_counts.append(int(row[1] if row[1] else 0))
|
|
102
|
+
curr_counts.append(int(row[2] if row[2] else 0))
|
|
103
|
+
|
|
104
|
+
return categories, base_counts, curr_counts
|
|
105
|
+
|
|
106
|
+
def execute(self):
|
|
107
|
+
|
|
108
|
+
from recce.adapter.dbt_adapter import DbtAdapter
|
|
109
|
+
|
|
110
|
+
dbt_adapter: DbtAdapter = default_context().adapter
|
|
111
|
+
|
|
112
|
+
with dbt_adapter.connection_named("query"):
|
|
113
|
+
self.connection = dbt_adapter.get_thread_connection()
|
|
114
|
+
model = self.params.model
|
|
115
|
+
column = self.params.column_name
|
|
116
|
+
k = self.params.k or 10
|
|
117
|
+
|
|
118
|
+
base_relation = dbt_adapter.create_relation(model, base=True)
|
|
119
|
+
if base_relation is None:
|
|
120
|
+
raise ValueError(f"Model '{model}' not found in the manifest")
|
|
121
|
+
|
|
122
|
+
curr_relation = dbt_adapter.create_relation(model, base=False)
|
|
123
|
+
if curr_relation is None:
|
|
124
|
+
raise ValueError(f"Model '{model}' not found in the manifest")
|
|
125
|
+
|
|
126
|
+
self.check_cancel()
|
|
127
|
+
categories, base_counts, curr_counts = self._query_top_k(
|
|
128
|
+
dbt_adapter, base_relation, curr_relation, column, k
|
|
129
|
+
)
|
|
130
|
+
self.check_cancel()
|
|
131
|
+
|
|
132
|
+
base_total, base_valids, curr_total, curr_valids = self._query_row_count_diff(
|
|
133
|
+
dbt_adapter, base_relation, curr_relation, column
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
result = {
|
|
137
|
+
"base": {
|
|
138
|
+
"values": categories,
|
|
139
|
+
"counts": base_counts,
|
|
140
|
+
"valids": base_valids,
|
|
141
|
+
"total": base_total,
|
|
142
|
+
},
|
|
143
|
+
"current": {
|
|
144
|
+
"values": categories,
|
|
145
|
+
"counts": curr_counts,
|
|
146
|
+
"valids": curr_valids,
|
|
147
|
+
"total": curr_total,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
def cancel(self):
|
|
153
|
+
super().cancel()
|
|
154
|
+
if self.connection:
|
|
155
|
+
self.close_connection(self.connection)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TopKDiffTaskResultDiffer(TaskResultDiffer):
|
|
159
|
+
def _check_result_changed_fn(self, result):
|
|
160
|
+
base = result.get("base")
|
|
161
|
+
current = result.get("current")
|
|
162
|
+
|
|
163
|
+
return TaskResultDiffer.diff(base, current)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TopKDiffCheckValidator(CheckValidator):
|
|
167
|
+
|
|
168
|
+
def validate_check(self, check: Check):
|
|
169
|
+
try:
|
|
170
|
+
TopKDiffParams(**check.params)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
raise ValueError(f"Invalid check: {str(e)}")
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Utility functions for task operations."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from recce.tasks.dataframe import DataFrame
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def strip_identifier_quotes(identifier: str) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Strip SQL identifier quotes from a column name.
|
|
11
|
+
|
|
12
|
+
Different databases use different quoting styles:
|
|
13
|
+
- Double quotes: "column" (PostgreSQL, Snowflake, etc.)
|
|
14
|
+
- Backticks: `column` (MySQL, BigQuery)
|
|
15
|
+
- Square brackets: [column] (SQL Server)
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
identifier: Column name that may be quoted
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Column name with quotes stripped
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> strip_identifier_quotes('"myColumn"')
|
|
25
|
+
'myColumn'
|
|
26
|
+
>>> strip_identifier_quotes('`my_column`')
|
|
27
|
+
'my_column'
|
|
28
|
+
>>> strip_identifier_quotes('[Column Name]')
|
|
29
|
+
'Column Name'
|
|
30
|
+
>>> strip_identifier_quotes('regular_column')
|
|
31
|
+
'regular_column'
|
|
32
|
+
"""
|
|
33
|
+
if not identifier or len(identifier) < 2:
|
|
34
|
+
return identifier
|
|
35
|
+
|
|
36
|
+
# Check for double quotes
|
|
37
|
+
if identifier.startswith('"') and identifier.endswith('"'):
|
|
38
|
+
return identifier[1:-1]
|
|
39
|
+
|
|
40
|
+
# Check for backticks
|
|
41
|
+
if identifier.startswith("`") and identifier.endswith("`"):
|
|
42
|
+
return identifier[1:-1]
|
|
43
|
+
|
|
44
|
+
# Check for square brackets
|
|
45
|
+
if identifier.startswith("[") and identifier.endswith("]"):
|
|
46
|
+
return identifier[1:-1]
|
|
47
|
+
|
|
48
|
+
return identifier
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def normalize_keys_to_columns(
|
|
52
|
+
keys: Optional[List[str]],
|
|
53
|
+
column_keys: List[str],
|
|
54
|
+
) -> Optional[List[str]]:
|
|
55
|
+
"""
|
|
56
|
+
Normalize user-provided keys to match actual column keys from the warehouse.
|
|
57
|
+
|
|
58
|
+
Different warehouses return column names in different cases:
|
|
59
|
+
- Snowflake: UPPERCASE (unless quoted)
|
|
60
|
+
- PostgreSQL/Redshift: lowercase (unless quoted)
|
|
61
|
+
- BigQuery: preserves original case
|
|
62
|
+
|
|
63
|
+
This function first attempts an exact match (for quoted columns that preserve
|
|
64
|
+
case), then falls back to case-insensitive matching to align user input
|
|
65
|
+
with the actual column keys returned by the warehouse.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
keys: User-provided keys (e.g., primary_keys from params)
|
|
69
|
+
column_keys: Actual column keys from the query result
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of keys normalized to match column_keys casing,
|
|
73
|
+
or None if keys is None.
|
|
74
|
+
If a key doesn't match any column, it's preserved as-is.
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
>>> normalize_keys_to_columns(["payment_id"], ["PAYMENT_ID", "ORDER_ID"])
|
|
78
|
+
["PAYMENT_ID"]
|
|
79
|
+
|
|
80
|
+
>>> normalize_keys_to_columns(["ID", "NAME"], ["id", "name", "value"])
|
|
81
|
+
["id", "name"]
|
|
82
|
+
|
|
83
|
+
>>> normalize_keys_to_columns(["preCommitID"], ["preCommitID", "order_id"])
|
|
84
|
+
["preCommitID"] # Exact match preserved for quoted columns
|
|
85
|
+
|
|
86
|
+
>>> normalize_keys_to_columns(['"customerID"'], ["customerID", "amount"])
|
|
87
|
+
["customerID"] # Quotes stripped, then matched
|
|
88
|
+
|
|
89
|
+
>>> normalize_keys_to_columns(['`my_column`'], ["MY_COLUMN"])
|
|
90
|
+
["MY_COLUMN"] # Backticks stripped, then case-insensitive match
|
|
91
|
+
"""
|
|
92
|
+
if keys is None:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
# Strip quotes from all keys first - quotes are for SQL execution,
|
|
96
|
+
# but the frontend should receive unquoted column names
|
|
97
|
+
unquoted_keys = [strip_identifier_quotes(key) for key in keys]
|
|
98
|
+
|
|
99
|
+
if not column_keys:
|
|
100
|
+
return unquoted_keys
|
|
101
|
+
|
|
102
|
+
# Build both exact and case-insensitive lookup maps
|
|
103
|
+
exact_key_set = set(column_keys)
|
|
104
|
+
case_insensitive_map = {col.lower(): col for col in column_keys}
|
|
105
|
+
|
|
106
|
+
normalized = []
|
|
107
|
+
for key in unquoted_keys:
|
|
108
|
+
if key in exact_key_set:
|
|
109
|
+
# Exact match - use as-is (handles quoted columns that preserved case)
|
|
110
|
+
normalized.append(key)
|
|
111
|
+
else:
|
|
112
|
+
# Case-insensitive fallback
|
|
113
|
+
actual_key = case_insensitive_map.get(key.lower())
|
|
114
|
+
normalized.append(actual_key if actual_key is not None else key)
|
|
115
|
+
|
|
116
|
+
return normalized
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def normalize_boolean_flag_columns(df: "DataFrame") -> "DataFrame":
|
|
120
|
+
"""
|
|
121
|
+
Normalize boolean flag columns (in_a, in_b) to lowercase for cross-warehouse consistency.
|
|
122
|
+
|
|
123
|
+
Different warehouses return column names in different cases:
|
|
124
|
+
- Snowflake: IN_A, IN_B (UPPERCASE)
|
|
125
|
+
- PostgreSQL/Redshift: in_a, in_b (lowercase)
|
|
126
|
+
- BigQuery: preserves original case
|
|
127
|
+
|
|
128
|
+
This function ensures these columns are always lowercase in the DataFrame
|
|
129
|
+
sent to the frontend, enabling exact string matching.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
df: DataFrame that may contain IN_A/IN_B columns
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
DataFrame with in_a/in_b columns normalized to lowercase
|
|
136
|
+
"""
|
|
137
|
+
from .dataframe import DataFrame, DataFrameColumn
|
|
138
|
+
|
|
139
|
+
normalized_columns = []
|
|
140
|
+
for col in df.columns:
|
|
141
|
+
key_upper = col.key.upper() if col.key else ""
|
|
142
|
+
if key_upper in ("IN_A", "IN_B"):
|
|
143
|
+
normalized_columns.append(DataFrameColumn(key=col.key.lower(), name=col.name.lower(), type=col.type))
|
|
144
|
+
else:
|
|
145
|
+
normalized_columns.append(col)
|
|
146
|
+
|
|
147
|
+
return DataFrame(columns=normalized_columns, data=df.data, limit=df.limit, more=df.more)
|