furniture-3d-system-Wu/furniture-3d-system/scripts/sql/check-external-readonly-contract.sql

561 lines
20 KiB
SQL

-- External read-only contract health check.
-- Run after applying the external schema migration.
-- This script is read-only and only reports contract or data risks.
WITH expected_views(view_name) AS (
VALUES
('materials_v1'),
('asset_variants_v1'),
('product_skus_v1'),
('product_assets_v1'),
('product_bom_v1'),
('intake_products_v1'),
('intake_products_ready_v1'),
('intake_sku_items_v1'),
('intake_sku_items_ready_v1')
)
SELECT
'missing_external_view' AS check_name,
expected_views.view_name
FROM expected_views
LEFT JOIN information_schema.views existing_views
ON existing_views.table_schema = 'external'
AND existing_views.table_name = expected_views.view_name
WHERE existing_views.table_name IS NULL
ORDER BY expected_views.view_name;
WITH expected_columns(view_name, column_name) AS (
VALUES
('materials_v1', 'material_id'),
('materials_v1', 'material_code'),
('materials_v1', 'material_name'),
('materials_v1', 'category_name'),
('materials_v1', 'thumbnail_url'),
('materials_v1', 'texture_url'),
('materials_v1', 'normal_map_url'),
('materials_v1', 'base_color'),
('materials_v1', 'metalness'),
('materials_v1', 'roughness'),
('materials_v1', 'opacity'),
('materials_v1', 'record_status'),
('asset_variants_v1', 'asset_variant_id'),
('asset_variants_v1', 'spec_code'),
('asset_variants_v1', 'spec_name'),
('asset_variants_v1', 'asset_type'),
('asset_variants_v1', 'asset_status'),
('asset_variants_v1', 'asset_family_id'),
('asset_variants_v1', 'asset_family_code'),
('asset_variants_v1', 'asset_family_name'),
('asset_variants_v1', 'asset_series_id'),
('asset_variants_v1', 'asset_series_code'),
('asset_variants_v1', 'asset_series_name'),
('asset_variants_v1', 'product_category_id'),
('asset_variants_v1', 'product_category_name'),
('asset_variants_v1', 'size_label'),
('asset_variants_v1', 'dimensions'),
('asset_variants_v1', 'model_url'),
('asset_variants_v1', 'thumbnail_url'),
('asset_variants_v1', 'barcode'),
('asset_variants_v1', 'supplier_name'),
('asset_variants_v1', 'supplier_code'),
('asset_variants_v1', 'version_no'),
('asset_variants_v1', 'substrate_id'),
('asset_variants_v1', 'substrate_name'),
('asset_variants_v1', 'finish_id'),
('asset_variants_v1', 'finish_code'),
('asset_variants_v1', 'finish_name'),
('asset_variants_v1', 'material_label'),
('asset_variants_v1', 'weight_kg'),
('asset_variants_v1', 'cost_price'),
('asset_variants_v1', 'estimated_cost_price'),
('asset_variants_v1', 'created_at'),
('asset_variants_v1', 'updated_at'),
('product_skus_v1', 'product_id'),
('product_skus_v1', 'product_model_number'),
('product_skus_v1', 'product_business_key'),
('product_skus_v1', 'product_name'),
('product_skus_v1', 'product_description'),
('product_skus_v1', 'product_category_id'),
('product_skus_v1', 'product_category_name'),
('product_skus_v1', 'product_thumbnail_url'),
('product_skus_v1', 'product_model_url'),
('product_skus_v1', 'sku_id'),
('product_skus_v1', 'sku_code'),
('product_skus_v1', 'sku_business_key'),
('product_skus_v1', 'sku_name'),
('product_skus_v1', 'price'),
('product_skus_v1', 'cost_price'),
('product_skus_v1', 'stock'),
('product_skus_v1', 'is_active'),
('product_skus_v1', 'record_status'),
('product_skus_v1', 'model_url'),
('product_skus_v1', 'image_url'),
('product_skus_v1', 'image_urls'),
('product_skus_v1', 'source_pre_sku_id'),
('product_skus_v1', 'source_asset_variant_id'),
('product_skus_v1', 'source_asset_variant_ids'),
('product_skus_v1', 'asset_family_code'),
('product_skus_v1', 'asset_family_name'),
('product_skus_v1', 'spec_code'),
('product_skus_v1', 'spec_name'),
('product_skus_v1', 'barcode'),
('product_skus_v1', 'size_json'),
('product_skus_v1', 'color'),
('product_skus_v1', 'material'),
('product_skus_v1', 'source_status'),
('product_skus_v1', 'synced_at'),
('product_skus_v1', 'warehouse_location_id'),
('product_skus_v1', 'warehouse_location_code'),
('product_skus_v1', 'warehouse_location_name'),
('product_skus_v1', 'created_at'),
('product_skus_v1', 'updated_at'),
('product_assets_v1', 'asset_type'),
('product_assets_v1', 'product_id'),
('product_assets_v1', 'sku_id'),
('product_assets_v1', 'asset_url'),
('product_assets_v1', 'file_name'),
('product_assets_v1', 'mime_type'),
('product_assets_v1', 'sort_order'),
('product_assets_v1', 'product_business_key'),
('product_assets_v1', 'sku_business_key'),
('product_bom_v1', 'item_type'),
('product_bom_v1', 'product_id'),
('product_bom_v1', 'sku_id'),
('product_bom_v1', 'item_id'),
('product_bom_v1', 'item_code'),
('product_bom_v1', 'item_name'),
('product_bom_v1', 'quantity'),
('product_bom_v1', 'unit'),
('product_bom_v1', 'material_id'),
('product_bom_v1', 'material_code'),
('product_bom_v1', 'material_name'),
('product_bom_v1', 'dimensions'),
('product_bom_v1', 'unit_cost'),
('product_bom_v1', 'total_cost'),
('product_bom_v1', 'created_at'),
('product_bom_v1', 'updated_at'),
('product_bom_v1', 'product_business_key'),
('product_bom_v1', 'sku_business_key'),
('intake_products_v1', 'intake_product_id'),
('intake_products_v1', 'spu'),
('intake_products_v1', 'spu_base'),
('intake_products_v1', 'brand'),
('intake_products_v1', 'product_business_key'),
('intake_products_v1', 'product_name'),
('intake_products_v1', 'product_image_url'),
('intake_products_v1', 'product_category'),
('intake_products_v1', 'sales_category'),
('intake_products_v1', 'product_year'),
('intake_products_v1', 'product_status'),
('intake_products_v1', 'designer'),
('intake_products_v1', 'operator'),
('intake_products_v1', 'purchaser'),
('intake_products_v1', 'product_planning'),
('intake_products_v1', 'visual_planning'),
('intake_products_v1', 'structure_designer'),
('intake_products_v1', 'purchase_owner'),
('intake_products_v1', 'product_development_kind'),
('intake_products_v1', 'goods_type'),
('intake_products_v1', 'material_type'),
('intake_products_v1', 'production_type'),
('intake_products_v1', 'created_at'),
('intake_products_v1', 'updated_at'),
('intake_products_ready_v1', 'intake_product_id'),
('intake_products_ready_v1', 'spu'),
('intake_products_ready_v1', 'spu_base'),
('intake_products_ready_v1', 'brand'),
('intake_products_ready_v1', 'product_business_key'),
('intake_products_ready_v1', 'product_name'),
('intake_products_ready_v1', 'product_image_url'),
('intake_products_ready_v1', 'product_category'),
('intake_products_ready_v1', 'sales_category'),
('intake_products_ready_v1', 'product_year'),
('intake_products_ready_v1', 'product_status'),
('intake_products_ready_v1', 'designer'),
('intake_products_ready_v1', 'operator'),
('intake_products_ready_v1', 'purchaser'),
('intake_products_ready_v1', 'product_planning'),
('intake_products_ready_v1', 'visual_planning'),
('intake_products_ready_v1', 'structure_designer'),
('intake_products_ready_v1', 'purchase_owner'),
('intake_products_ready_v1', 'product_development_kind'),
('intake_products_ready_v1', 'goods_type'),
('intake_products_ready_v1', 'material_type'),
('intake_products_ready_v1', 'production_type'),
('intake_products_ready_v1', 'created_at'),
('intake_products_ready_v1', 'updated_at'),
('intake_sku_items_v1', 'intake_product_id'),
('intake_sku_items_v1', 'spu'),
('intake_sku_items_v1', 'brand'),
('intake_sku_items_v1', 'product_business_key'),
('intake_sku_items_v1', 'intake_sku_item_id'),
('intake_sku_items_v1', 'sku_business_key'),
('intake_sku_items_v1', 'sku_sequence'),
('intake_sku_items_v1', 'batch_no'),
('intake_sku_items_v1', 'image_url'),
('intake_sku_items_v1', 'image_name'),
('intake_sku_items_v1', 'model'),
('intake_sku_items_v1', 'sku_code'),
('intake_sku_items_v1', 'spec_name'),
('intake_sku_items_v1', 'barcode'),
('intake_sku_items_v1', 'size'),
('intake_sku_items_v1', 'color'),
('intake_sku_items_v1', 'material'),
('intake_sku_items_v1', 'version_no'),
('intake_sku_items_v1', 'attribute_code'),
('intake_sku_items_v1', 'drawing_id'),
('intake_sku_items_v1', 'install_method'),
('intake_sku_items_v1', 'execute_standard'),
('intake_sku_items_v1', 'manufacturer'),
('intake_sku_items_v1', 'manufacturer_code'),
('intake_sku_items_v1', 'sales_status'),
('intake_sku_items_v1', 'model_3d_url'),
('intake_sku_items_v1', 'source'),
('intake_sku_items_v1', 'source_table'),
('intake_sku_items_v1', 'source_sku_id'),
('intake_sku_items_v1', 'missing_required_fields'),
('intake_sku_items_v1', 'data_quality_status'),
('intake_sku_items_v1', 'content_locked'),
('intake_sku_items_v1', 'sort_order'),
('intake_sku_items_ready_v1', 'intake_product_id'),
('intake_sku_items_ready_v1', 'spu'),
('intake_sku_items_ready_v1', 'brand'),
('intake_sku_items_ready_v1', 'product_business_key'),
('intake_sku_items_ready_v1', 'intake_sku_item_id'),
('intake_sku_items_ready_v1', 'sku_business_key'),
('intake_sku_items_ready_v1', 'sku_sequence'),
('intake_sku_items_ready_v1', 'batch_no'),
('intake_sku_items_ready_v1', 'image_url'),
('intake_sku_items_ready_v1', 'image_name'),
('intake_sku_items_ready_v1', 'model'),
('intake_sku_items_ready_v1', 'sku_code'),
('intake_sku_items_ready_v1', 'spec_name'),
('intake_sku_items_ready_v1', 'barcode'),
('intake_sku_items_ready_v1', 'size'),
('intake_sku_items_ready_v1', 'color'),
('intake_sku_items_ready_v1', 'material'),
('intake_sku_items_ready_v1', 'version_no'),
('intake_sku_items_ready_v1', 'attribute_code'),
('intake_sku_items_ready_v1', 'drawing_id'),
('intake_sku_items_ready_v1', 'install_method'),
('intake_sku_items_ready_v1', 'execute_standard'),
('intake_sku_items_ready_v1', 'manufacturer'),
('intake_sku_items_ready_v1', 'manufacturer_code'),
('intake_sku_items_ready_v1', 'sales_status'),
('intake_sku_items_ready_v1', 'model_3d_url'),
('intake_sku_items_ready_v1', 'source'),
('intake_sku_items_ready_v1', 'source_table'),
('intake_sku_items_ready_v1', 'source_sku_id'),
('intake_sku_items_ready_v1', 'missing_required_fields'),
('intake_sku_items_ready_v1', 'data_quality_status'),
('intake_sku_items_ready_v1', 'content_locked'),
('intake_sku_items_ready_v1', 'sort_order')
)
SELECT
'missing_external_column' AS check_name,
expected_columns.view_name,
expected_columns.column_name
FROM expected_columns
LEFT JOIN information_schema.columns existing_columns
ON existing_columns.table_schema = 'external'
AND existing_columns.table_name = expected_columns.view_name
AND existing_columns.column_name = expected_columns.column_name
WHERE existing_columns.column_name IS NULL
ORDER BY expected_columns.view_name, expected_columns.column_name;
SELECT
'external_contract_private_column_risk' AS check_name,
table_name AS view_name,
column_name
FROM information_schema.columns
WHERE table_schema = 'external'
AND column_name ~* '(payload|attributes|business_data|visibility|department|team|storage_key|file_path|password|token|secret)'
ORDER BY table_name, column_name;
SELECT
'external_view_json_source_dependency_risk' AS check_name,
viewname AS view_name,
CASE
WHEN pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"payload\"' THEN 'payload'
WHEN pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"attributes\"' THEN 'attributes'
WHEN pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"businessData\"' THEN 'businessData'
WHEN pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"displaySummary\"' THEN 'displaySummary'
WHEN pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"familySelection\"' THEN 'familySelection'
ELSE 'json_operator'
END AS dependency_hint
FROM pg_views
WHERE schemaname = 'external'
AND (
pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '\"(payload|attributes|businessData|displaySummary|familySelection)\"'
OR pg_get_viewdef(format('%I.%I', schemaname, viewname)::regclass) ~ '(->>|->|jsonb_array_elements)'
)
ORDER BY viewname, dependency_hint;
SELECT
'external_reader_public_schema_grant_risk' AS check_name,
grantee,
table_schema,
table_name,
privilege_type
FROM information_schema.table_privileges
WHERE grantee = 'external_reader'
AND table_schema = 'public'
ORDER BY table_name, privilege_type;
SELECT
'external_reader_non_select_grant_risk' AS check_name,
grantee,
table_schema,
table_name,
privilege_type
FROM information_schema.table_privileges
WHERE grantee = 'external_reader'
AND table_schema = 'external'
AND privilege_type <> 'SELECT'
ORDER BY table_name, privilege_type;
SELECT
'external_view_row_count' AS check_name,
'materials_v1' AS view_name,
COUNT(*)::integer AS row_count
FROM external.materials_v1
UNION ALL
SELECT
'external_view_row_count',
'asset_variants_v1',
COUNT(*)::integer
FROM external.asset_variants_v1
UNION ALL
SELECT
'external_view_row_count',
'product_skus_v1',
COUNT(*)::integer
FROM external.product_skus_v1
UNION ALL
SELECT
'external_view_row_count',
'product_assets_v1',
COUNT(*)::integer
FROM external.product_assets_v1
UNION ALL
SELECT
'external_view_row_count',
'product_bom_v1',
COUNT(*)::integer
FROM external.product_bom_v1
UNION ALL
SELECT
'external_view_row_count',
'intake_products_v1',
COUNT(*)::integer
FROM external.intake_products_v1
UNION ALL
SELECT
'external_view_row_count',
'intake_products_ready_v1',
COUNT(*)::integer
FROM external.intake_products_ready_v1
UNION ALL
SELECT
'external_view_row_count',
'intake_sku_items_v1',
COUNT(*)::integer
FROM external.intake_sku_items_v1
UNION ALL
SELECT
'external_view_row_count',
'intake_sku_items_ready_v1',
COUNT(*)::integer
FROM external.intake_sku_items_ready_v1;
SELECT
'product_skus_blank_key_fields' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(sku_code, '') IS NULL) AS blank_sku_code_count,
COUNT(*) FILTER (WHERE NULLIF(product_model_number, '') IS NULL) AS blank_product_model_number_count,
COUNT(*) FILTER (WHERE NULLIF(sku_name, '') IS NULL) AS blank_sku_name_count,
COUNT(*) FILTER (WHERE NULLIF(product_business_key, '') IS NULL) AS blank_product_business_key_count,
COUNT(*) FILTER (WHERE NULLIF(sku_business_key, '') IS NULL) AS blank_sku_business_key_count
FROM external.product_skus_v1;
SELECT
'asset_variants_blank_key_fields' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(spec_code, '') IS NULL) AS blank_spec_code_count,
COUNT(*) FILTER (WHERE NULLIF(spec_name, '') IS NULL) AS blank_spec_name_count,
COUNT(*) FILTER (WHERE NULLIF(asset_family_code, '') IS NULL) AS blank_asset_family_code_count
FROM external.asset_variants_v1;
SELECT
'materials_blank_key_fields' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(material_code, '') IS NULL) AS blank_material_code_count,
COUNT(*) FILTER (WHERE NULLIF(material_name, '') IS NULL) AS blank_material_name_count
FROM external.materials_v1;
SELECT
'product_bom_blank_key_fields' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(product_business_key, '') IS NULL) AS blank_product_business_key_count,
COUNT(*) FILTER (WHERE NULLIF(item_code, '') IS NULL) AS blank_item_code_count,
COUNT(*) FILTER (WHERE NULLIF(item_name, '') IS NULL) AS blank_item_name_count,
COUNT(*) FILTER (
WHERE item_type = 'PANEL'
AND NULLIF(material_code, '') IS NULL
) AS blank_panel_material_code_count
FROM external.product_bom_v1;
SELECT
'intake_sku_items_blank_key_fields' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(sku_code, '') IS NULL) AS blank_sku_code_count,
COUNT(*) FILTER (WHERE NULLIF(spec_name, '') IS NULL) AS blank_spec_name_count,
COUNT(*) FILTER (WHERE NULLIF(model, '') IS NULL) AS blank_model_count
FROM external.intake_sku_items_v1;
SELECT
'intake_sku_items_quality_status_count' AS check_name,
data_quality_status,
COUNT(*)::integer AS row_count
FROM external.intake_sku_items_v1
GROUP BY data_quality_status
ORDER BY data_quality_status;
SELECT
'intake_sku_items_ready_quality_violation' AS check_name,
COUNT(*)::integer AS row_count
FROM external.intake_sku_items_ready_v1
WHERE data_quality_status <> 'READY';
SELECT
'intake_ready_business_key_missing_risk' AS check_name,
(
SELECT COUNT(*)::integer
FROM external.intake_products_ready_v1
WHERE NULLIF(product_business_key, '') IS NULL
) AS missing_product_business_key_count,
(
SELECT COUNT(*)::integer
FROM external.intake_sku_items_ready_v1
WHERE NULLIF(product_business_key, '') IS NULL
OR NULLIF(sku_business_key, '') IS NULL
) AS missing_sku_business_key_count;
SELECT
'product_sku_business_key_duplicate_risk' AS check_name,
sku_business_key,
COUNT(*)::integer AS duplicate_count
FROM external.product_skus_v1
WHERE NULLIF(sku_business_key, '') IS NOT NULL
GROUP BY sku_business_key
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, sku_business_key
LIMIT 20;
SELECT
'intake_product_business_key_duplicate_risk' AS check_name,
product_business_key,
COUNT(*)::integer AS duplicate_count
FROM external.intake_products_ready_v1
WHERE NULLIF(product_business_key, '') IS NOT NULL
GROUP BY product_business_key
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, product_business_key
LIMIT 20;
SELECT
'intake_sku_business_key_duplicate_risk' AS check_name,
sku_business_key,
COUNT(*)::integer AS duplicate_count
FROM external.intake_sku_items_ready_v1
WHERE NULLIF(sku_business_key, '') IS NOT NULL
GROUP BY sku_business_key
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, sku_business_key
LIMIT 20;
SELECT
'product_assets_data_url_risk' AS check_name,
asset_type,
COUNT(*) AS data_url_count
FROM external.product_assets_v1
WHERE asset_url ~* '^data:'
GROUP BY asset_type
ORDER BY asset_type;
SELECT
'product_assets_business_key_missing_risk' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(product_business_key, '') IS NULL) AS missing_product_business_key_count,
COUNT(*) FILTER (WHERE NULLIF(sku_business_key, '') IS NULL) AS missing_sku_business_key_count
FROM external.product_assets_v1;
SELECT
'product_bom_business_key_missing_risk' AS check_name,
COUNT(*) FILTER (WHERE NULLIF(product_business_key, '') IS NULL) AS missing_product_business_key_count,
COUNT(*) FILTER (
WHERE sku_id IS NOT NULL
AND NULLIF(sku_business_key, '') IS NULL
) AS missing_sku_business_key_count
FROM external.product_bom_v1;
SELECT
'duplicate_product_sku_code_risk' AS check_name,
sku_code,
COUNT(*)::integer AS duplicate_count
FROM external.product_skus_v1
WHERE NULLIF(sku_code, '') IS NOT NULL
GROUP BY sku_code
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, sku_code
LIMIT 20;
SELECT
'duplicate_asset_variant_code_risk' AS check_name,
spec_code,
COUNT(*)::integer AS duplicate_count
FROM external.asset_variants_v1
WHERE NULLIF(spec_code, '') IS NOT NULL
GROUP BY spec_code
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, spec_code
LIMIT 20;
SELECT
'duplicate_material_code_risk' AS check_name,
material_code,
COUNT(*)::integer AS duplicate_count
FROM external.materials_v1
WHERE NULLIF(material_code, '') IS NOT NULL
GROUP BY material_code
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, material_code
LIMIT 20;
SELECT
'duplicate_product_barcode_risk' AS check_name,
barcode,
COUNT(*)::integer AS duplicate_count
FROM external.product_skus_v1
WHERE NULLIF(barcode, '') IS NOT NULL
GROUP BY barcode
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, barcode
LIMIT 20;
SELECT
'duplicate_intake_sku_code_risk' AS check_name,
sku_code,
COUNT(*)::integer AS duplicate_count
FROM external.intake_sku_items_v1
WHERE NULLIF(sku_code, '') IS NOT NULL
GROUP BY sku_code
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, sku_code
LIMIT 20;
SELECT
'duplicate_intake_barcode_risk' AS check_name,
barcode,
COUNT(*)::integer AS duplicate_count
FROM external.intake_sku_items_v1
WHERE NULLIF(barcode, '') IS NOT NULL
GROUP BY barcode
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, barcode
LIMIT 20;