diff --git a/.github/workflows/cypress-integration-tests-mysql.yml b/.github/workflows/cypress-integration-tests-mysql.yml
index 53628e155634..4932f5a2a8a7 100644
--- a/.github/workflows/cypress-integration-tests-mysql.yml
+++ b/.github/workflows/cypress-integration-tests-mysql.yml
@@ -15,6 +15,7 @@
name: MySQL Cypress Integration Tests
on:
+ workflow_dispatch:
push:
branches:
- main
diff --git a/.github/workflows/cypress-integration-tests-postgresql.yml b/.github/workflows/cypress-integration-tests-postgresql.yml
index 47cd833276bb..56ecedafa43c 100644
--- a/.github/workflows/cypress-integration-tests-postgresql.yml
+++ b/.github/workflows/cypress-integration-tests-postgresql.yml
@@ -15,6 +15,7 @@
name: PostgreSQL Cypress Integration Tests
on:
+ workflow_dispatch:
push:
branches:
- main
diff --git a/.github/workflows/docker-openmetadata-db.yml b/.github/workflows/docker-openmetadata-db.yml
index f89df6ede88d..2abfedde3967 100644
--- a/.github/workflows/docker-openmetadata-db.yml
+++ b/.github/workflows/docker-openmetadata-db.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Check trigger type
if: ${{ env.input == '' }}
- run: echo "input=1.2.0" >> $GITHUB_ENV
+ run: echo "input=1.1.5" >> $GITHUB_ENV
- name: Check out the Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/docker-openmetadata-ingestion-base.yml b/.github/workflows/docker-openmetadata-ingestion-base.yml
index 840e3ecdb571..eea6906ee09b 100644
--- a/.github/workflows/docker-openmetadata-ingestion-base.yml
+++ b/.github/workflows/docker-openmetadata-ingestion-base.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Check trigger type
if: ${{ env.input == '' }}
- run: echo "input=1.2.0" >> $GITHUB_ENV
+ run: echo "input=1.1.5" >> $GITHUB_ENV
- name: Check out the Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/docker-openmetadata-ingestion.yml b/.github/workflows/docker-openmetadata-ingestion.yml
index 9278ab0728fb..a9abe5883ec4 100644
--- a/.github/workflows/docker-openmetadata-ingestion.yml
+++ b/.github/workflows/docker-openmetadata-ingestion.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Check trigger type
if: ${{ env.input == '' }}
- run: echo "input=1.2.0" >> $GITHUB_ENV
+ run: echo "input=1.1.5" >> $GITHUB_ENV
- name: Check out the Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/docker-openmetadata-postgres.yml b/.github/workflows/docker-openmetadata-postgres.yml
index 6505f27c7ff9..32ec33645f41 100644
--- a/.github/workflows/docker-openmetadata-postgres.yml
+++ b/.github/workflows/docker-openmetadata-postgres.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Check trigger type
if: ${{ env.input == '' }}
- run: echo "input=1.2.0" >> $GITHUB_ENV
+ run: echo "input=1.1.5" >> $GITHUB_ENV
- name: Check out the Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/docker-openmetadata-server.yml b/.github/workflows/docker-openmetadata-server.yml
index 7c89d253176c..34c2e9efa3f3 100644
--- a/.github/workflows/docker-openmetadata-server.yml
+++ b/.github/workflows/docker-openmetadata-server.yml
@@ -63,7 +63,7 @@ jobs:
steps:
- name: Check trigger type
id: check_trigger
- run: echo "DOCKER_RELEASE_TAG=1.2.0" >> $GITHUB_OUTPUT
+ run: echo "DOCKER_RELEASE_TAG=1.1.5" >> $GITHUB_OUTPUT
- name: Download application from Artifiact
uses: actions/download-artifact@v2
@@ -128,7 +128,7 @@ jobs:
- name: Check trigger type
id: check_trigger
if: ${{ env.DOCKER_RELEASE_TAG == '' }}
- run: echo "DOCKER_RELEASE_TAG=1.2.0" >> $GITHUB_ENV
+ run: echo "DOCKER_RELEASE_TAG=1.1.5" >> $GITHUB_ENV
- name: Check out the Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml
index ba65c1b4abf5..ca5a65651de8 100644
--- a/.github/workflows/maven-build.yml
+++ b/.github/workflows/maven-build.yml
@@ -12,6 +12,7 @@
name: Maven MySQL Tests CI
on:
+ workflow_dispatch:
push:
branches:
- main
@@ -116,7 +117,7 @@ jobs:
- name: Build with Maven
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: ${{ github.event_name == 'push' }}
+ if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
run: mvn -Dsonar.login=${{ secrets.SONAR_TOKEN }} clean test
- name: Clean Up
diff --git a/.github/workflows/openmetadata-airflow-apis.yml b/.github/workflows/openmetadata-airflow-apis.yml
index d24101197e0a..13f887d984fc 100644
--- a/.github/workflows/openmetadata-airflow-apis.yml
+++ b/.github/workflows/openmetadata-airflow-apis.yml
@@ -34,6 +34,6 @@ jobs:
run: |
make install_dev install_apis
cd openmetadata-airflow-apis; \
- python setup.py install sdist bdist_wheel; \
+ python setup.py build sdist bdist_wheel; \
twine check dist/*; \
twine upload dist/* --verbose
diff --git a/Makefile b/Makefile
index 0112b042e713..02f87a85835c 100644
--- a/Makefile
+++ b/Makefile
@@ -246,4 +246,83 @@ generate-schema-docs: ## Generates markdown files for documenting the JSON Sche
@echo "Generating Schema docs"
python -m pip install "jsonschema2md"
python scripts/generate_docs_schemas.py
-
\ No newline at end of file
+
+#Upgrade release automation scripts below
+.PHONY: update_all
+update_all: ## To update all the release related files run make update_all RELEASE_VERSION=2.2.2 PY_RELEASE_VERSION=2.2.2.2
+ @echo "The release version is: $(RELEASE_VERSION)" ; \
+ echo "The python metadata release version: $(PY_RELEASE_VERSION)" ; \
+ $(MAKE) update_maven ; \
+ $(MAKE) update_github_action_paths ; \
+ $(MAKE) update_python_release_paths ; \
+ $(MAKE) update_dockerfile_version ; \
+ $(MAKE) update_ingestion_dockerfile_version ; \
+
+#remove comment and use the below section when want to use this sub module "update_all" independently to update github actions
+#make update_all RELEASE_VERSION=2.2.2 PY_RELEASE_VERSION=2.2.2.2
+
+.PHONY: update_maven
+update_maven: ## To update the common and pom.xml maven version
+ @echo "Updating Maven projects to version $(RELEASE_VERSION)..."; \
+ mvn versions:set -DnewVersion=$(RELEASE_VERSION)
+#remove comment and use the below section when want to use this sub module "update_maven" independently to update github actions
+#make update_maven RELEASE_VERSION=2.2.2
+
+
+.PHONY: update_github_action_paths
+update_github_action_paths: ## To update the github action ci docker files
+ @echo "Updating docker github action release version to $(RELEASE_VERSION)... "; \
+ file_paths="docker/docker-compose-quickstart/Dockerfile \
+ .github/workflows/docker-openmetadata-db.yml \
+ .github/workflows/docker-openmetadata-ingestion-base.yml \
+ .github/workflows/docker-openmetadata-ingestion.yml \
+ .github/workflows/docker-openmetadata-postgres.yml \
+ .github/workflows/docker-openmetadata-server.yml"; \
+ for file_path in $$file_paths; do \
+ python3 scripts/update_version.py 1 $$file_path -s $(RELEASE_VERSION) ; \
+ done; \
+ file_paths1="docker/docker-compose-quickstart/Dockerfile"; \
+ for file_path in $$file_paths1; do \
+ python3 scripts/update_version.py 4 $$file_path -s $(RELEASE_VERSION) ; \
+ done
+
+#remove comment and use the below section when want to use this sub module "update_github_action_paths" independently to update github actions
+#make update_github_action_paths RELEASE_VERSION=2.2.2
+
+.PHONY: update_python_release_paths
+update_python_release_paths: ## To update the setup.py files
+ file_paths="ingestion/setup.py \
+ openmetadata-airflow-apis/setup.py"; \
+ echo "Updating Python setup file versions to $(PY_RELEASE_VERSION)... "; \
+ for file_path in $$file_paths; do \
+ python3 scripts/update_version.py 2 $$file_path -s $(PY_RELEASE_VERSION) ; \
+ done
+# Commented section for independent usage of the module update_python_release_paths independently to update github actions
+#make update_python_release_paths PY_RELEASE_VERSION=2.2.2.2
+
+.PHONY: update_dockerfile_version
+update_dockerfile_version: ## To update the dockerfiles version
+ @file_paths="docker/docker-compose-ingestion/docker-compose-ingestion-postgres.yml \
+ docker/docker-compose-ingestion/docker-compose-ingestion.yml \
+ docker/docker-compose-openmetadata/docker-compose-openmetadata.yml \
+ docker/docker-compose-quickstart/docker-compose-postgres.yml \
+ docker/docker-compose-quickstart/docker-compose.yml"; \
+ echo "Updating docker github action release version to $(RELEASE_VERSION)... "; \
+ for file_path in $$file_paths; do \
+ python3 scripts/update_version.py 3 $$file_path -s $(RELEASE_VERSION) ; \
+ done
+#remove comment and use the below section when want to use this sub module "update_dockerfile_version" independently to update github actions
+#make update_dockerfile_version RELEASE_VERSION=2.2.2
+
+.PHONY: update_ingestion_dockerfile_version
+update_ingestion_dockerfile_version: ## To update the ingestion dockerfiles version
+ @file_paths="ingestion/Dockerfile \
+ ingestion/operators/docker/Dockerfile"; \
+ echo "Updating ingestion dockerfile release version to $(PY_RELEASE_VERSION)... "; \
+ for file_path in $$file_paths; do \
+ python3 scripts/update_version.py 4 $$file_path -s $(PY_RELEASE_VERSION) ; \
+ done
+#remove comment and use the below section when want to use this sub module "update_ingestion_dockerfile_version" independently to update github actions
+#make update_ingestion_dockerfile_version PY_RELEASE_VERSION=2.2.2.2
+
+#Upgrade release automation scripts above
diff --git a/bootstrap/sql/migrations/native/1.1.2/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.1.2/mysql/schemaChanges.sql
index 6b09a3a84afb..3c69c9fd152c 100644
--- a/bootstrap/sql/migrations/native/1.1.2/mysql/schemaChanges.sql
+++ b/bootstrap/sql/migrations/native/1.1.2/mysql/schemaChanges.sql
@@ -20,4 +20,4 @@ SET json = JSON_INSERT(
'$.connection.config.authType.password',
JSON_EXTRACT(json, '$.connection.config.password'))
where serviceType = 'Trino'
- AND JSON_EXTRACT(json, '$.connection.config.password') IS NOT NULL;
\ No newline at end of file
+ AND JSON_EXTRACT(json, '$.connection.config.password') IS NOT NULL;
diff --git a/bootstrap/sql/migrations/native/1.1.3/postgres/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.1.3/postgres/postDataMigrationSQLScript.sql
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/bootstrap/sql/migrations/native/1.1.3/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.1.3/postgres/schemaChanges.sql
deleted file mode 100644
index e6de4bf12ec9..000000000000
--- a/bootstrap/sql/migrations/native/1.1.3/postgres/schemaChanges.sql
+++ /dev/null
@@ -1,6 +0,0 @@
-ALTER TABLE entity_extension_time_series ALTER COLUMN entityFQNHash TYPE VARCHAR(768), ALTER COLUMN jsonSchema TYPE VARCHAR(50) , ALTER COLUMN extension TYPE VARCHAR(100) ,
- ADD CONSTRAINT entity_extension_time_series_constraint UNIQUE (entityFQNHash, extension, timestamp);
-ALTER TABLE field_relationship ALTER COLUMN fromFQNHash TYPE VARCHAR(768), ALTER COLUMN toFQNHash TYPE VARCHAR(768);
-ALTER TABLE thread_entity ALTER COLUMN entityLink TYPE VARCHAR(3072);
-ALTER TABLE tag_usage ALTER COLUMN tagFQNHash TYPE VARCHAR(768), ALTER COLUMN targetFQNHash TYPE VARCHAR(768);
-ALTER TABLE test_suite ALTER COLUMN fqnHash TYPE VARCHAR(768);
diff --git a/bootstrap/sql/migrations/native/1.1.5/mysql/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.1.5/mysql/postDataMigrationSQLScript.sql
new file mode 100644
index 000000000000..2ce1654285b5
--- /dev/null
+++ b/bootstrap/sql/migrations/native/1.1.5/mysql/postDataMigrationSQLScript.sql
@@ -0,0 +1,50 @@
+START TRANSACTION;
+-- We'll rank all the runs (timestamps) for every day, and delete all the data but the most recent one.
+DELETE FROM report_data_time_series WHERE JSON_EXTRACT(json, '$.id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> '$.id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY `date` ORDER BY `timestamp` DESC) as denseRank
+ FROM (
+ SELECT
+ *
+ FROM report_data_time_series rdts
+ WHERE json ->> '$.reportDataType' = 'WebAnalyticEntityViewReportData'
+ ) duplicates
+ ORDER BY `date` DESC, `timestamp` DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+
+DELETE FROM report_data_time_series WHERE JSON_EXTRACT(json, '$.id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> '$.id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY `date` ORDER BY `timestamp` DESC) as denseRank
+ FROM (
+ SELECT
+ *
+ FROM report_data_time_series rdts
+ WHERE json ->> '$.reportDataType' = 'EntityReportData'
+ ) duplicates
+ ORDER BY `date` DESC, `timestamp` DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+
+DELETE FROM report_data_time_series WHERE JSON_EXTRACT(json, '$.id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> '$.id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY `date` ORDER BY `timestamp` DESC) as denseRank
+ FROM (
+ SELECT
+ *
+ FROM report_data_time_series rdts
+ WHERE json ->> '$.reportDataType' = 'WebAnalyticUserActivityReportData'
+ ) duplicates
+ ORDER BY `date` DESC, `timestamp` DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+COMMIT;
\ No newline at end of file
diff --git a/bootstrap/sql/migrations/native/1.1.3/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.1.5/mysql/schemaChanges.sql
similarity index 55%
rename from bootstrap/sql/migrations/native/1.1.3/mysql/schemaChanges.sql
rename to bootstrap/sql/migrations/native/1.1.5/mysql/schemaChanges.sql
index d52b8f8dd0e1..837bedf99cb6 100644
--- a/bootstrap/sql/migrations/native/1.1.3/mysql/schemaChanges.sql
+++ b/bootstrap/sql/migrations/native/1.1.5/mysql/schemaChanges.sql
@@ -1,9 +1,83 @@
+-- Update table and column profile timestamps to be in milliseconds
+UPDATE entity_extension_time_series
+ SET json = JSON_INSERT(
+ JSON_REMOVE(json, '$.timestamp'),
+ '$.timestamp',
+ JSON_EXTRACT(json, '$.timestamp') * 1000
+ )
+WHERE
+ extension in ('table.tableProfile', 'table.columnProfile', 'testCase.testCaseResult');
+;
+
+START TRANSACTION;
+-- Create report data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS report_data_time_series (
+ entityFQNHash VARCHAR(768) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSON NOT NULL,
+ timestamp BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.timestamp') NOT NULL,
+ date DATE GENERATED ALWAYS AS (FROM_UNIXTIME((json ->> '$.timestamp') DIV 1000)) NOT NULL,
+ INDEX report_data_time_series_point_ts (timestamp),
+ INDEX report_data_time_series_date (date)
+);
+
+INSERT INTO report_data_time_series (entityFQNHash,extension,jsonSchema,json)
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series WHERE extension = 'reportData.reportDataResult';
+
+DELETE FROM entity_extension_time_series
+WHERE extension = 'reportData.reportDataResult';
+COMMIT;
+
+START TRANSACTION;
+-- Create profiler data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS profiler_data_time_series (
+ entityFQNHash VARCHAR(768) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSON NOT NULL,
+ operation VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.operation') NULL,
+ timestamp BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.timestamp') NOT NULL,
+ UNIQUE profiler_data_time_series_unique_hash_extension_ts (entityFQNHash, extension, operation, timestamp),
+ INDEX profiler_data_time_series_combined_id_ts (extension, timestamp)
+);
+
+INSERT INTO profiler_data_time_series (entityFQNHash,extension,jsonSchema,json)
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series
+WHERE extension IN ('table.columnProfile', 'table.tableProfile', 'table.systemProfile');
+
+DELETE FROM entity_extension_time_series
+WHERE extension IN ('table.columnProfile', 'table.tableProfile', 'table.systemProfile');
+COMMIT;
+
+START TRANSACTION;
+-- Create data quality data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS data_quality_data_time_series (
+ entityFQNHash VARCHAR(768) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSON NOT NULL,
+ timestamp BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.timestamp') NOT NULL,
+ UNIQUE data_quality_data_time_series_unique_hash_extension_ts (entityFQNHash, extension, timestamp),
+ INDEX data_quality_data_time_series_combined_id_ts (extension, timestamp)
+);
+
+INSERT INTO data_quality_data_time_series (entityFQNHash,extension,jsonSchema,json)
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series
+WHERE extension = 'testCase.testCaseResult';
+
+DELETE FROM entity_extension_time_series
+WHERE extension = 'testCase.testCaseResult';
+COMMIT;
+
ALTER TABLE automations_workflow MODIFY COLUMN nameHash VARCHAR(256) COLLATE ascii_bin,MODIFY COLUMN workflowType VARCHAR(256) COLLATE ascii_bin, MODIFY COLUMN status VARCHAR(256) COLLATE ascii_bin;
-ALTER TABLE entity_extension MODIFY COLUMN extension VARCHAR(256) COLLATE ascii_bin;
ALTER TABLE entity_extension_time_series MODIFY COLUMN entityFQNHash VARCHAR(768) COLLATE ascii_bin, MODIFY COLUMN jsonSchema VARCHAR(50) COLLATE ascii_bin, MODIFY COLUMN extension VARCHAR(100) COLLATE ascii_bin,
ADD CONSTRAINT entity_extension_time_series_constraint UNIQUE (entityFQNHash, extension, timestamp);
ALTER TABLE field_relationship MODIFY COLUMN fromFQNHash VARCHAR(768) COLLATE ascii_bin, MODIFY COLUMN toFQNHash VARCHAR(768) COLLATE ascii_bin;
-ALTER TABLE thread_entity MODIFY COLUMN entityLink VARCHAR(3072) GENERATED ALWAYS AS (json ->> '$.about') NOT NULL, MODIFY COLUMN createdBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.createdBy') STORED NOT NULL COLLATE ascii_bin;
+ALTER TABLE thread_entity MODIFY COLUMN entityLink VARCHAR(3072) GENERATED ALWAYS AS (json ->> '$.about') NOT NULL;
ALTER TABLE event_subscription_entity MODIFY COLUMN nameHash VARCHAR(256) COLLATE ascii_bin;
ALTER TABLE ingestion_pipeline_entity MODIFY COLUMN fqnHash VARCHAR(768) COLLATE ascii_bin;
ALTER TABLE bot_entity MODIFY COLUMN nameHash VARCHAR(256) COLLATE ascii_bin;
diff --git a/bootstrap/sql/migrations/native/1.1.5/postgres/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.1.5/postgres/postDataMigrationSQLScript.sql
new file mode 100644
index 000000000000..948f4fb754c2
--- /dev/null
+++ b/bootstrap/sql/migrations/native/1.1.5/postgres/postDataMigrationSQLScript.sql
@@ -0,0 +1,53 @@
+BEGIN;
+-- We'll rank all the runs (timestamps) for every day, and delete all the data but the most recent one.
+DELETE FROM report_data_time_series WHERE (json ->> 'id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> 'id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY date ORDER BY timestamp DESC) as denseRank
+ FROM (
+ SELECT
+ *,
+ DATE(TO_TIMESTAMP((json ->> 'timestamp')::bigint/1000)) as date
+ FROM report_data_time_series rdts
+ WHERE json ->> 'reportDataType' = 'WebAnalyticEntityViewReportData'
+ ) duplicates
+ ORDER BY date DESC, timestamp DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+
+DELETE FROM report_data_time_series WHERE (json ->> 'id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> 'id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY date ORDER BY timestamp DESC) as denseRank
+ FROM (
+ SELECT
+ *,
+ DATE(TO_TIMESTAMP((json ->> 'timestamp')::bigint/1000)) as date
+ FROM report_data_time_series rdts
+ WHERE json ->> 'reportDataType' = 'EntityReportData'
+ ) duplicates
+ ORDER BY date DESC, timestamp DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+
+DELETE FROM report_data_time_series WHERE (json ->> 'id') IN (
+ select ids FROM (
+ SELECT
+ (json ->> 'id') AS ids,
+ DENSE_RANK() OVER(PARTITION BY date ORDER BY timestamp DESC) as denseRank
+ FROM (
+ SELECT
+ *,
+ DATE(TO_TIMESTAMP((json ->> 'timestamp')::bigint/1000)) as date
+ FROM report_data_time_series rdts
+ WHERE json ->> 'reportDataType' = 'WebAnalyticUserActivityReportData'
+ ) duplicates
+ ORDER BY date DESC, timestamp DESC
+ ) as dense_ranked
+ WHERE denseRank != 1
+);
+COMMIT;
\ No newline at end of file
diff --git a/bootstrap/sql/migrations/native/1.1.5/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.1.5/postgres/schemaChanges.sql
new file mode 100644
index 000000000000..553447353a97
--- /dev/null
+++ b/bootstrap/sql/migrations/native/1.1.5/postgres/schemaChanges.sql
@@ -0,0 +1,83 @@
+-- Update table and column profile timestamps to be in milliseconds
+UPDATE entity_extension_time_series
+SET json = jsonb_set(
+ json,
+ '{timestamp}',
+ to_jsonb(cast(json#>'{timestamp}' as int8) *1000)
+)
+WHERE
+ extension in ('table.tableProfile', 'table.columnProfile', 'testCase.testCaseResult');
+;
+
+BEGIN;
+-- Run the following SQL to update the schema in a transaction
+-- Create report data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS report_data_time_series (
+ entityFQNHash VARCHAR(768),
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSONB NOT NULL,
+ timestamp BIGINT CHECK (timestamp > 0) GENERATED ALWAYS AS ((json ->> 'timestamp')::bigint) STORED NOT NULL
+);
+CREATE INDEX IF NOT EXISTS report_data_time_series_point_ts ON report_data_time_series (timestamp);
+
+INSERT INTO report_data_time_series (entityFQNHash,extension,jsonSchema,json)
+
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series WHERE extension = 'reportData.reportDataResult';
+
+DELETE FROM entity_extension_time_series
+WHERE extension = 'reportData.reportDataResult';
+COMMIT;
+
+BEGIN;
+-- Create profiler data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS profiler_data_time_series (
+ entityFQNHash VARCHAR(768),
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSON NOT NULL,
+ operation VARCHAR(256) GENERATED ALWAYS AS ((json ->> 'operation')::text) STORED NULL,
+ timestamp BIGINT CHECK (timestamp > 0) GENERATED ALWAYS AS ((json ->> 'timestamp')::bigint) STORED NOT NULL,
+ CONSTRAINT profiler_data_time_series_unique_hash_extension_ts UNIQUE(entityFQNHash, extension, operation, timestamp)
+);
+
+CREATE INDEX IF NOT EXISTS profiler_data_time_series_combined_id_ts ON profiler_data_time_series (extension, timestamp);
+
+INSERT INTO profiler_data_time_series (entityFQNHash,extension,jsonSchema,json)
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series
+WHERE extension IN ('table.columnProfile', 'table.tableProfile', 'table.systemProfile');
+
+DELETE FROM entity_extension_time_series
+WHERE extension IN ('table.columnProfile', 'table.tableProfile', 'table.systemProfile');
+COMMIT;
+
+BEGIN;
+-- Create profiler data time series table and move data from entity_extension_time_series
+CREATE TABLE IF NOT EXISTS data_quality_data_time_series (
+ entityFQNHash VARCHAR(768),
+ extension VARCHAR(256) NOT NULL,
+ jsonSchema VARCHAR(256) NOT NULL,
+ json JSON NOT NULL,
+ timestamp BIGINT CHECK (timestamp > 0) GENERATED ALWAYS AS ((json ->> 'timestamp')::bigint) STORED NOT NULL,
+ CONSTRAINT data_quality_data_time_series_unique_hash_extension_ts UNIQUE(entityFQNHash, extension, timestamp)
+);
+
+CREATE INDEX IF NOT EXISTS data_quality_data_time_series_combined_id_ts ON data_quality_data_time_series (extension, timestamp);
+
+INSERT INTO data_quality_data_time_series (entityFQNHash,extension,jsonSchema,json)
+SELECT entityFQNHash, extension, jsonSchema, json
+FROM entity_extension_time_series
+WHERE extension = 'testCase.testCaseResult';
+
+DELETE FROM entity_extension_time_series
+WHERE extension = 'testCase.testCaseResult';
+COMMIT;
+
+ALTER TABLE entity_extension_time_series ALTER COLUMN entityFQNHash TYPE VARCHAR(768), ALTER COLUMN jsonSchema TYPE VARCHAR(50) , ALTER COLUMN extension TYPE VARCHAR(100) ,
+ ADD CONSTRAINT entity_extension_time_series_constraint UNIQUE (entityFQNHash, extension, timestamp);
+ALTER TABLE field_relationship ALTER COLUMN fromFQNHash TYPE VARCHAR(768), ALTER COLUMN toFQNHash TYPE VARCHAR(768);
+ALTER TABLE thread_entity ALTER COLUMN entityLink TYPE VARCHAR(3072);
+ALTER TABLE tag_usage ALTER COLUMN tagFQNHash TYPE VARCHAR(768), ALTER COLUMN targetFQNHash TYPE VARCHAR(768);
+ALTER TABLE test_suite ALTER COLUMN fqnHash TYPE VARCHAR(768);
diff --git a/bootstrap/sql/migrations/native/1.2.0/mysql/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.2.0/mysql/postDataMigrationSQLScript.sql
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/bootstrap/sql/migrations/native/1.2.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.2.0/mysql/schemaChanges.sql
deleted file mode 100644
index 05f678e0a590..000000000000
--- a/bootstrap/sql/migrations/native/1.2.0/mysql/schemaChanges.sql
+++ /dev/null
@@ -1,54 +0,0 @@
--- column deleted not needed for entities that don't support soft delete
-ALTER TABLE query_entity DROP COLUMN deleted;
-ALTER TABLE event_subscription_entity DROP COLUMN deleted;
-
--- create domain entity table
-CREATE TABLE IF NOT EXISTS domain_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
- fqnHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
- json JSON NOT NULL,
- updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
-
--- create data product entity table
-CREATE TABLE IF NOT EXISTS data_product_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
- fqnHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
- json JSON NOT NULL,
- updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
-
--- create search service entity
-CREATE TABLE IF NOT EXISTS search_service_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
- nameHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
- serviceType VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.serviceType') NOT NULL,
- json JSON NOT NULL,
- updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
- deleted BOOLEAN GENERATED ALWAYS AS (json -> '$.deleted'),
- PRIMARY KEY (id),
- UNIQUE (nameHash)
- );
-
--- create search index entity
-CREATE TABLE IF NOT EXISTS search_index_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> '$.id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.name') NOT NULL,
- fqnHash VARCHAR(256) NOT NULL COLLATE ascii_bin,
- json JSON NOT NULL,
- updatedAt BIGINT UNSIGNED GENERATED ALWAYS AS (json ->> '$.updatedAt') NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> '$.updatedBy') NOT NULL,
- deleted BOOLEAN GENERATED ALWAYS AS (json -> '$.deleted'),
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
diff --git a/bootstrap/sql/migrations/native/1.2.0/postgres/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.2.0/postgres/postDataMigrationSQLScript.sql
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/bootstrap/sql/migrations/native/1.2.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.2.0/postgres/schemaChanges.sql
deleted file mode 100644
index 127237d506e6..000000000000
--- a/bootstrap/sql/migrations/native/1.2.0/postgres/schemaChanges.sql
+++ /dev/null
@@ -1,54 +0,0 @@
--- column deleted not needed for entities that don't support soft delete
-ALTER TABLE query_entity DROP COLUMN deleted;
-ALTER TABLE event_subscription_entity DROP COLUMN deleted;
-
--- create domain entity table
-CREATE TABLE IF NOT EXISTS domain_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
- fqnHash VARCHAR(256) NOT NULL,
- json JSONB NOT NULL,
- updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
-
--- create data product entity table
-CREATE TABLE IF NOT EXISTS data_product_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
- fqnHash VARCHAR(256) NOT NULL,
- json JSONB NOT NULL,
- updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
-
--- create search service entity
-CREATE TABLE IF NOT EXISTS search_service_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
- nameHash VARCHAR(256) NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
- serviceType VARCHAR(256) GENERATED ALWAYS AS (json ->> 'serviceType') STORED NOT NULL,
- json JSONB NOT NULL,
- updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
- deleted BOOLEAN GENERATED ALWAYS AS ((json ->> 'deleted')::boolean) STORED,
- PRIMARY KEY (id),
- UNIQUE (nameHash)
- );
-
--- create search index entity
-CREATE TABLE IF NOT EXISTS search_index_entity (
- id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL,
- name VARCHAR(256) GENERATED ALWAYS AS (json ->> 'name') STORED NOT NULL,
- fqnHash VARCHAR(256) NOT NULL,
- json JSONB NOT NULL,
- updatedAt BIGINT GENERATED ALWAYS AS ((json ->> 'updatedAt')::bigint) STORED NOT NULL,
- updatedBy VARCHAR(256) GENERATED ALWAYS AS (json ->> 'updatedBy') STORED NOT NULL,
- deleted BOOLEAN GENERATED ALWAYS AS ((json ->> 'deleted')::boolean) STORED,
- PRIMARY KEY (id),
- UNIQUE (fqnHash)
- );
diff --git a/common/pom.xml b/common/pom.xml
index 7531a6c26042..2fb2c2fe3114 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -18,7 +18,7 @@
platform
org.open-metadata
- 1.2.0-SNAPSHOT
+ 1.1.5
4.0.0
diff --git a/docker/development/docker-compose-postgres.yml b/docker/development/docker-compose-postgres.yml
index b22e42fc41e2..78716934cea9 100644
--- a/docker/development/docker-compose-postgres.yml
+++ b/docker/development/docker-compose-postgres.yml
@@ -70,7 +70,6 @@ services:
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# Migration
MIGRATION_LIMIT_PARAM: ${MIGRATION_LIMIT_PARAM:-1200}
@@ -229,7 +228,6 @@ services:
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# OpenMetadata Server Authentication Configuration
AUTHORIZER_CLASS_NAME: ${AUTHORIZER_CLASS_NAME:-org.openmetadata.service.security.DefaultAuthorizer}
AUTHORIZER_REQUEST_FILTER: ${AUTHORIZER_REQUEST_FILTER:-org.openmetadata.service.security.JwtFilter}
diff --git a/docker/development/docker-compose.yml b/docker/development/docker-compose.yml
index 77b8ba7b75f7..34c87bd66e45 100644
--- a/docker/development/docker-compose.yml
+++ b/docker/development/docker-compose.yml
@@ -69,7 +69,6 @@ services:
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# Migration
MIGRATION_LIMIT_PARAM: ${MIGRATION_LIMIT_PARAM:-1200}
@@ -225,7 +224,6 @@ services:
dockerfile: docker/development/Dockerfile
container_name: openmetadata_server
environment:
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
diff --git a/docker/docker-compose-ingestion/docker-compose-ingestion.yml b/docker/docker-compose-ingestion/docker-compose-ingestion.yml
index dc5d7cf3f123..769c2f21af4f 100644
--- a/docker/docker-compose-ingestion/docker-compose-ingestion.yml
+++ b/docker/docker-compose-ingestion/docker-compose-ingestion.yml
@@ -18,7 +18,7 @@ volumes:
services:
ingestion:
container_name: openmetadata_ingestion
- image: docker.getcollate.io/openmetadata/ingestion:1.2.0
+ image: docker.getcollate.io/openmetadata/ingestion:1.1.5
environment:
AIRFLOW__API__AUTH_BACKENDS: "airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session"
AIRFLOW__CORE__EXECUTOR: LocalExecutor
diff --git a/docker/docker-compose-openmetadata/docker-compose-openmetadata.yml b/docker/docker-compose-openmetadata/docker-compose-openmetadata.yml
index a830c73cb994..25ace6583898 100644
--- a/docker/docker-compose-openmetadata/docker-compose-openmetadata.yml
+++ b/docker/docker-compose-openmetadata/docker-compose-openmetadata.yml
@@ -14,13 +14,12 @@ services:
execute-migrate-all:
container_name: execute_migrate_all
command: "./bootstrap/bootstrap_storage.sh migrate-all"
- image: docker.getcollate.io/openmetadata/server:1.2.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# Migration
MIGRATION_LIMIT_PARAM: ${MIGRATION_LIMIT_PARAM:-1200}
@@ -168,13 +167,12 @@ services:
openmetadata-server:
container_name: openmetadata_server
restart: always
- image: docker.getcollate.io/openmetadata/server:1.2.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# OpenMetadata Server Authentication Configuration
AUTHORIZER_CLASS_NAME: ${AUTHORIZER_CLASS_NAME:-org.openmetadata.service.security.DefaultAuthorizer}
diff --git a/docker/docker-compose-openmetadata/env-mysql b/docker/docker-compose-openmetadata/env-mysql
index 3cb8aa814bf4..fbd5ff88d80b 100644
--- a/docker/docker-compose-openmetadata/env-mysql
+++ b/docker/docker-compose-openmetadata/env-mysql
@@ -2,7 +2,6 @@ OPENMETADATA_CLUSTER_NAME="openmetadata"
SERVER_PORT="8585"
SERVER_ADMIN_PORT="8586"
LOG_LEVEL="INFO"
-OPENMETADATA_DEBUG="false"
# Migration
MIGRATION_LIMIT_PARAM = 1200
diff --git a/docker/docker-compose-openmetadata/env-postgres b/docker/docker-compose-openmetadata/env-postgres
index d9004ac4bbc5..a450f775dd59 100644
--- a/docker/docker-compose-openmetadata/env-postgres
+++ b/docker/docker-compose-openmetadata/env-postgres
@@ -2,7 +2,6 @@ OPENMETADATA_CLUSTER_NAME="openmetadata"
SERVER_PORT="8585"
SERVER_ADMIN_PORT="8586"
LOG_LEVEL="INFO"
-OPENMETADATA_DEBUG="false"
# Migration
MIGRATION_LIMIT_PARAM = 1200
diff --git a/docker/docker-compose-quickstart/Dockerfile b/docker/docker-compose-quickstart/Dockerfile
index e35fb3932fb3..b0c6b5eb845b 100644
--- a/docker/docker-compose-quickstart/Dockerfile
+++ b/docker/docker-compose-quickstart/Dockerfile
@@ -11,7 +11,9 @@
# Build stage
FROM alpine:3.15 AS build
-ENV RELEASE_URL="https://github.com/open-metadata/OpenMetadata/releases/download/1.2.0-release/openmetadata-1.2.0.tar.gz"
+ARG RI_VERSION="1.1.5"
+ENV RELEASE_URL="https://github.com/open-metadata/OpenMetadata/releases/download/${RI_VERSION}-release/openmetadata-${RI_VERSION}.tar.gz"
+
RUN mkdir -p /opt/openmetadata && \
wget ${RELEASE_URL} && \
tar zxvf openmetadata-*.tar.gz -C /opt/openmetadata --strip-components 1 && \
@@ -24,7 +26,7 @@ ARG COMMIT_ID
LABEL maintainer="OpenMetadata"
LABEL org.open-metadata.image.authors="support@openmetadata.org"
LABEL org.open-metadata.vendor="OpenMetadata"
-LABEL org.open-metadata.release-version="1.2.0"
+LABEL org.open-metadata.release-version="1.1.5"
LABEL org.open-metadata.description="OpenMetadata is an open source platform for metadata management and discovery."
LABEL org.open-metadata.url="https://open-metadata.org/"
LABEL org.open-metadata.vcs-url="https://github.com/open-metadata/OpenMetadata"
diff --git a/docker/docker-compose-quickstart/docker-compose-postgres.yml b/docker/docker-compose-quickstart/docker-compose-postgres.yml
index 2d5cf718d1ab..e6518bb14431 100644
--- a/docker/docker-compose-quickstart/docker-compose-postgres.yml
+++ b/docker/docker-compose-quickstart/docker-compose-postgres.yml
@@ -18,7 +18,7 @@ volumes:
services:
postgresql:
container_name: openmetadata_postgresql
- image: docker.getcollate.io/openmetadata/postgresql:1.2.0
+ image: docker.getcollate.io/openmetadata/postgresql:1.1.5
restart: always
command: "--work_mem=10MB"
environment:
@@ -55,14 +55,13 @@ services:
execute-migrate-all:
container_name: execute_migrate_all
- image: docker.getcollate.io/openmetadata/server:1.1.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
command: "./bootstrap/bootstrap_storage.sh migrate-all"
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# Migration
MIGRATION_LIMIT_PARAM: ${MIGRATION_LIMIT_PARAM:-1200}
@@ -214,13 +213,12 @@ services:
openmetadata-server:
container_name: openmetadata_server
restart: always
- image: docker.getcollate.io/openmetadata/server:1.1.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# OpenMetadata Server Authentication Configuration
AUTHORIZER_CLASS_NAME: ${AUTHORIZER_CLASS_NAME:-org.openmetadata.service.security.DefaultAuthorizer}
@@ -373,7 +371,7 @@ services:
ingestion:
container_name: openmetadata_ingestion
- image: docker.getcollate.io/openmetadata/ingestion:1.1.0
+ image: docker.getcollate.io/openmetadata/ingestion:1.1.5
depends_on:
elasticsearch:
condition: service_started
diff --git a/docker/docker-compose-quickstart/docker-compose.yml b/docker/docker-compose-quickstart/docker-compose.yml
index 2a8167645722..a0c8a733aa83 100644
--- a/docker/docker-compose-quickstart/docker-compose.yml
+++ b/docker/docker-compose-quickstart/docker-compose.yml
@@ -18,7 +18,7 @@ volumes:
services:
mysql:
container_name: openmetadata_mysql
- image: docker.getcollate.io/openmetadata/db:1.2.0
+ image: docker.getcollate.io/openmetadata/db:1.1.5
command: "--sort_buffer_size=10M"
restart: always
environment:
@@ -53,14 +53,13 @@ services:
execute-migrate-all:
container_name: execute_migrate_all
- image: docker.getcollate.io/openmetadata/server:1.1.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
command: "./bootstrap/bootstrap_storage.sh migrate-all"
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# Migration
MIGRATION_LIMIT_PARAM: ${MIGRATION_LIMIT_PARAM:-1200}
@@ -213,13 +212,12 @@ services:
openmetadata-server:
container_name: openmetadata_server
restart: always
- image: docker.getcollate.io/openmetadata/server:1.1.0
+ image: docker.getcollate.io/openmetadata/server:1.1.5
environment:
OPENMETADATA_CLUSTER_NAME: ${OPENMETADATA_CLUSTER_NAME:-openmetadata}
SERVER_PORT: ${SERVER_PORT:-8585}
SERVER_ADMIN_PORT: ${SERVER_ADMIN_PORT:-8586}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
- OPENMETADATA_DEBUG: ${OPENMETADATA_DEBUG:-false}
# OpenMetadata Server Authentication Configuration
AUTHORIZER_CLASS_NAME: ${AUTHORIZER_CLASS_NAME:-org.openmetadata.service.security.DefaultAuthorizer}
@@ -373,7 +371,7 @@ services:
ingestion:
container_name: openmetadata_ingestion
- image: docker.getcollate.io/openmetadata/ingestion:1.1.0
+ image: docker.getcollate.io/openmetadata/ingestion:1.1.5
depends_on:
elasticsearch:
condition: service_started
diff --git a/docker/postgresql/Dockerfile_postgres b/docker/postgresql/Dockerfile_postgres
index e264e58b19bc..ddd375fcd81e 100644
--- a/docker/postgresql/Dockerfile_postgres
+++ b/docker/postgresql/Dockerfile_postgres
@@ -1,4 +1,4 @@
-FROM postgres:15
+FROM postgres:14
WORKDIR /docker-entrypoint-initdb.d
COPY docker/postgresql/postgres-script.sql .
RUN chmod -R 775 /docker-entrypoint-initdb.d
\ No newline at end of file
diff --git a/docker/run_local_docker.sh b/docker/run_local_docker.sh
index d6299d41dfbb..b0236001eeb2 100755
--- a/docker/run_local_docker.sh
+++ b/docker/run_local_docker.sh
@@ -114,6 +114,7 @@ done
until curl -s -f --header 'Authorization: Basic YWRtaW46YWRtaW4=' "http://localhost:8080/api/v1/dags/sample_data"; do
printf 'Checking if Sample Data DAG is reachable...\n'
+ curl --header 'Authorization: Basic YWRtaW46YWRtaW4=' "http://localhost:8080/api/v1/dags/sample_data"
sleep 5
done
diff --git a/ingestion/Dockerfile b/ingestion/Dockerfile
index bb0fad549b14..0fe31ae4dddc 100644
--- a/ingestion/Dockerfile
+++ b/ingestion/Dockerfile
@@ -1,4 +1,4 @@
-FROM apache/airflow:2.5.3-python3.9
+FROM apache/airflow:2.6.3-python3.9
USER root
RUN curl -sS https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl -sS https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list
@@ -80,10 +80,10 @@ ARG INGESTION_DEPENDENCY="all"
ENV PIP_NO_CACHE_DIR=1
# Make pip silent
ENV PIP_QUIET=1
-
+ARG RI_VERSION="1.1.5.0"
RUN pip install --upgrade pip
-RUN pip install "openmetadata-managed-apis~=1.1.0.4" --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-2.6.3/constraints-3.9.txt"
-RUN pip install "openmetadata-ingestion[${INGESTION_DEPENDENCY}]~=1.1.0.4"
+RUN pip install "openmetadata-managed-apis~=${RI_VERSION}" --constraint "https://raw.githubusercontent.com/apache/airflow/constraints-2.6.3/constraints-3.9.txt"
+RUN pip install "openmetadata-ingestion[${INGESTION_DEPENDENCY}]~=${RI_VERSION}"
# Temporary workaround for https://github.com/open-metadata/OpenMetadata/issues/9593
RUN echo "Image built for $(uname -m)"
diff --git a/ingestion/Dockerfile.ci b/ingestion/Dockerfile.ci
index df3244588257..6931b899f210 100644
--- a/ingestion/Dockerfile.ci
+++ b/ingestion/Dockerfile.ci
@@ -1,4 +1,4 @@
-FROM apache/airflow:2.5.3-python3.9
+FROM apache/airflow:2.6.3-python3.9
USER root
RUN curl -sS https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl -sS https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list
diff --git a/ingestion/examples/sample_data/searchIndexes/searchIndexes.json b/ingestion/examples/sample_data/searchIndexes/searchIndexes.json
deleted file mode 100644
index 9c496d38ed6f..000000000000
--- a/ingestion/examples/sample_data/searchIndexes/searchIndexes.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "searchIndexes": [
- {
- "id": "e093dd27-390e-4360-8efd-e4d63ec167a9",
- "name": "table_search_index",
- "displayName": "TableSearchIndex",
- "fullyQualifiedName": "elasticsearch_sample.table_search_index",
- "description": "Table Search Index",
- "version": 0.1,
- "updatedAt": 1638354087591,
- "serviceType": "ElasticSearch",
- "fields": [
- {
- "name": "name",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Table Entity Name.",
- "tags": []
- },
- {
- "name": "displayName",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Table Entity DisplayName.",
- "tags": []
- },
- {
- "name": "description",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Table Entity Description.",
- "tags": []
- },
- {
- "name": "columns",
- "dataType": "NESTED",
- "dataTypeDisplay": "nested",
- "description": "Table Columns.",
- "tags": [],
- "children": [
- {
- "name": "name",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Column Name.",
- "tags": []
- },
- {
- "name": "displayName",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Column DisplayName.",
- "tags": []
- },
- {
- "name": "description",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Column Description.",
- "tags": []
- }
- ]
- },
- {
- "name": "databaseSchema",
- "dataType": "TEXT",
- "dataTypeDisplay": "text",
- "description": "Database Schema that this table belongs to.",
- "tags": []
- }
- ],
- "tags": [],
- "followers": []
- }
- ]
-}
\ No newline at end of file
diff --git a/ingestion/examples/sample_data/searchIndexes/service.json b/ingestion/examples/sample_data/searchIndexes/service.json
deleted file mode 100644
index 82e9b21cc80b..000000000000
--- a/ingestion/examples/sample_data/searchIndexes/service.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "type": "elasticsearch",
- "serviceName": "elasticsearch_sample",
- "serviceConnection": {
- "config": {
- "type": "ElasticSearch",
- "hostPort": "localhost:9200"
- }
- },
- "sourceConfig": {
- }
-}
\ No newline at end of file
diff --git a/ingestion/operators/docker/Dockerfile b/ingestion/operators/docker/Dockerfile
index 1bdc65807024..d99268712e65 100644
--- a/ingestion/operators/docker/Dockerfile
+++ b/ingestion/operators/docker/Dockerfile
@@ -86,9 +86,10 @@ ENV PIP_QUIET=1
RUN pip install --upgrade pip
ARG INGESTION_DEPENDENCY="all"
+ARG RI_VERSION="1.1.5.0"
RUN pip install --upgrade pip
-RUN pip install "openmetadata-ingestion[airflow]~=1.1.0.4"
-RUN pip install "openmetadata-ingestion[${INGESTION_DEPENDENCY}]~=1.1.0.4"
+RUN pip install "openmetadata-ingestion[airflow]~=${RI_VERSION}"
+RUN pip install "openmetadata-ingestion[${INGESTION_DEPENDENCY}]~=${RI_VERSION}"
# Temporary workaround for https://github.com/open-metadata/OpenMetadata/issues/9593
RUN echo "Image built for $(uname -m)"
diff --git a/ingestion/setup.py b/ingestion/setup.py
index c3bd2947e714..0352dc16197a 100644
--- a/ingestion/setup.py
+++ b/ingestion/setup.py
@@ -93,7 +93,7 @@ def get_long_description():
"google-auth>=1.33.0",
VERSIONS["grpc-tools"], # Used in sample data
"idna<3,>=2.5",
- "importlib-metadata~=4.13.0", # From airflow constraints
+ "importlib-metadata>=4.13.0", # From airflow constraints
"Jinja2>=2.11.3",
"jsonpatch==1.32",
"jsonschema",
@@ -108,7 +108,7 @@ def get_long_description():
"requests-aws4auth~=1.1", # Only depends on requests as external package. Leaving as base.
"setuptools~=66.0.0",
"sqlalchemy>=1.4.0,<2",
- "openmetadata-sqllineage>=1.0.4",
+ "collate-sqllineage>=1.0.4",
"tabulate==0.9.0",
"typing-compat~=0.1.0", # compatibility requirements for 3.7
"typing_extensions<=4.5.0", # We need to have this fixed due to a yanked release 4.6.0
@@ -266,7 +266,7 @@ def get_long_description():
build_options = {"includes": ["_cffi_backend"]}
setup(
name="openmetadata-ingestion",
- version="1.2.0.0.dev0",
+ version="1.1.5.0",
url="https://open-metadata.org/",
author="OpenMetadata Committers",
license="Apache License 2.0",
diff --git a/ingestion/src/metadata/cli/db_dump.py b/ingestion/src/metadata/cli/db_dump.py
index ec39207cee61..674ff93a0e0f 100644
--- a/ingestion/src/metadata/cli/db_dump.py
+++ b/ingestion/src/metadata/cli/db_dump.py
@@ -31,6 +31,7 @@
"field_relationship",
"tag_usage",
"openmetadata_settings",
+ "profiler_data_time_series"
}
CUSTOM_TABLES = {"entity_extension_time_series": {"exclude_columns": ["timestamp"]}}
diff --git a/ingestion/src/metadata/data_insight/api/workflow.py b/ingestion/src/metadata/data_insight/api/workflow.py
index 6e5a3db33b97..0c4e66160d38 100644
--- a/ingestion/src/metadata/data_insight/api/workflow.py
+++ b/ingestion/src/metadata/data_insight/api/workflow.py
@@ -27,6 +27,7 @@
from pydantic import ValidationError
from metadata.config.common import WorkflowExecutionError
+from metadata.data_insight.helper.data_insight_es_index import DataInsightEsIndex
from metadata.data_insight.processor.data_processor import DataProcessor
from metadata.data_insight.processor.entity_report_data_processor import (
EntityReportDataProcessor,
@@ -58,7 +59,10 @@
from metadata.timer.workflow_reporter import get_ingestion_status_timer
from metadata.utils.importer import get_sink
from metadata.utils.logger import data_insight_logger, set_loggers_level
-from metadata.utils.time_utils import get_beginning_of_day_timestamp_mill
+from metadata.utils.time_utils import (
+ get_beginning_of_day_timestamp_mill,
+ get_end_of_day_timestamp_mill,
+)
from metadata.utils.workflow_output_handler import print_data_insight_status
from metadata.workflow.workflow_status_mixin import WorkflowStatusMixin
@@ -78,6 +82,7 @@ class DataInsightWorkflow(WorkflowStatusMixin):
def __init__(self, config: OpenMetadataWorkflowConfig) -> None:
self.config = config
self._timer: Optional[RepeatedTimer] = None
+ self.date = datetime.utcnow().strftime("%Y-%m-%d")
set_loggers_level(config.workflowConfig.loggerLevel.value)
@@ -165,10 +170,56 @@ def _get_kpis(self) -> list[Kpi]:
return [kpi for kpi in kpis.entities if self._is_kpi_active(kpi)]
+ def _check_and_handle_existing_es_data(self, index: str) -> None:
+ """Handles scenarios where data has already been ingested for the execution data.
+ If we find some data for the execution date we should deleted those documents before
+ re indexing new documents.
+
+ !IMPORTANT! This should be deprecared and the logic should be handle in the event
+ publisher side once we have the event publisher handling DI indexing.
+ """
+ gte = get_beginning_of_day_timestamp_mill()
+ lte = get_end_of_day_timestamp_mill()
+ query = {
+ "size": 1000,
+ "query": {
+ "range": {
+ "timestamp": {
+ "gte": gte,
+ "lte": lte,
+ }
+ }
+ },
+ }
+ data = self.es_sink.read_records(index, query)
+ try:
+ hit_total = data["hits"]["total"]["value"]
+ documents = data["hits"]["hits"]
+ except KeyError as exc:
+ logger.error(exc)
+ else:
+ if hit_total > 0:
+ body = [
+ {"delete": {"_index": document["_index"], "_id": document["_id"]}}
+ for document in documents
+ ]
+ try:
+ self.es_sink.bulk_operation(body)
+ except Exception as exc:
+ logger.debug(traceback.format_exc())
+ logger.error(f"Could not delete existing data - {exc}")
+ raise RuntimeError
+ return None
+ return None
+
def _execute_data_processor(self):
"""Data processor method to refine raw data into report data and ingest it in ES"""
for report_data_type in ReportDataType:
logger.info(f"Processing data for report type {report_data_type}")
+ # we delete the report data for the current date to avoid duplicates
+ # entries in the database.
+ self.metadata.delete_report_data(report_data_type, self.date)
+ has_checked_and_handled_existing_es_data = False
try:
self.source = DataProcessor.create(
_data_processor_type=report_data_type.value, metadata=self.metadata
@@ -177,6 +228,11 @@ def _execute_data_processor(self):
if hasattr(self, "sink"):
self.sink.write_record(record)
if hasattr(self, "es_sink"):
+ if not has_checked_and_handled_existing_es_data:
+ self._check_and_handle_existing_es_data(
+ DataInsightEsIndex[record.data.__class__.__name__].value
+ )
+ has_checked_and_handled_existing_es_data = True
self.es_sink.write_record(record)
else:
logger.warning(
diff --git a/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py b/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py
index 10d6c7d12c89..e6d1a199c361 100644
--- a/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py
+++ b/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py
@@ -93,7 +93,7 @@ def run_test_case(
test_handler = TestHandler(
self.dfs,
test_case=test_case,
- execution_date=datetime.now(tz=timezone.utc).timestamp(),
+ execution_date=int(datetime.now(tz=timezone.utc).timestamp() * 1000),
)
return Validator(validator_obj=test_handler).validate()
diff --git a/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py b/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py
index 8da479a2dab8..9413625cfaa0 100644
--- a/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py
+++ b/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py
@@ -165,7 +165,7 @@ def run_test_case(
test_handler = TestHandler(
self.runner,
test_case=test_case,
- execution_date=datetime.now(tz=timezone.utc).timestamp(),
+ execution_date=int(datetime.now(tz=timezone.utc).timestamp() * 1000),
)
return Validator(validator_obj=test_handler).validate()
diff --git a/ingestion/src/metadata/ingestion/api/parser.py b/ingestion/src/metadata/ingestion/api/parser.py
index dfbc302c7f80..3743a331257c 100644
--- a/ingestion/src/metadata/ingestion/api/parser.py
+++ b/ingestion/src/metadata/ingestion/api/parser.py
@@ -42,10 +42,6 @@
PipelineConnection,
PipelineServiceType,
)
-from metadata.generated.schema.entity.services.searchService import (
- SearchConnection,
- SearchServiceType,
-)
from metadata.generated.schema.entity.services.storageService import (
StorageConnection,
StorageServiceType,
@@ -78,10 +74,6 @@
PipelineMetadataConfigType,
PipelineServiceMetadataPipeline,
)
-from metadata.generated.schema.metadataIngestion.searchServiceMetadataPipeline import (
- SearchMetadataConfigType,
- SearchServiceMetadataPipeline,
-)
from metadata.generated.schema.metadataIngestion.storageServiceMetadataPipeline import (
StorageMetadataConfigType,
StorageServiceMetadataPipeline,
@@ -110,7 +102,6 @@
**{service: PipelineConnection for service in PipelineServiceType.__members__},
**{service: MlModelConnection for service in MlModelServiceType.__members__},
**{service: StorageConnection for service in StorageServiceType.__members__},
- **{service: SearchConnection for service in SearchServiceType.__members__},
}
SOURCE_CONFIG_CLASS_MAP = {
@@ -122,7 +113,6 @@
MlModelMetadataConfigType.MlModelMetadata.value: MlModelServiceMetadataPipeline,
DatabaseMetadataConfigType.DatabaseMetadata.value: DatabaseServiceMetadataPipeline,
StorageMetadataConfigType.StorageMetadata.value: StorageServiceMetadataPipeline,
- SearchMetadataConfigType.SearchMetadata.value: SearchServiceMetadataPipeline,
}
diff --git a/ingestion/src/metadata/ingestion/models/search_index_data.py b/ingestion/src/metadata/ingestion/models/search_index_data.py
deleted file mode 100644
index 8d8ae80a3a4c..000000000000
--- a/ingestion/src/metadata/ingestion/models/search_index_data.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Model required to ingest search index sample data
-"""
-
-from pydantic import BaseModel
-
-from metadata.generated.schema.entity.data.searchIndex import (
- SearchIndex,
- SearchIndexSampleData,
-)
-
-
-class OMetaIndexSampleData(BaseModel):
- entity: SearchIndex
- data: SearchIndexSampleData
diff --git a/ingestion/src/metadata/ingestion/ometa/mixins/data_insight_mixin.py b/ingestion/src/metadata/ingestion/ometa/mixins/data_insight_mixin.py
index 9046ec580cda..09a52f46a913 100644
--- a/ingestion/src/metadata/ingestion/ometa/mixins/data_insight_mixin.py
+++ b/ingestion/src/metadata/ingestion/ometa/mixins/data_insight_mixin.py
@@ -19,7 +19,7 @@
from typing import List, Optional
from metadata.generated.schema.analytics.basic import WebAnalyticEventType
-from metadata.generated.schema.analytics.reportData import ReportData
+from metadata.generated.schema.analytics.reportData import ReportData, ReportDataType
from metadata.generated.schema.analytics.webAnalyticEventData import (
WebAnalyticEventData,
)
@@ -174,3 +174,14 @@ def delete_web_analytic_event_before_ts_exclusive(
"""
event_type_value = event_type.value
self.client.delete(f"/analytics/web/events/{event_type_value}/{tmsp}/collect")
+
+ def delete_report_data(self, report_data_type: ReportDataType, date: str) -> None:
+ """Delete report data at a specific date for a specific report data type
+
+ Args:
+ report_data_type (ReportDataType): report date type to delete
+ date (str): date for which to delete the report data
+ """
+ self.client.delete(
+ f"/analytics/dataInsights/data/{report_data_type.value}/{date}"
+ )
diff --git a/ingestion/src/metadata/ingestion/ometa/mixins/query_mixin.py b/ingestion/src/metadata/ingestion/ometa/mixins/query_mixin.py
index 72806148ec2f..5e7291e7eccb 100644
--- a/ingestion/src/metadata/ingestion/ometa/mixins/query_mixin.py
+++ b/ingestion/src/metadata/ingestion/ometa/mixins/query_mixin.py
@@ -13,7 +13,8 @@
To be used by OpenMetadata class
"""
-
+import hashlib
+import json
from typing import List, Optional, Union
from metadata.generated.schema.api.data.createQuery import CreateQueryRequest
@@ -22,6 +23,7 @@
from metadata.generated.schema.entity.data.table import Table
from metadata.generated.schema.type.basic import Uuid
from metadata.generated.schema.type.entityReference import EntityReference
+from metadata.ingestion.ometa.client import REST
from metadata.ingestion.ometa.utils import model_str
@@ -32,6 +34,21 @@ class OMetaQueryMixin:
To be inherited by OpenMetadata
"""
+ client: REST
+
+ def _get_query_hash(self, query: str) -> str:
+ result = hashlib.md5(query.encode())
+ return str(result.hexdigest())
+
+ def _get_or_create_query(self, query: CreateQueryRequest) -> Optional[Query]:
+ query_hash = self._get_query_hash(query=query.query.__root__)
+ query_entity = self.get_by_name(entity=Query, fqn=query_hash)
+ if query_entity is None:
+ resp = self.client.put(self.get_suffix(Query), data=query.json())
+ if resp and resp.get("id"):
+ query_entity = Query(**resp)
+ return query_entity
+
def ingest_entity_queries_data(
self, entity: Union[Table, Dashboard], queries: List[CreateQueryRequest]
) -> None:
@@ -42,16 +59,35 @@ def ingest_entity_queries_data(
:param queries: CreateQueryRequest to add
"""
for create_query in queries:
- query = self.client.put(self.get_suffix(Query), data=create_query.json())
- if query and query.get("id"):
+ query = self._get_or_create_query(create_query)
+ if query:
+ # Add Query Usage
table_ref = EntityReference(id=entity.id.__root__, type="table")
# convert object to json array string
table_ref_json = "[" + table_ref.json() + "]"
self.client.put(
- f"{self.get_suffix(Query)}/{query.get('id')}/usage",
+ f"{self.get_suffix(Query)}/{model_str(query.id)}/usage",
data=table_ref_json,
)
+ # Add Query Users
+ user_fqn_list = create_query.users
+ if user_fqn_list:
+ self.client.put(
+ f"{self.get_suffix(Query)}/{model_str(query.id)}/users",
+ data=json.dumps(
+ [model_str(user_fqn) for user_fqn in user_fqn_list]
+ ),
+ )
+
+ # Add Query used by
+ user_list = create_query.usedBy
+ if user_list:
+ self.client.put(
+ f"{self.get_suffix(Query)}/{model_str(query.id)}/usedBy",
+ data=json.dumps(user_list),
+ )
+
def get_entity_queries(
self, entity_id: Union[Uuid, str], fields: Optional[List[str]] = None
) -> Optional[List[Query]]:
diff --git a/ingestion/src/metadata/ingestion/ometa/mixins/search_index_mixin.py b/ingestion/src/metadata/ingestion/ometa/mixins/search_index_mixin.py
deleted file mode 100644
index 590b74ca509a..000000000000
--- a/ingestion/src/metadata/ingestion/ometa/mixins/search_index_mixin.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Mixin class containing Search Index specific methods
-
-To be used by OpenMetadata class
-"""
-import traceback
-from typing import Optional
-
-from metadata.generated.schema.entity.data.searchIndex import (
- SearchIndex,
- SearchIndexSampleData,
-)
-from metadata.ingestion.ometa.client import REST
-from metadata.utils.logger import ometa_logger
-
-logger = ometa_logger()
-
-
-class OMetaSearchIndexMixin:
- """
- OpenMetadata API methods related to search index.
-
- To be inherited by OpenMetadata
- """
-
- client: REST
-
- def ingest_search_index_sample_data(
- self, search_index: SearchIndex, sample_data: SearchIndexSampleData
- ) -> Optional[SearchIndexSampleData]:
- """
- PUT sample data for a search index
-
- :param search_index: SearchIndex Entity to update
- :param sample_data: Data to add
- """
- resp = None
- try:
- resp = self.client.put(
- f"{self.get_suffix(SearchIndex)}/{search_index.id.__root__}/sampleData",
- data=sample_data.json(),
- )
- except Exception as exc:
- logger.debug(traceback.format_exc())
- logger.warning(
- f"Error trying to PUT sample data for {search_index.fullyQualifiedName.__root__}: {exc}"
- )
-
- if resp:
- try:
- return SearchIndexSampleData(**resp["sampleData"])
- except UnicodeError as err:
- logger.debug(traceback.format_exc())
- logger.warning(
- "Unicode Error parsing the sample data response "
- f"from {search_index.fullyQualifiedName.__root__}: {err}"
- )
- except Exception as exc:
- logger.debug(traceback.format_exc())
- logger.warning(
- "Error trying to parse sample data results"
- f"from {search_index.fullyQualifiedName.__root__}: {exc}"
- )
-
- return None
diff --git a/ingestion/src/metadata/ingestion/ometa/mixins/table_mixin.py b/ingestion/src/metadata/ingestion/ometa/mixins/table_mixin.py
index f098bcf01e8a..511ceb30deb6 100644
--- a/ingestion/src/metadata/ingestion/ometa/mixins/table_mixin.py
+++ b/ingestion/src/metadata/ingestion/ometa/mixins/table_mixin.py
@@ -254,11 +254,6 @@ def get_profile_data(
url_after = f"&after={after}" if after else ""
profile_type_url = profile_type.__name__[0].lower() + profile_type.__name__[1:]
- # system profile uses milliseconds
- if profile_type is not SystemProfile:
- start_ts = start_ts // 1000
- end_ts = end_ts // 1000
-
resp = self.client.get(
f"{self.get_suffix(Table)}/{fqn}/{profile_type_url}?limit={limit}{url_after}",
data={"startTs": start_ts, "endTs": end_ts},
diff --git a/ingestion/src/metadata/ingestion/ometa/ometa_api.py b/ingestion/src/metadata/ingestion/ometa/ometa_api.py
index d614629fce24..e58e2344be25 100644
--- a/ingestion/src/metadata/ingestion/ometa/ometa_api.py
+++ b/ingestion/src/metadata/ingestion/ometa/ometa_api.py
@@ -42,7 +42,6 @@
from metadata.ingestion.ometa.mixins.pipeline_mixin import OMetaPipelineMixin
from metadata.ingestion.ometa.mixins.query_mixin import OMetaQueryMixin
from metadata.ingestion.ometa.mixins.role_policy_mixin import OMetaRolePolicyMixin
-from metadata.ingestion.ometa.mixins.search_index_mixin import OMetaSearchIndexMixin
from metadata.ingestion.ometa.mixins.server_mixin import OMetaServerMixin
from metadata.ingestion.ometa.mixins.service_mixin import OMetaServiceMixin
from metadata.ingestion.ometa.mixins.table_mixin import OMetaTableMixin
@@ -120,7 +119,6 @@ class OpenMetadata(
OMetaUserMixin,
OMetaQueryMixin,
OMetaRolePolicyMixin,
- OMetaSearchIndexMixin,
Generic[T, C],
):
"""
@@ -246,7 +244,6 @@ def get_entity_from_create(self, create: Type[C]) -> Type[T]:
.replace("testsuite", "testSuite")
.replace("testdefinition", "testDefinition")
.replace("testcase", "testCase")
- .replace("searchindex", "searchIndex")
)
class_path = ".".join(
diff --git a/ingestion/src/metadata/ingestion/ometa/routes.py b/ingestion/src/metadata/ingestion/ometa/routes.py
index b57b83214914..232c2562d138 100644
--- a/ingestion/src/metadata/ingestion/ometa/routes.py
+++ b/ingestion/src/metadata/ingestion/ometa/routes.py
@@ -38,9 +38,6 @@
from metadata.generated.schema.api.data.createMlModel import CreateMlModelRequest
from metadata.generated.schema.api.data.createPipeline import CreatePipelineRequest
from metadata.generated.schema.api.data.createQuery import CreateQueryRequest
-from metadata.generated.schema.api.data.createSearchIndex import (
- CreateSearchIndexRequest,
-)
from metadata.generated.schema.api.data.createTable import CreateTableRequest
from metadata.generated.schema.api.data.createTopic import CreateTopicRequest
from metadata.generated.schema.api.lineage.addLineage import AddLineageRequest
@@ -63,9 +60,6 @@
from metadata.generated.schema.api.services.createPipelineService import (
CreatePipelineServiceRequest,
)
-from metadata.generated.schema.api.services.createSearchService import (
- CreateSearchServiceRequest,
-)
from metadata.generated.schema.api.services.createStorageService import (
CreateStorageServiceRequest,
)
@@ -97,7 +91,6 @@
from metadata.generated.schema.entity.data.pipeline import Pipeline
from metadata.generated.schema.entity.data.query import Query
from metadata.generated.schema.entity.data.report import Report
-from metadata.generated.schema.entity.data.searchIndex import SearchIndex
from metadata.generated.schema.entity.data.table import Table
from metadata.generated.schema.entity.data.topic import Topic
from metadata.generated.schema.entity.policies.policy import Policy
@@ -113,7 +106,6 @@
from metadata.generated.schema.entity.services.metadataService import MetadataService
from metadata.generated.schema.entity.services.mlmodelService import MlModelService
from metadata.generated.schema.entity.services.pipelineService import PipelineService
-from metadata.generated.schema.entity.services.searchService import SearchService
from metadata.generated.schema.entity.services.storageService import StorageService
from metadata.generated.schema.entity.teams.role import Role
from metadata.generated.schema.entity.teams.team import Team
@@ -148,8 +140,6 @@
CreateQueryRequest.__name__: "/queries",
Container.__name__: "/containers",
CreateContainerRequest.__name__: "/containers",
- SearchIndex.__name__: "/searchIndexes",
- CreateSearchIndexRequest.__name__: "/searchIndexes",
# Classifications
Tag.__name__: "/tags",
CreateTagRequest.__name__: "/tags",
@@ -189,8 +179,6 @@
CreateMlModelServiceRequest.__name__: "/services/mlmodelServices",
MetadataService.__name__: "/services/metadataServices",
CreateMetadataServiceRequest.__name__: "/services/metadataServices",
- SearchService.__name__: "/services/searchServices",
- CreateSearchServiceRequest.__name__: "/services/searchServices",
IngestionPipeline.__name__: "/services/ingestionPipelines",
TestConnectionDefinition.__name__: "/services/testConnectionDefinitions",
# Data Quality
diff --git a/ingestion/src/metadata/ingestion/ometa/utils.py b/ingestion/src/metadata/ingestion/ometa/utils.py
index 43a8ee6085db..c5b1db7f52ae 100644
--- a/ingestion/src/metadata/ingestion/ometa/utils.py
+++ b/ingestion/src/metadata/ingestion/ometa/utils.py
@@ -31,7 +31,6 @@ def format_name(name: str) -> str:
return re.sub(r"[" + subs + "]", "_", name)
-# pylint: disable=too-many-return-statements
def get_entity_type(
entity: Union[Type[T], str],
) -> str:
@@ -55,8 +54,6 @@ def get_entity_type(
return class_name.replace("testsuite", "testSuite")
if "databaseschema" in class_name:
return class_name.replace("databaseschema", "databaseSchema")
- if "searchindex" in class_name:
- return class_name.replace("searchindex", "searchIndex")
return class_name
diff --git a/ingestion/src/metadata/ingestion/processor/query_parser.py b/ingestion/src/metadata/ingestion/processor/query_parser.py
index ca6cb6576824..1c9d427f1eb3 100644
--- a/ingestion/src/metadata/ingestion/processor/query_parser.py
+++ b/ingestion/src/metadata/ingestion/processor/query_parser.py
@@ -20,6 +20,7 @@
from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
OpenMetadataConnection,
)
+from metadata.generated.schema.type.basic import DateTime
from metadata.generated.schema.type.queryParserData import ParsedData, QueryParserData
from metadata.generated.schema.type.tableQuery import TableQueries, TableQuery
from metadata.ingestion.api.processor import Processor
@@ -40,11 +41,10 @@ def parse_sql_statement(record: TableQuery, dialect: Dialect) -> Optional[Parsed
:return: QueryParserData
"""
- start_date = record.analysisDate
- if isinstance(record.analysisDate, str):
- start_date = datetime.datetime.strptime(
- str(record.analysisDate), "%Y-%m-%d %H:%M:%S"
- ).date()
+ start_time = record.analysisDate
+ if isinstance(start_time, DateTime):
+ start_date = start_time.__root__.date()
+ start_time = datetime.datetime.strptime(str(start_date.isoformat()), "%Y-%m-%d")
lineage_parser = LineageParser(record.query, dialect=dialect)
@@ -58,7 +58,7 @@ def parse_sql_statement(record: TableQuery, dialect: Dialect) -> Optional[Parsed
databaseSchema=record.databaseSchema,
sql=record.query,
userName=record.userName,
- date=int(start_date.__root__.timestamp()),
+ date=int(start_time.timestamp()),
serviceName=record.serviceName,
duration=record.duration,
)
diff --git a/ingestion/src/metadata/ingestion/sink/metadata_rest.py b/ingestion/src/metadata/ingestion/sink/metadata_rest.py
index 962cd46654ab..10c047be6dcb 100644
--- a/ingestion/src/metadata/ingestion/sink/metadata_rest.py
+++ b/ingestion/src/metadata/ingestion/sink/metadata_rest.py
@@ -41,7 +41,6 @@
from metadata.ingestion.models.ometa_topic_data import OMetaTopicSampleData
from metadata.ingestion.models.pipeline_status import OMetaPipelineStatus
from metadata.ingestion.models.profile_data import OMetaTableProfileSampleData
-from metadata.ingestion.models.search_index_data import OMetaIndexSampleData
from metadata.ingestion.models.tests_data import (
OMetaLogicalTestSuiteSample,
OMetaTestCaseResultsSample,
@@ -110,9 +109,6 @@ def __init__(
OMetaTestCaseResultsSample, self.write_test_case_results_sample
)
self.write_record.register(OMetaTopicSampleData, self.write_topic_sample_data)
- self.write_record.register(
- OMetaIndexSampleData, self.write_search_index_sample_data
- )
@classmethod
def create(cls, config_dict: dict, metadata_config: OpenMetadataConnection):
@@ -471,27 +467,5 @@ def write_topic_sample_data(self, record: OMetaTopicSampleData):
f"Unexpected error while ingesting sample data for topic [{record.topic.name.__root__}]: {exc}"
)
- def write_search_index_sample_data(self, record: OMetaIndexSampleData):
- """
- Ingest Search Index Sample Data
- """
- try:
- if record.data.messages:
- self.metadata.ingest_search_index_sample_data(
- record.entity,
- record.data,
- )
- logger.debug(
- f"Successfully ingested sample data for {record.entity.name.__root__}"
- )
- self.status.records_written(
- f"SearchIndexSampleData: {record.entity.name.__root__}"
- )
- except Exception as exc:
- logger.debug(traceback.format_exc())
- logger.error(
- f"Unexpected error while ingesting sample data for search index [{record.entity.name.__root__}]: {exc}"
- )
-
def close(self):
pass
diff --git a/ingestion/src/metadata/ingestion/source/dashboard/tableau/models.py b/ingestion/src/metadata/ingestion/source/dashboard/tableau/models.py
index 32f514d964ab..caf0a74d3ab1 100644
--- a/ingestion/src/metadata/ingestion/source/dashboard/tableau/models.py
+++ b/ingestion/src/metadata/ingestion/source/dashboard/tableau/models.py
@@ -96,7 +96,6 @@ class DatasourceField(BaseModel):
id: str
name: Optional[str]
upstreamColumns: Optional[List[Union[UpstreamColumn, None]]]
- fullyQualifiedName: Optional[str]
description: Optional[str]
diff --git a/ingestion/src/metadata/ingestion/source/dashboard/tableau/queries.py b/ingestion/src/metadata/ingestion/source/dashboard/tableau/queries.py
index 573f57ad14ea..89dac28cd276 100644
--- a/ingestion/src/metadata/ingestion/source/dashboard/tableau/queries.py
+++ b/ingestion/src/metadata/ingestion/source/dashboard/tableau/queries.py
@@ -27,7 +27,6 @@
name
remoteType
}}
- fullyQualifiedName
description
}}
workbook {{
diff --git a/ingestion/src/metadata/ingestion/source/database/bigquery/connection.py b/ingestion/src/metadata/ingestion/source/database/bigquery/connection.py
index e12af299ef47..33c24e36ecfb 100644
--- a/ingestion/src/metadata/ingestion/source/database/bigquery/connection.py
+++ b/ingestion/src/metadata/ingestion/source/database/bigquery/connection.py
@@ -98,6 +98,8 @@ def test_connection(
Test connection. This can be executed either as part
of a metadata workflow or during an Automation Workflow
"""
+ _, project_ids = auth.default()
+ project_ids = project_ids if isinstance(project_ids, list) else [project_ids]
def get_tags(taxonomies):
for taxonomy in taxonomies:
@@ -107,42 +109,37 @@ def get_tags(taxonomies):
return policy_tags
def test_tags():
- list_project_ids = auth.default()
- project_id = list_project_ids[1]
-
- if isinstance(project_id, str):
- taxonomies = PolicyTagManagerClient().list_taxonomies(
- parent=f"projects/{project_id}/locations/{service_connection.taxonomyLocation}"
- )
- return get_tags(taxonomies)
-
- if isinstance(project_id, list):
+ for project in project_ids:
taxonomies = PolicyTagManagerClient().list_taxonomies(
- parent=f"projects/{project_id[0]}/locations/{service_connection.taxonomyLocation}"
+ parent=f"projects/{project}/locations/{service_connection.taxonomyLocation}"
)
-
return get_tags(taxonomies)
-
return None
- test_fn = {
- "CheckAccess": partial(test_connection_engine_step, engine),
- "GetSchemas": partial(execute_inspector_func, engine, "get_schema_names"),
- "GetTables": partial(execute_inspector_func, engine, "get_table_names"),
- "GetViews": partial(execute_inspector_func, engine, "get_view_names"),
- "GetTags": test_tags,
- "GetQueries": partial(
- test_query,
- engine=engine,
- statement=BIGQUERY_TEST_STATEMENT.format(
- region=service_connection.usageLocation
+ def test_connection_inner(engine):
+ test_fn = {
+ "CheckAccess": partial(test_connection_engine_step, engine),
+ "GetSchemas": partial(execute_inspector_func, engine, "get_schema_names"),
+ "GetTables": partial(execute_inspector_func, engine, "get_table_names"),
+ "GetViews": partial(execute_inspector_func, engine, "get_view_names"),
+ "GetTags": test_tags,
+ "GetQueries": partial(
+ test_query,
+ engine=engine,
+ statement=BIGQUERY_TEST_STATEMENT.format(
+ region=service_connection.usageLocation
+ ),
),
- ),
- }
-
- test_connection_steps(
- metadata=metadata,
- test_fn=test_fn,
- service_type=service_connection.type.value,
- automation_workflow=automation_workflow,
- )
+ }
+
+ test_connection_steps(
+ metadata=metadata,
+ test_fn=test_fn,
+ service_type=service_connection.type.value,
+ automation_workflow=automation_workflow,
+ )
+
+ for project in project_ids:
+ if project in str(engine.url):
+ continue
+ test_connection_inner(engine)
diff --git a/ingestion/src/metadata/ingestion/source/database/bigquery/metadata.py b/ingestion/src/metadata/ingestion/source/database/bigquery/metadata.py
index a9bba7575192..bfda62c173f4 100644
--- a/ingestion/src/metadata/ingestion/source/database/bigquery/metadata.py
+++ b/ingestion/src/metadata/ingestion/source/database/bigquery/metadata.py
@@ -44,12 +44,12 @@
)
from metadata.generated.schema.security.credentials.gcpValues import (
GcpCredentialsValues,
- MultipleProjectId,
SingleProjectId,
)
from metadata.generated.schema.type.tagLabel import TagLabel
from metadata.ingestion.api.source import InvalidSourceException
from metadata.ingestion.models.ometa_classification import OMetaTagAndClassification
+from metadata.ingestion.source.connections import get_connection
from metadata.ingestion.source.database.bigquery.queries import (
BIGQUERY_SCHEMA_DESCRIPTION,
BIGQUERY_TABLE_AND_TYPE,
@@ -200,9 +200,9 @@ def create(cls, config_dict, metadata_config: OpenMetadataConnection):
return cls(config, metadata_config)
@staticmethod
- def set_project_id():
+ def set_project_id() -> List[str]:
_, project_ids = auth.default()
- return project_ids
+ return project_ids if isinstance(project_ids, list) else [project_ids]
def query_table_names_and_types(
self, schema_name: str
@@ -222,7 +222,9 @@ def query_table_names_and_types(
type_=_bigquery_table_types.get(table_type, TableType.Regular),
)
for table_name, table_type in self.engine.execute(
- BIGQUERY_TABLE_AND_TYPE.format(schema_name)
+ BIGQUERY_TABLE_AND_TYPE.format(
+ project_id=self.client.project, schema_name=schema_name
+ )
)
or []
]
@@ -319,12 +321,41 @@ def yield_database_schema(
)
yield database_schema_request_obj
+ def get_table_obj(self, table_name: str):
+ schema_name = self.context.database_schema.name.__root__
+ database = self.context.database.name.__root__
+ bq_table_fqn = fqn._build(database, schema_name, table_name)
+ return self.client.get_table(bq_table_fqn)
+
+ def yield_table_tag_details(self, table_name_and_type: Tuple[str, str]):
+ table_name, _ = table_name_and_type
+ table_obj = self.get_table_obj(table_name=table_name)
+ if table_obj.labels:
+ for key, value in table_obj.labels.items():
+ yield from get_ometa_tag_and_classification(
+ tags=[value],
+ classification_name=key,
+ tag_description="Bigquery Table Label",
+ classification_desciption="",
+ )
+
def get_tag_labels(self, table_name: str) -> Optional[List[TagLabel]]:
"""
This will only get executed if the tags context
is properly informed
"""
- return []
+ table_tag_labels = super().get_tag_labels(table_name) or []
+ table_obj = self.get_table_obj(table_name=table_name)
+ if table_obj.labels:
+ for key, _ in table_obj.labels.items():
+ tag_label = get_tag_label(
+ metadata=self.metadata,
+ tag_name=key,
+ classification_name=key,
+ )
+ if tag_label:
+ table_tag_labels.append(tag_label)
+ return table_tag_labels
def get_column_tag_labels(
self, table_name: str, column: dict
@@ -366,45 +397,31 @@ def set_inspector(self, database_name: str):
)
self.client = get_bigquery_client(project_id=database_name, **kwargs)
+ self.engine = get_connection(self.service_connection)
self.inspector = inspect(self.engine)
def get_database_names(self) -> Iterable[str]:
- if hasattr(
- self.service_connection.credentials.gcpConfig, "projectId"
- ) and isinstance(
- self.service_connection.credentials.gcpConfig.projectId, MultipleProjectId
- ):
- for project_id in self.project_ids:
- database_name = project_id
- database_fqn = fqn.build(
- self.metadata,
- entity_type=Database,
- service_name=self.context.database_service.name.__root__,
- database_name=database_name,
- )
- if filter_by_database(
- self.source_config.databaseFilterPattern,
- database_fqn
- if self.source_config.useFqnForFiltering
- else database_name,
- ):
- self.status.filter(database_fqn, "Database Filtered out")
- continue
-
+ for project_id in self.project_ids:
+ database_fqn = fqn.build(
+ self.metadata,
+ entity_type=Database,
+ service_name=self.context.database_service.name.__root__,
+ database_name=project_id,
+ )
+ if filter_by_database(
+ self.source_config.databaseFilterPattern,
+ database_fqn if self.source_config.useFqnForFiltering else project_id,
+ ):
+ self.status.filter(database_fqn, "Database Filtered out")
+ else:
try:
- self.set_inspector(database_name=database_name)
- self.project_id = ( # pylint: disable=attribute-defined-outside-init
- database_name
- )
- yield database_name
+ self.set_inspector(database_name=project_id)
+ yield project_id
except Exception as exc:
logger.debug(traceback.format_exc())
logger.error(
- f"Error trying to connect to database {database_name}: {exc}"
+ f"Error trying to connect to database {project_id}: {exc}"
)
- else:
- self.set_inspector(database_name=self.project_ids)
- yield self.project_ids
def get_view_definition(
self, table_type: str, table_name: str, schema_name: str, inspector: Inspector
@@ -412,7 +429,9 @@ def get_view_definition(
if table_type == TableType.View:
try:
view_definition = inspector.get_view_definition(
- f"{self.context.database.name.__root__}.{schema_name}.{table_name}"
+ fqn._build(
+ self.context.database.name.__root__, schema_name, table_name
+ )
)
view_definition = (
"" if view_definition is None else str(view_definition)
@@ -430,7 +449,7 @@ def get_table_partition_details(
check if the table is partitioned table and return the partition details
"""
database = self.context.database.name.__root__
- table = self.client.get_table(f"{database}.{schema_name}.{table_name}")
+ table = self.client.get_table(fqn._build(database, schema_name, table_name))
if table.time_partitioning is not None:
if table.time_partitioning.field:
table_partition = TablePartition(
diff --git a/ingestion/src/metadata/ingestion/source/database/bigquery/queries.py b/ingestion/src/metadata/ingestion/source/database/bigquery/queries.py
index 6b33dc0e1467..c0186255e69e 100644
--- a/ingestion/src/metadata/ingestion/source/database/bigquery/queries.py
+++ b/ingestion/src/metadata/ingestion/source/database/bigquery/queries.py
@@ -45,7 +45,7 @@
BIGQUERY_SCHEMA_DESCRIPTION = textwrap.dedent(
"""
SELECT option_value as schema_description FROM
- {project_id}.region-{region}.INFORMATION_SCHEMA.SCHEMATA_OPTIONS
+ `{project_id}`.`region-{region}`.INFORMATION_SCHEMA.SCHEMATA_OPTIONS
where schema_name = '{schema_name}' and option_name = 'description'
and option_value is not null
"""
@@ -53,6 +53,6 @@
BIGQUERY_TABLE_AND_TYPE = textwrap.dedent(
"""
- select table_name, table_type from {}.INFORMATION_SCHEMA.TABLES where table_type != 'VIEW'
+ select table_name, table_type from `{project_id}`.{schema_name}.INFORMATION_SCHEMA.TABLES where table_type != 'VIEW'
"""
)
diff --git a/ingestion/src/metadata/ingestion/source/database/common_db_source.py b/ingestion/src/metadata/ingestion/source/database/common_db_source.py
index dea5e52e93ac..2d76cc19cadd 100644
--- a/ingestion/src/metadata/ingestion/source/database/common_db_source.py
+++ b/ingestion/src/metadata/ingestion/source/database/common_db_source.py
@@ -523,9 +523,6 @@ def standardize_table_name(self, schema_name: str, table: str) -> str:
"""
return table
- def yield_table_tag(self) -> Iterable[OMetaTagAndClassification]:
- pass
-
def get_source_url(
self,
database_name: Optional[str] = None,
diff --git a/ingestion/src/metadata/ingestion/source/database/database_service.py b/ingestion/src/metadata/ingestion/source/database/database_service.py
index 736de690516f..6719c4326529 100644
--- a/ingestion/src/metadata/ingestion/source/database/database_service.py
+++ b/ingestion/src/metadata/ingestion/source/database/database_service.py
@@ -112,7 +112,7 @@ class DatabaseServiceTopology(ServiceTopology):
NodeStage(
type_=OMetaTagAndClassification,
context="tags",
- processor="yield_tag_details",
+ processor="yield_database_schema_tag_details",
ack_sink=False,
nullable=True,
cache_all=True,
@@ -130,6 +130,14 @@ class DatabaseServiceTopology(ServiceTopology):
table = TopologyNode(
producer="get_tables_name_and_type",
stages=[
+ NodeStage(
+ type_=OMetaTagAndClassification,
+ context="tags",
+ processor="yield_table_tag_details",
+ ack_sink=False,
+ nullable=True,
+ cache_all=True,
+ ),
NodeStage(
type_=Table,
context="table",
@@ -218,7 +226,23 @@ def yield_tag(self, schema_name: str) -> Iterable[OMetaTagAndClassification]:
From topology. To be run for each schema
"""
- def yield_tag_details(
+ def yield_table_tags(
+ self, table_name_and_type: Tuple[str, TableType]
+ ) -> Iterable[CreateTableRequest]:
+ """
+ From topology. To be run for each table
+ """
+
+ def yield_table_tag_details(
+ self, table_name_and_type: str
+ ) -> Iterable[OMetaTagAndClassification]:
+ """
+ From topology. To be run for each table
+ """
+ if self.source_config.includeTags:
+ yield from self.yield_table_tags(table_name_and_type) or []
+
+ def yield_database_schema_tag_details(
self, schema_name: str
) -> Iterable[OMetaTagAndClassification]:
"""
@@ -267,7 +291,7 @@ def get_tag_by_fqn(self, entity_fqn: str) -> Optional[List[TagLabel]]:
tag_labels = []
for tag_and_category in self.context.tags or []:
- if tag_and_category.fqn.__root__ == entity_fqn:
+ if tag_and_category.fqn and tag_and_category.fqn.__root__ == entity_fqn:
tag_label = get_tag_label(
metadata=self.metadata,
tag_name=tag_and_category.tag_request.name.__root__,
diff --git a/ingestion/src/metadata/ingestion/source/database/sample_data.py b/ingestion/src/metadata/ingestion/source/database/sample_data.py
index 479289e9c156..a68aeb878ee3 100644
--- a/ingestion/src/metadata/ingestion/source/database/sample_data.py
+++ b/ingestion/src/metadata/ingestion/source/database/sample_data.py
@@ -34,9 +34,6 @@
)
from metadata.generated.schema.api.data.createMlModel import CreateMlModelRequest
from metadata.generated.schema.api.data.createPipeline import CreatePipelineRequest
-from metadata.generated.schema.api.data.createSearchIndex import (
- CreateSearchIndexRequest,
-)
from metadata.generated.schema.api.data.createTable import CreateTableRequest
from metadata.generated.schema.api.data.createTableProfile import (
CreateTableProfileRequest,
@@ -80,7 +77,6 @@
from metadata.generated.schema.entity.services.messagingService import MessagingService
from metadata.generated.schema.entity.services.mlmodelService import MlModelService
from metadata.generated.schema.entity.services.pipelineService import PipelineService
-from metadata.generated.schema.entity.services.searchService import SearchService
from metadata.generated.schema.entity.services.storageService import StorageService
from metadata.generated.schema.entity.teams.team import Team
from metadata.generated.schema.entity.teams.user import User
@@ -462,34 +458,6 @@ def __init__(self, config: WorkflowSource, metadata_config: OpenMetadataConnecti
)
)
- self.storage_service_json = json.load(
- open( # pylint: disable=consider-using-with
- sample_data_folder + "/storage/service.json",
- "r",
- encoding=UTF_8,
- )
- )
-
- self.search_service_json = json.load(
- open( # pylint: disable=consider-using-with
- sample_data_folder + "/searchIndexes/service.json",
- "r",
- encoding=UTF_8,
- )
- )
- self.search_service = self.metadata.get_service_or_create(
- entity=SearchService,
- config=WorkflowSource(**self.search_service_json),
- )
-
- self.search_indexes = json.load(
- open( # pylint: disable=consider-using-with
- sample_data_folder + "/searchIndexes/searchIndexes.json",
- "r",
- encoding=UTF_8,
- )
- )
-
@classmethod
def create(cls, config_dict, metadata_config: OpenMetadataConnection):
"""Create class instance"""
@@ -519,7 +487,6 @@ def next_record(self) -> Iterable[Entity]:
yield from self.ingest_pipeline_status()
yield from self.ingest_mlmodels()
yield from self.ingest_containers()
- yield from self.ingest_search_indexes()
yield from self.ingest_profiles()
yield from self.ingest_test_suite()
yield from self.ingest_test_case()
@@ -740,30 +707,6 @@ def ingest_topics(self) -> Iterable[CreateTopicRequest]:
sample_data=TopicSampleData(messages=topic["sampleData"]),
)
- def ingest_search_indexes(self) -> Iterable[CreateSearchIndexRequest]:
- """
- Ingest Sample SearchIndexes
- """
- for search_index in self.search_indexes["searchIndexes"]:
- search_index["service"] = EntityReference(
- id=self.search_service.id, type="searchService"
- )
- create_search_index = CreateSearchIndexRequest(
- name=search_index["name"],
- description=search_index["description"],
- displayName=search_index["displayName"],
- tags=search_index["tags"],
- fields=search_index["fields"],
- service=self.search_service.fullyQualifiedName,
- )
-
- self.status.scanned(
- f"SearchIndex Scanned: {create_search_index.name.__root__}"
- )
- yield create_search_index
-
- # TODO: Add search index sample data
-
def ingest_looker(self) -> Iterable[Entity]:
"""
Looker sample data
@@ -1178,15 +1121,22 @@ def ingest_profiles(self) -> Iterable[OMetaTableProfileSampleData]:
rowCount=profile["rowCount"],
createDateTime=profile.get("createDateTime"),
sizeInByte=profile.get("sizeInByte"),
- timestamp=(
- datetime.now(tz=timezone.utc) - timedelta(days=days)
- ).timestamp(),
+ timestamp=int(
+ (
+ datetime.now(tz=timezone.utc) - timedelta(days=days)
+ ).timestamp()
+ * 1000
+ ),
),
columnProfile=[
ColumnProfile(
- timestamp=(
- datetime.now(tz=timezone.utc) - timedelta(days=days)
- ).timestamp(),
+ timestamp=int(
+ (
+ datetime.now(tz=timezone.utc)
+ - timedelta(days=days)
+ ).timestamp()
+ * 1000
+ ),
**col_profile,
)
for col_profile in profile["columnProfile"]
@@ -1273,9 +1223,10 @@ def ingest_test_case_results(self) -> Iterable[OMetaTestCaseResultsSample]:
for days, result in enumerate(test_case_results["results"]):
yield OMetaTestCaseResultsSample(
test_case_results=TestCaseResult(
- timestamp=(
- datetime.now() - timedelta(days=days)
- ).timestamp(),
+ timestamp=int(
+ (datetime.now() - timedelta(days=days)).timestamp()
+ * 1000
+ ),
testCaseStatus=result["testCaseStatus"],
result=result["result"],
testResultValue=[
diff --git a/ingestion/src/metadata/ingestion/source/search/elasticsearch/connection.py b/ingestion/src/metadata/ingestion/source/search/elasticsearch/connection.py
deleted file mode 100644
index dabc89f17a44..000000000000
--- a/ingestion/src/metadata/ingestion/source/search/elasticsearch/connection.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Source connection handler
-"""
-from typing import Optional
-
-from elasticsearch import Elasticsearch
-
-from metadata.generated.schema.entity.automations.workflow import (
- Workflow as AutomationWorkflow,
-)
-from metadata.generated.schema.entity.services.connections.search.elasticSearchConnection import (
- ApiAuthentication,
- BasicAuthentication,
- ElasticsearchConnection,
-)
-from metadata.ingestion.connections.builders import init_empty_connection_arguments
-from metadata.ingestion.connections.test_connections import test_connection_steps
-from metadata.ingestion.ometa.ometa_api import OpenMetadata
-
-
-def get_connection(connection: ElasticsearchConnection) -> Elasticsearch:
- """
- Create connection
- """
- basic_auth = None
- api_key = None
- if isinstance(connection.authType, BasicAuthentication):
- basic_auth = (
- connection.authType.username,
- connection.authType.password.get_secret_value(),
- )
-
- if isinstance(connection.authType, ApiAuthentication):
- api_key = (
- connection.authType.apiKeyId,
- connection.authType.apiKey.get_secret_value(),
- )
-
- if not connection.connectionArguments:
- connection.connectionArguments = init_empty_connection_arguments()
-
- return Elasticsearch(
- [connection.hostPort],
- basic_auth=basic_auth,
- api_key=api_key,
- scheme=connection.scheme.value,
- **connection.connectionArguments.__root__
- )
-
-
-def test_connection(
- metadata: OpenMetadata,
- client: Elasticsearch,
- service_connection: ElasticsearchConnection,
- automation_workflow: Optional[AutomationWorkflow] = None,
-) -> None:
- """
- Test connection. This can be executed either as part
- of a metadata workflow or during an Automation Workflow
- """
-
- test_fn = {
- "CheckAccess": client.info,
- "GetSearchIndexes": client.indices.get_alias,
- }
-
- test_connection_steps(
- metadata=metadata,
- test_fn=test_fn,
- service_type=service_connection.type.value,
- automation_workflow=automation_workflow,
- )
diff --git a/ingestion/src/metadata/ingestion/source/search/elasticsearch/metadata.py b/ingestion/src/metadata/ingestion/source/search/elasticsearch/metadata.py
deleted file mode 100644
index dd58649aff1c..000000000000
--- a/ingestion/src/metadata/ingestion/source/search/elasticsearch/metadata.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Elasticsearch source to extract metadata
-"""
-from typing import Any, Iterable, Optional
-
-from elasticsearch import Elasticsearch
-
-from metadata.generated.schema.api.data.createSearchIndex import (
- CreateSearchIndexRequest,
-)
-from metadata.generated.schema.entity.data.searchIndex import SearchIndexSampleData
-from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
- OpenMetadataConnection,
-)
-from metadata.generated.schema.entity.services.connections.search.elasticSearchConnection import (
- ElasticsearchConnection,
-)
-from metadata.generated.schema.metadataIngestion.workflow import (
- Source as WorkflowSource,
-)
-from metadata.ingestion.api.source import InvalidSourceException, Source
-from metadata.ingestion.models.search_index_data import OMetaIndexSampleData
-from metadata.ingestion.source.search.elasticsearch.parser import parse_es_index_mapping
-from metadata.ingestion.source.search.search_service import SearchServiceSource
-from metadata.utils.logger import ingestion_logger
-
-logger = ingestion_logger()
-
-
-WILDCARD_SEARCH = "*"
-
-
-class ElasticsearchSource(SearchServiceSource):
- """
- Implements the necessary methods ot extract
- Search Index metadata from Elastic Search
- """
-
- def __init__(self, config: Source, metadata_config: OpenMetadataConnection):
- super().__init__(config, metadata_config)
- self.client: Elasticsearch = self.connection
-
- @classmethod
- def create(cls, config_dict, metadata_config: OpenMetadataConnection):
- config: WorkflowSource = WorkflowSource.parse_obj(config_dict)
- connection: ElasticsearchConnection = config.serviceConnection.__root__.config
- if not isinstance(connection, ElasticsearchConnection):
- raise InvalidSourceException(
- f"Expected ElasticsearchConnection, but got {connection}"
- )
- return cls(config, metadata_config)
-
- def get_search_index_list(self) -> Iterable[dict]:
- """
- Get List of all search index
- """
- index_list = self.client.indices.get_alias() or {}
- for index in index_list.keys():
- yield self.client.indices.get(index)
-
- def get_search_index_name(self, search_index_details: dict) -> Optional[str]:
- """
- Get Search Index Name
- """
- if search_index_details and len(search_index_details) == 1:
- return list(search_index_details.keys())[0]
-
- return None
-
- def yield_search_index(
- self, search_index_details: Any
- ) -> Iterable[CreateSearchIndexRequest]:
- """
- Method to Get Search Index Entity
- """
- index_name = self.get_search_index_name(search_index_details)
- if index_name:
- yield CreateSearchIndexRequest(
- name=index_name,
- displayName=index_name,
- searchIndexSettings=search_index_details.get(index_name, {}).get(
- "settings", {}
- ),
- service=self.context.search_service.fullyQualifiedName.__root__,
- fields=parse_es_index_mapping(
- search_index_details.get(index_name, {}).get("mappings")
- ),
- )
-
- def yield_search_index_sample_data(
- self, search_index_details: Any
- ) -> Iterable[OMetaIndexSampleData]:
- """
- Method to Get Sample Data of Search Index Entity
- """
- if self.source_config.includeSampleData and self.context.search_index:
-
- sample_data = self.client.search(
- index=self.context.search_index.name.__root__,
- q=WILDCARD_SEARCH,
- size=self.source_config.sampleSize,
- request_timeout=self.service_connection.connectionTimeoutSecs,
- )
-
- yield OMetaIndexSampleData(
- entity=self.context.search_index,
- data=SearchIndexSampleData(
- messages=[
- str(message)
- for message in sample_data.get("hits", {}).get("hits", [])
- ]
- ),
- )
diff --git a/ingestion/src/metadata/ingestion/source/search/elasticsearch/parser.py b/ingestion/src/metadata/ingestion/source/search/elasticsearch/parser.py
deleted file mode 100644
index 06b7aa7ce875..000000000000
--- a/ingestion/src/metadata/ingestion/source/search/elasticsearch/parser.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Utils module to parse the jsonschema
-"""
-
-import traceback
-from typing import List, Optional
-
-from metadata.generated.schema.entity.data.searchIndex import DataType, SearchIndexField
-from metadata.utils.logger import ingestion_logger
-
-logger = ingestion_logger()
-
-
-# If any type of ES field is not recognized mark it as unknown
-# pylint: disable=no-member,unused-argument,protected-access
-@classmethod
-def _missing_(cls, value):
- return cls.UNKNOWN
-
-
-DataType._missing_ = _missing_
-
-
-def parse_es_index_mapping(mapping: dict) -> Optional[List[SearchIndexField]]:
- """
- Recursively convert the parsed schema into required models
- """
- field_models = []
- try:
- properties = mapping.get("properties", {})
- for key, value in properties.items():
- data_type = (
- DataType(value.get("type").upper())
- if value.get("type")
- else DataType.OBJECT
- )
- field_models.append(
- SearchIndexField(
- name=key,
- dataType=data_type,
- description=value.get("description"),
- children=parse_es_index_mapping(value)
- if value.get("properties")
- else None,
- )
- )
- except Exception as exc: # pylint: disable=broad-except
- logger.debug(traceback.format_exc())
- logger.warning(f"Unable to parse the index properties: {exc}")
-
- return field_models
diff --git a/ingestion/src/metadata/ingestion/source/search/search_service.py b/ingestion/src/metadata/ingestion/source/search/search_service.py
deleted file mode 100644
index 5e59426479ad..000000000000
--- a/ingestion/src/metadata/ingestion/source/search/search_service.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Base class for ingesting search index services
-"""
-from abc import ABC, abstractmethod
-from typing import Any, Iterable, List, Optional, Set
-
-from metadata.generated.schema.api.data.createSearchIndex import (
- CreateSearchIndexRequest,
-)
-from metadata.generated.schema.entity.data.searchIndex import (
- SearchIndex,
- SearchIndexSampleData,
-)
-from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
- OpenMetadataConnection,
-)
-from metadata.generated.schema.entity.services.searchService import (
- SearchConnection,
- SearchService,
-)
-from metadata.generated.schema.metadataIngestion.searchServiceMetadataPipeline import (
- SearchServiceMetadataPipeline,
-)
-from metadata.generated.schema.metadataIngestion.workflow import (
- Source as WorkflowSource,
-)
-from metadata.ingestion.api.source import Source
-from metadata.ingestion.api.topology_runner import TopologyRunnerMixin
-from metadata.ingestion.models.delete_entity import (
- DeleteEntity,
- delete_entity_from_source,
-)
-from metadata.ingestion.models.search_index_data import OMetaIndexSampleData
-from metadata.ingestion.models.topology import (
- NodeStage,
- ServiceTopology,
- TopologyNode,
- create_source_context,
-)
-from metadata.ingestion.ometa.ometa_api import OpenMetadata
-from metadata.ingestion.source.connections import get_connection, get_test_connection_fn
-from metadata.utils import fqn
-from metadata.utils.filters import filter_by_search_index
-from metadata.utils.logger import ingestion_logger
-
-logger = ingestion_logger()
-
-
-class SearchServiceTopology(ServiceTopology):
- """
- Defines the hierarchy in Search Services.
-
- We could have a topology validator. We can only consume
- data that has been produced by any parent node.
- """
-
- root = TopologyNode(
- producer="get_services",
- stages=[
- NodeStage(
- type_=SearchService,
- context="search_service",
- processor="yield_create_request_search_service",
- overwrite=False,
- must_return=True,
- ),
- ],
- children=["search_index"],
- post_process=["mark_search_indexes_as_deleted"],
- )
- search_index = TopologyNode(
- producer="get_search_index",
- stages=[
- NodeStage(
- type_=SearchIndex,
- context="search_index",
- processor="yield_search_index",
- consumer=["search_service"],
- ),
- NodeStage(
- type_=OMetaIndexSampleData,
- context="search_index_sample_data",
- processor="yield_search_index_sample_data",
- consumer=["search_service"],
- ack_sink=False,
- nullable=True,
- ),
- ],
- )
-
-
-class SearchServiceSource(TopologyRunnerMixin, Source, ABC):
- """
- Base class for Search Services.
- It implements the topology and context.
- """
-
- source_config: SearchServiceMetadataPipeline
- config: WorkflowSource
- # Big union of types we want to fetch dynamically
- service_connection: SearchConnection.__fields__["config"].type_
-
- topology = SearchServiceTopology()
- context = create_source_context(topology)
- index_source_state: Set = set()
-
- def __init__(
- self,
- config: WorkflowSource,
- metadata_config: OpenMetadataConnection,
- ):
- super().__init__()
- self.config = config
- self.metadata_config = metadata_config
- self.metadata = OpenMetadata(metadata_config)
- self.source_config: SearchServiceMetadataPipeline = (
- self.config.sourceConfig.config
- )
- self.service_connection = self.config.serviceConnection.__root__.config
- self.connection = get_connection(self.service_connection)
-
- # Flag the connection for the test connection
- self.connection_obj = self.connection
- self.test_connection()
-
- @abstractmethod
- def yield_search_index(
- self, search_index_details: Any
- ) -> Iterable[CreateSearchIndexRequest]:
- """
- Method to Get Search Index Entity
- """
-
- def yield_search_index_sample_data(
- self, search_index_details: Any
- ) -> Iterable[SearchIndexSampleData]:
- """
- Method to Get Sample Data of Search Index Entity
- """
-
- @abstractmethod
- def get_search_index_list(self) -> Optional[List[Any]]:
- """
- Get List of all search index
- """
-
- @abstractmethod
- def get_search_index_name(self, search_index_details: Any) -> str:
- """
- Get Search Index Name
- """
-
- def get_search_index(self) -> Any:
- for index_details in self.get_search_index_list():
- search_index_name = self.get_search_index_name(index_details)
- if filter_by_search_index(
- self.source_config.searchIndexFilterPattern,
- search_index_name,
- ):
- self.status.filter(
- search_index_name,
- "Search Index Filtered Out",
- )
- continue
- yield index_details
-
- def yield_create_request_search_service(self, config: WorkflowSource):
- yield self.metadata.get_create_service_from_source(
- entity=SearchService, config=config
- )
-
- def get_services(self) -> Iterable[WorkflowSource]:
- yield self.config
-
- def prepare(self):
- """
- Nothing to prepare by default
- """
-
- def test_connection(self) -> None:
- test_connection_fn = get_test_connection_fn(self.service_connection)
- test_connection_fn(self.metadata, self.connection_obj, self.service_connection)
-
- def mark_search_indexes_as_deleted(self) -> Iterable[DeleteEntity]:
- """
- Method to mark the search index as deleted
- """
- if self.source_config.markDeletedSearchIndexes:
- yield from delete_entity_from_source(
- metadata=self.metadata,
- entity_type=SearchIndex,
- entity_source_state=self.index_source_state,
- mark_deleted_entity=self.source_config.markDeletedSearchIndexes,
- params={
- "service": self.context.search_service.fullyQualifiedName.__root__
- },
- )
-
- def register_record(self, search_index_request: CreateSearchIndexRequest) -> None:
- """
- Mark the search index record as scanned and update the index_source_state
- """
- index_fqn = fqn.build(
- self.metadata,
- entity_type=SearchIndex,
- service_name=search_index_request.service.__root__,
- search_index_name=search_index_request.name.__root__,
- )
-
- self.index_source_state.add(index_fqn)
- self.status.scanned(search_index_request.name.__root__)
-
- def close(self):
- """
- Nothing to close by default
- """
diff --git a/ingestion/src/metadata/ingestion/stage/table_usage.py b/ingestion/src/metadata/ingestion/stage/table_usage.py
index cc58a659b5fd..1c8b64e2cd08 100644
--- a/ingestion/src/metadata/ingestion/stage/table_usage.py
+++ b/ingestion/src/metadata/ingestion/stage/table_usage.py
@@ -18,6 +18,7 @@
import shutil
import traceback
from pathlib import Path
+from typing import List, Tuple
from metadata.config.common import ConfigModel
from metadata.generated.schema.api.data.createQuery import CreateQueryRequest
@@ -25,7 +26,7 @@
OpenMetadataConnection,
)
from metadata.generated.schema.entity.teams.user import User
-from metadata.generated.schema.type.queryParserData import QueryParserData
+from metadata.generated.schema.type.queryParserData import ParsedData, QueryParserData
from metadata.generated.schema.type.tableUsageCount import TableUsageCount
from metadata.ingestion.api.stage import Stage
from metadata.ingestion.ometa.ometa_api import OpenMetadata
@@ -80,20 +81,22 @@ def init_location(self) -> None:
logger.info(f"Creating the directory to store staging data in {location}")
location.mkdir(parents=True, exist_ok=True)
- def _get_user_entity(self, username: str):
+ def _get_user_entity(self, username: str) -> Tuple[List[str], List[str]]:
if username:
user = self.metadata.get_by_name(entity=User, fqn=username)
if user:
- return [user.fullyQualifiedName.__root__]
- return []
+ return [user.fullyQualifiedName.__root__], []
+ return [], [username]
def _add_sql_query(self, record, table):
+ users, used_by = self._get_user_entity(record.userName)
if self.table_queries.get((table, record.date)):
self.table_queries[(table, record.date)].append(
CreateQueryRequest(
query=record.sql,
- users=self._get_user_entity(record.userName),
+ users=users,
queryDate=record.date,
+ usedBy=used_by,
duration=record.duration,
)
)
@@ -101,13 +104,45 @@ def _add_sql_query(self, record, table):
self.table_queries[(table, record.date)] = [
CreateQueryRequest(
query=record.sql,
- users=self._get_user_entity(record.userName),
+ users=users,
queryDate=record.date,
+ usedBy=used_by,
duration=record.duration,
)
]
+ def _handle_table_usage(self, parsed_data: ParsedData, table: str) -> None:
+ table_joins = parsed_data.joins.get(table)
+ try:
+ self._add_sql_query(record=parsed_data, table=table)
+ table_usage_count = self.table_usage.get((table, parsed_data.date))
+ if table_usage_count is not None:
+ table_usage_count.count = table_usage_count.count + 1
+ if table_joins:
+ table_usage_count.joins.extend(table_joins)
+ else:
+ joins = []
+ if table_joins:
+ joins.extend(table_joins)
+
+ table_usage_count = TableUsageCount(
+ table=table,
+ databaseName=parsed_data.databaseName,
+ date=parsed_data.date,
+ joins=joins,
+ serviceName=parsed_data.serviceName,
+ sqlQueries=[],
+ databaseSchema=parsed_data.databaseSchema,
+ )
+
+ except Exception as exc:
+ logger.debug(traceback.format_exc())
+ logger.warning(f"Error in staging record: {exc}")
+ self.table_usage[(table, parsed_data.date)] = table_usage_count
+ logger.info(f"Successfully record staged for {table}")
+
def stage_record(self, record: QueryParserData) -> None:
+
"""
Process the parsed data and store it in a file
"""
@@ -119,34 +154,7 @@ def stage_record(self, record: QueryParserData) -> None:
if parsed_data is None:
continue
for table in parsed_data.tables:
- table_joins = parsed_data.joins.get(table)
- try:
- self._add_sql_query(record=parsed_data, table=table)
- table_usage_count = self.table_usage.get((table, parsed_data.date))
- if table_usage_count is not None:
- table_usage_count.count = table_usage_count.count + 1
- if table_joins:
- table_usage_count.joins.extend(table_joins)
- else:
- joins = []
- if table_joins:
- joins.extend(table_joins)
-
- table_usage_count = TableUsageCount(
- table=table,
- databaseName=parsed_data.databaseName,
- date=parsed_data.date,
- joins=joins,
- serviceName=parsed_data.serviceName,
- sqlQueries=[],
- databaseSchema=parsed_data.databaseSchema,
- )
-
- except Exception as exc:
- logger.debug(traceback.format_exc())
- logger.warning(f"Error in staging record: {exc}")
- self.table_usage[(table, parsed_data.date)] = table_usage_count
- logger.info(f"Successfully record staged for {table}")
+ self._handle_table_usage(parsed_data=parsed_data, table=table)
self.dump_data_to_file()
def dump_data_to_file(self):
diff --git a/ingestion/src/metadata/profiler/interface/pandas/profiler_interface.py b/ingestion/src/metadata/profiler/interface/pandas/profiler_interface.py
index dbbecb9a292a..f29fe0c4c2b8 100644
--- a/ingestion/src/metadata/profiler/interface/pandas/profiler_interface.py
+++ b/ingestion/src/metadata/profiler/interface/pandas/profiler_interface.py
@@ -8,6 +8,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+# pylint: disable=arguments-differ
"""
Interfaces with database for all database engine
@@ -32,7 +33,6 @@
from metadata.profiler.processor.sampler.sampler_factory import sampler_factory_
from metadata.readers.dataframe.models import DatalakeTableSchemaWrapper
from metadata.utils.datalake.datalake_utils import fetch_dataframe
-from metadata.utils.dispatch import valuedispatch
from metadata.utils.logger import profiler_interface_registry_logger
from metadata.utils.sqa_like_column import SQALikeColumn, Type
@@ -109,20 +109,10 @@ def _get_sampler(self):
profile_sample_query=self.profile_query,
)
- @valuedispatch
- def _get_metrics(self, *args, **kwargs):
- """Generic getter method for metrics. To be used with
- specific dispatch methods
- """
- logger.warning("Could not get metric. No function registered.")
-
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Table.value)
- def _(
+ def _compute_table_metrics(
self,
- metric_type: str,
metrics: List[Metrics],
- dfs,
+ runner: List,
*args,
**kwargs,
):
@@ -138,7 +128,7 @@ def _(
try:
row_dict = {}
- df_list = [df.where(pd.notnull(df), None) for df in dfs]
+ df_list = [df.where(pd.notnull(df), None) for df in runner]
for metric in metrics:
row_dict[metric.name()] = metric().df_fn(df_list)
return row_dict
@@ -147,13 +137,10 @@ def _(
logger.warning(f"Error trying to compute profile for {exc}")
raise RuntimeError(exc)
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Static.value)
- def _(
+ def _compute_static_metrics(
self,
- metric_type: str,
metrics: List[Metrics],
- dfs,
+ runner: List,
column,
*args,
**kwargs,
@@ -172,7 +159,7 @@ def _(
try:
row_dict = {}
for metric in metrics:
- metric_resp = metric(column).df_fn(dfs)
+ metric_resp = metric(column).df_fn(runner)
row_dict[metric.name()] = (
None if pd.isnull(metric_resp) else metric_resp
)
@@ -183,13 +170,10 @@ def _(
)
raise RuntimeError(exc)
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Query.value)
- def _(
+ def _compute_query_metrics(
self,
- metric_type: str,
- metrics: Metrics,
- dfs,
+ metric: Metrics,
+ runner: List,
column,
*args,
**kwargs,
@@ -204,18 +188,15 @@ def _(
dictionnary of results
"""
col_metric = None
- col_metric = metrics(column).df_fn(dfs)
+ col_metric = metric(column).df_fn(runner)
if not col_metric:
return None
- return {metrics.name(): col_metric}
+ return {metric.name(): col_metric}
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Window.value)
- def _(
+ def _compute_window_metrics(
self,
- metric_type: str,
- metrics: Metrics,
- dfs,
+ metrics: List[Metrics],
+ runner: List,
column,
*args,
**kwargs,
@@ -224,19 +205,21 @@ def _(
Given a list of metrics, compute the given results
and returns the values
"""
+
try:
metric_values = {}
for metric in metrics:
- metric_values[metric.name()] = metric(column).df_fn(dfs)
+ metric_values[metric.name()] = metric(column).df_fn(runner)
return metric_values if metric_values else None
except Exception as exc:
logger.debug(traceback.format_exc())
logger.warning(f"Unexpected exception computing metrics: {exc}")
return None
- @_get_metrics.register(MetricTypes.System.value)
- def _(
+ def _compute_system_metrics(
self,
+ metrics: Metrics,
+ runner: List,
*args,
**kwargs,
):
@@ -260,11 +243,9 @@ def compute_metrics(
try:
row = None
if self.dfs:
- row = self._get_metrics(
- metric_type.value,
+ row = self._get_metric_fn[metric_type.value](
metrics,
dfs,
- session=self.client,
column=column,
)
except Exception as exc:
@@ -354,7 +335,9 @@ def get_all_metrics(
profile_results["columns"][column].update(
{
"name": column,
- "timestamp": datetime.now(tz=timezone.utc).timestamp(),
+ "timestamp": int(
+ datetime.now(tz=timezone.utc).timestamp() * 1000
+ ),
**profile,
}
)
diff --git a/ingestion/src/metadata/profiler/interface/profiler_interface.py b/ingestion/src/metadata/profiler/interface/profiler_interface.py
index 5befd078bccf..85bb77d138e4 100644
--- a/ingestion/src/metadata/profiler/interface/profiler_interface.py
+++ b/ingestion/src/metadata/profiler/interface/profiler_interface.py
@@ -15,7 +15,7 @@
"""
from abc import ABC, abstractmethod
-from typing import Dict, Optional, Union
+from typing import Dict, List, Optional, Union
from sqlalchemy import Column
from typing_extensions import Self
@@ -36,7 +36,9 @@
from metadata.ingestion.ometa.ometa_api import OpenMetadata
from metadata.ingestion.source.connections import get_connection
from metadata.profiler.api.models import ProfileSampleConfig, TableConfig
+from metadata.profiler.metrics.core import MetricTypes
from metadata.profiler.metrics.registry import Metrics
+from metadata.profiler.processor.runner import QueryRunner
from metadata.utils.partition import get_partition_details
@@ -78,6 +80,14 @@ def __init__(
)
self.timeout_seconds = timeout_seconds
+ self._get_metric_fn = {
+ MetricTypes.Table.value: self._compute_table_metrics,
+ MetricTypes.Static.value: self._compute_static_metrics,
+ MetricTypes.Query.value: self._compute_query_metrics,
+ MetricTypes.Window.value: self._compute_window_metrics,
+ MetricTypes.System.value: self._compute_system_metrics,
+ }
+
@abstractmethod
def _get_sampler(self):
"""Get the sampler"""
@@ -222,7 +232,57 @@ def table(self):
raise NotImplementedError
@abstractmethod
- def _get_metrics(self, *args, **kwargs):
+ def _compute_table_metrics(
+ self,
+ metrics: List[Metrics],
+ runner,
+ *args,
+ **kwargs,
+ ):
+ """Get metrics"""
+ raise NotImplementedError
+
+ @abstractmethod
+ def _compute_static_metrics(
+ self,
+ metrics: List[Metrics],
+ runner,
+ *args,
+ **kwargs,
+ ):
+ """Get metrics"""
+ raise NotImplementedError
+
+ @abstractmethod
+ def _compute_query_metrics(
+ self,
+ metric: Metrics,
+ runner,
+ *args,
+ **kwargs,
+ ):
+ """Get metrics"""
+ raise NotImplementedError
+
+ @abstractmethod
+ def _compute_window_metrics(
+ self,
+ metrics: List[Metrics],
+ runner: QueryRunner,
+ *args,
+ **kwargs,
+ ):
+ """Get metrics"""
+ raise NotImplementedError
+
+ @abstractmethod
+ def _compute_system_metrics(
+ self,
+ metrics: Metrics,
+ runner,
+ *args,
+ **kwargs,
+ ):
"""Get metrics"""
raise NotImplementedError
diff --git a/ingestion/src/metadata/profiler/interface/profiler_interface_factory.py b/ingestion/src/metadata/profiler/interface/profiler_interface_factory.py
index 684e6be3643c..15d7aff293cf 100644
--- a/ingestion/src/metadata/profiler/interface/profiler_interface_factory.py
+++ b/ingestion/src/metadata/profiler/interface/profiler_interface_factory.py
@@ -21,6 +21,9 @@
from metadata.generated.schema.entity.services.connections.database.datalakeConnection import (
DatalakeConnection,
)
+from metadata.generated.schema.entity.services.connections.database.singleStoreConnection import (
+ SingleStoreConnection,
+)
from metadata.generated.schema.entity.services.databaseService import DatabaseConnection
from metadata.profiler.interface.pandas.profiler_interface import (
PandasProfilerInterface,
@@ -32,6 +35,9 @@
from metadata.profiler.interface.sqlalchemy.profiler_interface import (
SQAProfilerInterface,
)
+from metadata.profiler.interface.sqlalchemy.single_store.profiler_interface import (
+ SingleStoreProfilerInterface,
+)
class ProfilerInterfaceFactory:
@@ -58,6 +64,9 @@ def create(self, interface_type: str, *args, **kwargs):
profiler_interface_factory.register(
BigQueryConnection.__name__, BigQueryProfilerInterface
)
+profiler_interface_factory.register(
+ SingleStoreConnection.__name__, SingleStoreProfilerInterface
+)
profiler_interface_factory.register(
DatalakeConnection.__name__, PandasProfilerInterface
)
diff --git a/ingestion/src/metadata/profiler/interface/sqlalchemy/profiler_interface.py b/ingestion/src/metadata/profiler/interface/sqlalchemy/profiler_interface.py
index 99f26c96ea58..09a6c814fcbe 100644
--- a/ingestion/src/metadata/profiler/interface/sqlalchemy/profiler_interface.py
+++ b/ingestion/src/metadata/profiler/interface/sqlalchemy/profiler_interface.py
@@ -8,6 +8,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+# pylint: disable=arguments-differ
"""
Interfaces with database for all database engine
@@ -40,7 +41,6 @@
from metadata.profiler.processor.runner import QueryRunner
from metadata.profiler.processor.sampler.sampler_factory import sampler_factory_
from metadata.utils.custom_thread_pool import CustomThreadPoolExecutor
-from metadata.utils.dispatch import valuedispatch
from metadata.utils.logger import profiler_interface_registry_logger
logger = profiler_interface_registry_logger()
@@ -153,18 +153,8 @@ def _compute_static_metrics_wo_sum(
handle_query_exception(msg, exc, session)
return None
- @valuedispatch
- def _get_metrics(self, *args, **kwargs):
- """Generic getter method for metrics. To be used with
- specific dispatch methods
- """
- logger.warning("Could not get metric. No function registered.")
-
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Table.value)
- def _(
+ def _compute_table_metrics(
self,
- metric_type: str,
metrics: List[Metrics],
runner: QueryRunner,
session,
@@ -180,7 +170,6 @@ def _(
dictionnary of results
"""
# pylint: disable=protected-access
-
try:
dialect = runner._session.get_bind().dialect.name
row = table_metric_construct_factory.construct(
@@ -201,15 +190,12 @@ def _(
session.rollback()
raise RuntimeError(exc)
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Static.value)
- def _(
+ def _compute_static_metrics(
self,
- metric_type: str,
metrics: List[Metrics],
runner: QueryRunner,
+ column,
session,
- column: Column,
*args,
**kwargs,
):
@@ -247,16 +233,15 @@ def _(
handle_query_exception(msg, exc, session)
return None
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Query.value)
- def _(
+ def _compute_query_metrics(
self,
- metric_type: str,
metric: Metrics,
runner: QueryRunner,
+ column,
session,
- column: Column,
sample,
+ *args,
+ **kwargs,
):
"""Given a list of metrics, compute the given results
and returns the values
@@ -267,6 +252,7 @@ def _(
Returns:
dictionnary of results
"""
+
try:
col_metric = metric(column)
metric_query = col_metric.query(sample=sample, session=session)
@@ -284,15 +270,12 @@ def _(
handle_query_exception(msg, exc, session)
return None
- # pylint: disable=unused-argument
- @_get_metrics.register(MetricTypes.Window.value)
- def _(
+ def _compute_window_metrics(
self,
- metric_type: str,
metrics: List[Metrics],
runner: QueryRunner,
+ column,
session,
- column: Column,
*args,
**kwargs,
):
@@ -305,6 +288,7 @@ def _(
Returns:
dictionnary of results
"""
+
if not metrics:
return None
try:
@@ -327,11 +311,9 @@ def _(
return dict(row)
return None
- @_get_metrics.register(MetricTypes.System.value)
- def _(
+ def _compute_system_metrics(
self,
- metric_type: str,
- metric: Metrics,
+ metrics: Metrics,
runner: QueryRunner,
session,
*args,
@@ -348,7 +330,7 @@ def _(
dictionnary of results
"""
try:
- rows = metric().sql(session, conn_config=self.service_connection_config)
+ rows = metrics().sql(session, conn_config=self.service_connection_config)
return rows
except Exception as exc:
msg = f"Error trying to compute profile for {runner.table.__tablename__}: {exc}"
@@ -412,8 +394,7 @@ def compute_metrics_in_thread(
)
try:
- row = self._get_metrics(
- metric_type.value,
+ row = self._get_metric_fn[metric_type.value](
metrics,
runner=runner,
session=session,
@@ -471,7 +452,9 @@ def get_all_metrics(
profile_results["columns"][column].update(
{
"name": column,
- "timestamp": datetime.now(tz=timezone.utc).timestamp(),
+ "timestamp": int(
+ datetime.now(tz=timezone.utc).timestamp() * 1000
+ ),
**profile,
}
)
diff --git a/bootstrap/sql/migrations/native/1.1.3/mysql/postDataMigrationSQLScript.sql b/ingestion/src/metadata/profiler/interface/sqlalchemy/single_store/__init__.py
similarity index 100%
rename from bootstrap/sql/migrations/native/1.1.3/mysql/postDataMigrationSQLScript.sql
rename to ingestion/src/metadata/profiler/interface/sqlalchemy/single_store/__init__.py
diff --git a/ingestion/src/metadata/profiler/interface/sqlalchemy/single_store/profiler_interface.py b/ingestion/src/metadata/profiler/interface/sqlalchemy/single_store/profiler_interface.py
new file mode 100644
index 000000000000..6d87883a2e34
--- /dev/null
+++ b/ingestion/src/metadata/profiler/interface/sqlalchemy/single_store/profiler_interface.py
@@ -0,0 +1,86 @@
+# Copyright 2021 Collate
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Interfaces with database for all database engine
+supporting sqlalchemy abstraction layer
+"""
+
+from typing import List
+
+from sqlalchemy.exc import ProgrammingError
+
+from metadata.profiler.interface.sqlalchemy.profiler_interface import (
+ SQAProfilerInterface,
+ handle_query_exception,
+)
+from metadata.profiler.metrics.registry import Metrics
+from metadata.profiler.processor.runner import QueryRunner
+from metadata.profiler.source.single_store.metrics.window.first_quartile import (
+ SingleStoreFirstQuartile,
+)
+from metadata.profiler.source.single_store.metrics.window.median import (
+ SingleStoreMedian,
+)
+from metadata.profiler.source.single_store.metrics.window.third_quartile import (
+ SingleStoreThirdQuartile,
+)
+from metadata.utils.logger import profiler_interface_registry_logger
+
+logger = profiler_interface_registry_logger()
+
+
+class SingleStoreProfilerInterface(SQAProfilerInterface):
+ """
+ Interface to interact with registry supporting
+ sqlalchemy.
+ """
+
+ def _compute_window_metrics(
+ self,
+ metrics: List[Metrics],
+ runner: QueryRunner,
+ *args,
+ **kwargs,
+ ):
+ """Given a list of metrics, compute the given results
+ and returns the values
+
+ Args:
+ column: the column to compute the metrics against
+ metrics: list of metrics to compute
+ Returns:
+ dictionnary of results
+ """
+ session = kwargs.get("session")
+ column = kwargs.get("column")
+
+ if not metrics:
+ return None
+ try:
+ # we patch the metrics at runtime to use the SingleStore specific functions
+ # as we can't compile the query based on the dialect as it return `mysql`
+ metrics = [SingleStoreFirstQuartile, SingleStoreMedian, SingleStoreThirdQuartile] # type: ignore
+ row = runner.select_first_from_sample(
+ *[metric(column).fn() for metric in metrics],
+ )
+ except ProgrammingError:
+ logger.info(
+ f"Skipping window metrics for {runner.table.__tablename__}.{column.name} due to overflow"
+ )
+ return None
+
+ except Exception as exc:
+ msg = f"Error trying to compute profile for {runner.table.__tablename__}.{column.name}: {exc}"
+ handle_query_exception(msg, exc, session)
+ if row:
+ return dict(row)
+ return None
diff --git a/ingestion/src/metadata/profiler/metrics/system/queries/redshift.py b/ingestion/src/metadata/profiler/metrics/system/queries/redshift.py
index c6a3d61fa4db..1e76ab80b98d 100644
--- a/ingestion/src/metadata/profiler/metrics/system/queries/redshift.py
+++ b/ingestion/src/metadata/profiler/metrics/system/queries/redshift.py
@@ -35,19 +35,17 @@
sti."database",
sti."schema",
sti."table",
- sq.text,
DATE_TRUNC('second', data.starttime) AS starttime
FROM
data
INNER JOIN pg_catalog.svv_table_info sti ON data.tbl = sti.table_id
- INNER JOIN pg_catalog.stl_querytext sq ON data.query = sq.query
where
sti."database" = '{database}' AND
sti."schema" = '{schema}' AND
"rows" != 0 AND
DATE(data.starttime) >= CURRENT_DATE - 1
- GROUP BY 2,3,4,5,6
- ORDER BY 6 DESC
+ GROUP BY 2,3,4,5
+ ORDER BY 5 DESC
"""
@@ -73,7 +71,7 @@ def get_query_results(
database_name=row.database,
schema_name=row.schema,
table_name=row.table,
- query_text=row.text,
+ query_text=None,
query_type=operation,
timestamp=row.starttime,
rows=row.rows,
diff --git a/ingestion/src/metadata/profiler/metrics/system/system.py b/ingestion/src/metadata/profiler/metrics/system/system.py
index 5f3b7608c319..b57a4ae38571 100644
--- a/ingestion/src/metadata/profiler/metrics/system/system.py
+++ b/ingestion/src/metadata/profiler/metrics/system/system.py
@@ -113,10 +113,6 @@ def _(
dataset_id = table.__table_args__["schema"] # type: ignore
metric_results: List[Dict] = []
- # QueryResult = namedtuple(
- # "QueryResult",
- # "query_type,timestamp,destination_table,dml_statistics",
- # )
jobs = get_value_from_cache(
SYSTEM_QUERY_RESULT_CACHE, f"{Dialects.BigQuery}.{project_id}.{dataset_id}.jobs"
diff --git a/ingestion/src/metadata/profiler/metrics/window/first_quartile.py b/ingestion/src/metadata/profiler/metrics/window/first_quartile.py
index c508f276ec0a..ecfce32fff17 100644
--- a/ingestion/src/metadata/profiler/metrics/window/first_quartile.py
+++ b/ingestion/src/metadata/profiler/metrics/window/first_quartile.py
@@ -19,15 +19,15 @@
from sqlalchemy import column
from metadata.profiler.metrics.core import StaticMetric, _label
+from metadata.profiler.metrics.window.percentille_mixin import PercentilMixin
from metadata.profiler.orm.functions.length import LenFn
-from metadata.profiler.orm.functions.median import MedianFn
from metadata.profiler.orm.registry import is_concatenable, is_quantifiable
from metadata.utils.logger import profiler_logger
logger = profiler_logger()
-class FirstQuartile(StaticMetric):
+class FirstQuartile(StaticMetric, PercentilMixin):
"""
First Quartile Metric
@@ -53,14 +53,14 @@ def fn(self):
"""sqlalchemy function"""
if is_quantifiable(self.col.type):
# col fullname is only needed for MySQL and SQLite
- return MedianFn(
+ return self._compute_sqa_fn(
column(self.col.name, self.col.type),
self.col.table.fullname if self.col.table is not None else None,
0.25,
)
if is_concatenable(self.col.type):
- return MedianFn(
+ return self._compute_sqa_fn(
LenFn(column(self.col.name, self.col.type)),
self.col.table.fullname if self.col.table is not None else None,
0.25,
diff --git a/ingestion/src/metadata/profiler/metrics/window/median.py b/ingestion/src/metadata/profiler/metrics/window/median.py
index 1a4c623e581b..e8d4f42e946e 100644
--- a/ingestion/src/metadata/profiler/metrics/window/median.py
+++ b/ingestion/src/metadata/profiler/metrics/window/median.py
@@ -19,15 +19,15 @@
from sqlalchemy import column
from metadata.profiler.metrics.core import StaticMetric, _label
+from metadata.profiler.metrics.window.percentille_mixin import PercentilMixin
from metadata.profiler.orm.functions.length import LenFn
-from metadata.profiler.orm.functions.median import MedianFn
from metadata.profiler.orm.registry import is_concatenable, is_quantifiable
from metadata.utils.logger import profiler_logger
logger = profiler_logger()
-class Median(StaticMetric):
+class Median(StaticMetric, PercentilMixin):
"""
Median Metric
@@ -53,14 +53,14 @@ def fn(self):
"""sqlalchemy function"""
if is_quantifiable(self.col.type):
# col fullname is only needed for MySQL and SQLite
- return MedianFn(
+ return self._compute_sqa_fn(
column(self.col.name, self.col.type),
self.col.table.fullname if self.col.table is not None else None,
0.5,
)
if is_concatenable(self.col.type):
- return MedianFn(
+ return self._compute_sqa_fn(
LenFn(column(self.col.name, self.col.type)),
self.col.table.fullname if self.col.table is not None else None,
0.5,
diff --git a/ingestion/src/metadata/profiler/metrics/window/percentille_mixin.py b/ingestion/src/metadata/profiler/metrics/window/percentille_mixin.py
new file mode 100644
index 000000000000..38a98ccc8ce7
--- /dev/null
+++ b/ingestion/src/metadata/profiler/metrics/window/percentille_mixin.py
@@ -0,0 +1,9 @@
+"""function calls shared accross all percentile metrics"""
+
+from metadata.profiler.orm.functions.median import MedianFn
+
+
+class PercentilMixin:
+ def _compute_sqa_fn(self, column, table, percentile):
+ """Generic method to compute the quartile using sqlalchemy"""
+ return MedianFn(column, table, percentile)
diff --git a/ingestion/src/metadata/profiler/metrics/window/third_quartile.py b/ingestion/src/metadata/profiler/metrics/window/third_quartile.py
index d571739419de..1a7b06094e9a 100644
--- a/ingestion/src/metadata/profiler/metrics/window/third_quartile.py
+++ b/ingestion/src/metadata/profiler/metrics/window/third_quartile.py
@@ -19,15 +19,15 @@
from sqlalchemy import column
from metadata.profiler.metrics.core import StaticMetric, _label
+from metadata.profiler.metrics.window.percentille_mixin import PercentilMixin
from metadata.profiler.orm.functions.length import LenFn
-from metadata.profiler.orm.functions.median import MedianFn
from metadata.profiler.orm.registry import is_concatenable, is_quantifiable
from metadata.utils.logger import profiler_logger
logger = profiler_logger()
-class ThirdQuartile(StaticMetric):
+class ThirdQuartile(StaticMetric, PercentilMixin):
"""
Third Quartile Metric
@@ -53,14 +53,14 @@ def fn(self):
"""sqlalchemy function"""
if is_quantifiable(self.col.type):
# col fullname is only needed for MySQL and SQLite
- return MedianFn(
+ return self._compute_sqa_fn(
column(self.col.name, self.col.type),
self.col.table.fullname if self.col.table is not None else None,
0.75,
)
if is_concatenable(self.col.type):
- return MedianFn(
+ return self._compute_sqa_fn(
LenFn(column(self.col.name, self.col.type)),
self.col.table.fullname if self.col.table is not None else None,
0.75,
diff --git a/ingestion/src/metadata/profiler/processor/core.py b/ingestion/src/metadata/profiler/processor/core.py
index 342295ae14d3..8a8354c02429 100644
--- a/ingestion/src/metadata/profiler/processor/core.py
+++ b/ingestion/src/metadata/profiler/processor/core.py
@@ -92,7 +92,7 @@ def __init__(
self.include_columns = include_columns
self.exclude_columns = exclude_columns
self._metrics = metrics
- self._profile_date = datetime.now(tz=timezone.utc).timestamp()
+ self._profile_date = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
self.profile_sample_config = self.profiler_interface.profile_sample_config
self.validate_composed_metric()
diff --git a/ingestion/src/metadata/profiler/source/single_store/functions/median.py b/ingestion/src/metadata/profiler/source/single_store/functions/median.py
new file mode 100644
index 000000000000..cd509d782e5d
--- /dev/null
+++ b/ingestion/src/metadata/profiler/source/single_store/functions/median.py
@@ -0,0 +1,17 @@
+"""Median function for single store"""
+
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy.sql.functions import FunctionElement
+
+from metadata.profiler.metrics.core import CACHE
+
+
+class SingleStoreMedianFn(FunctionElement):
+ inherit_cache = CACHE
+
+
+@compiles(SingleStoreMedianFn)
+def _(elements, compiler, **kwargs): # pylint: disable=unused-argument
+ col = compiler.process(elements.clauses.clauses[0])
+ percentile = elements.clauses.clauses[2].value
+ return f"approx_percentile({col}, {percentile:.2f})"
diff --git a/ingestion/src/metadata/profiler/source/single_store/metrics/window/first_quartile.py b/ingestion/src/metadata/profiler/source/single_store/metrics/window/first_quartile.py
new file mode 100644
index 000000000000..3bfd15f1bcbf
--- /dev/null
+++ b/ingestion/src/metadata/profiler/source/single_store/metrics/window/first_quartile.py
@@ -0,0 +1,10 @@
+"""Override first quartile metric definition for SingleStore"""
+
+from metadata.profiler.metrics.window.first_quartile import FirstQuartile
+from metadata.profiler.source.single_store.functions.median import SingleStoreMedianFn
+
+
+class SingleStoreFirstQuartile(FirstQuartile):
+ def _compute_sqa_fn(self, column, table, percentile):
+ """Generic method to compute the quartile using sqlalchemy"""
+ return SingleStoreMedianFn(column, table, percentile)
diff --git a/ingestion/src/metadata/profiler/source/single_store/metrics/window/median.py b/ingestion/src/metadata/profiler/source/single_store/metrics/window/median.py
new file mode 100644
index 000000000000..843fb971b5f6
--- /dev/null
+++ b/ingestion/src/metadata/profiler/source/single_store/metrics/window/median.py
@@ -0,0 +1,10 @@
+"""Override first quartile metric definition for SingleStore"""
+
+from metadata.profiler.metrics.window.median import Median
+from metadata.profiler.source.single_store.functions.median import SingleStoreMedianFn
+
+
+class SingleStoreMedian(Median):
+ def _compute_sqa_fn(self, column, table, percentile):
+ """Generic method to compute the quartile using sqlalchemy"""
+ return SingleStoreMedianFn(column, table, percentile)
diff --git a/ingestion/src/metadata/profiler/source/single_store/metrics/window/third_quartile.py b/ingestion/src/metadata/profiler/source/single_store/metrics/window/third_quartile.py
new file mode 100644
index 000000000000..c8c8ef53274d
--- /dev/null
+++ b/ingestion/src/metadata/profiler/source/single_store/metrics/window/third_quartile.py
@@ -0,0 +1,10 @@
+"""Override first quartile metric definition for SingleStore"""
+
+from metadata.profiler.metrics.window.third_quartile import ThirdQuartile
+from metadata.profiler.source.single_store.functions.median import SingleStoreMedianFn
+
+
+class SingleStoreThirdQuartile(ThirdQuartile):
+ def _compute_sqa_fn(self, column, table, percentile):
+ """Generic method to compute the quartile using sqlalchemy"""
+ return SingleStoreMedianFn(column, table, percentile)
diff --git a/ingestion/src/metadata/utils/fqn.py b/ingestion/src/metadata/utils/fqn.py
index 9e92393b0c91..347d78b34322 100644
--- a/ingestion/src/metadata/utils/fqn.py
+++ b/ingestion/src/metadata/utils/fqn.py
@@ -33,7 +33,6 @@
from metadata.generated.schema.entity.data.databaseSchema import DatabaseSchema
from metadata.generated.schema.entity.data.mlmodel import MlModel
from metadata.generated.schema.entity.data.pipeline import Pipeline
-from metadata.generated.schema.entity.data.searchIndex import SearchIndex
from metadata.generated.schema.entity.data.table import Column, DataModel, Table
from metadata.generated.schema.entity.data.topic import Topic
from metadata.generated.schema.entity.teams.team import Team
@@ -265,20 +264,6 @@ def _(
return _build(service_name, topic_name)
-@fqn_build_registry.add(SearchIndex)
-def _(
- _: OpenMetadata, # ES Index not necessary for Search Index FQN building
- *,
- service_name: str,
- search_index_name: str,
-) -> str:
- if not service_name or not search_index_name:
- raise FQNBuildingException(
- f"Args should be informed, but got service=`{service_name}`, search_index=`{search_index_name}``"
- )
- return _build(service_name, search_index_name)
-
-
@fqn_build_registry.add(Tag)
def _(
_: OpenMetadata, # ES Index not necessary for Tag FQN building
diff --git a/ingestion/tests/integration/data_insight/test_data_insight_workflow.py b/ingestion/tests/integration/data_insight/test_data_insight_workflow.py
index 448589f66940..9373d845ae00 100644
--- a/ingestion/tests/integration/data_insight/test_data_insight_workflow.py
+++ b/ingestion/tests/integration/data_insight/test_data_insight_workflow.py
@@ -359,6 +359,32 @@ def test_write_kpi_result(self):
assert kpi_result
+ def test_multiple_execution(self) -> None:
+ """test multiple execution of the workflow is not yielding duplicate entries"""
+ data = {}
+
+ workflow: DataInsightWorkflow = DataInsightWorkflow.create(data_insight_config)
+ workflow.execute()
+ workflow.stop()
+ sleep(2) # we'll wait for 2 seconds
+ new_workflow: DataInsightWorkflow = DataInsightWorkflow.create(
+ data_insight_config
+ )
+ new_workflow.execute()
+ new_workflow.stop()
+
+ for report_data_type in ReportDataType:
+ data[report_data_type] = self.metadata.get_data_insight_report_data(
+ self.start_ts,
+ self.end_ts,
+ report_data_type.value,
+ )
+
+ for _, values in data.items():
+ timestamp = [value.get("timestamp") for value in values.get("data")]
+ # we'll check we only have 1 execution timestamp
+ assert len(set(timestamp)) == 1
+
@classmethod
def tearDownClass(cls) -> None:
kpis: list[Kpi] = cls.metadata.list_entities(
diff --git a/ingestion/tests/integration/orm_profiler/test_orm_profiler_e2e.py b/ingestion/tests/integration/orm_profiler/test_orm_profiler_e2e.py
index 5fedf2a96a6e..fea999391411 100644
--- a/ingestion/tests/integration/orm_profiler/test_orm_profiler_e2e.py
+++ b/ingestion/tests/integration/orm_profiler/test_orm_profiler_e2e.py
@@ -212,9 +212,6 @@ def test_ingestion(self):
)
assert table_entity.fullyQualifiedName.__root__ == "test_sqlite.main.main.users"
- @pytest.mark.skip(
- "need to reactivate once https://github.com/open-metadata/OpenMetadata/issues/8930 is handled. Skipping to prevent Cypress failure"
- )
def test_profiler_workflow(self):
"""
Prepare and execute the profiler workflow
@@ -539,8 +536,7 @@ def test_workflow_values_partition(self):
).profile
assert profile.rowCount == 4.0
- # uncomment when reactivate once https://github.com/open-metadata/OpenMetadata/issues/8930 is fixed
- # assert profile.profileSample is None
+ assert profile.profileSample is None
workflow_config["processor"] = {
"type": "orm-profiler",
diff --git a/ingestion/tests/unit/topology/search/test_elasticsearch.py b/ingestion/tests/unit/topology/search/test_elasticsearch.py
deleted file mode 100644
index 8b0896dbfd9e..000000000000
--- a/ingestion/tests/unit/topology/search/test_elasticsearch.py
+++ /dev/null
@@ -1,218 +0,0 @@
-# Copyright 2021 Collate
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Test ES using the topology
-"""
-
-from unittest import TestCase
-from unittest.mock import patch
-
-from metadata.generated.schema.api.data.createSearchIndex import (
- CreateSearchIndexRequest,
-)
-from metadata.generated.schema.entity.data.searchIndex import DataType, SearchIndexField
-from metadata.generated.schema.entity.services.searchService import (
- SearchConnection,
- SearchService,
- SearchServiceType,
-)
-from metadata.generated.schema.metadataIngestion.workflow import (
- OpenMetadataWorkflowConfig,
-)
-from metadata.ingestion.source.search.elasticsearch.metadata import ElasticsearchSource
-
-mock_es_config = {
- "source": {
- "type": "elasticsearch",
- "serviceName": "local_elasticsearch",
- "serviceConnection": {
- "config": {
- "type": "ElasticSearch",
- "authType": {
- "username": "username",
- "password": "password",
- },
- "hostPort": "localhost:9200",
- }
- },
- "sourceConfig": {"config": {"type": "SearchMetadata"}},
- },
- "sink": {"type": "metadata-rest", "config": {}},
- "workflowConfig": {
- "openMetadataServerConfig": {
- "hostPort": "http://localhost:8585/api",
- "authProvider": "openmetadata",
- "securityConfig": {
- "jwtToken": "eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlzQm90IjpmYWxzZSwiaXNzIjoib3Blbi1tZXRhZGF0YS5vcmciLCJpYXQiOjE2NjM5Mzg0NjIsImVtYWlsIjoiYWRtaW5Ab3Blbm1ldGFkYXRhLm9yZyJ9.tS8um_5DKu7HgzGBzS1VTA5uUjKWOCU0B_j08WXBiEC0mr0zNREkqVfwFDD-d24HlNEbrqioLsBuFRiwIWKc1m_ZlVQbG7P36RUxhuv2vbSp80FKyNM-Tj93FDzq91jsyNmsQhyNv_fNr3TXfzzSPjHt8Go0FMMP66weoKMgW2PbXlhVKwEuXUHyakLLzewm9UMeQaEiRzhiTMU3UkLXcKbYEJJvfNFcLwSl9W8JCO_l0Yj3ud-qt_nQYEZwqW6u5nfdQllN133iikV4fM5QZsMCnm8Rq1mvLR0y9bmJiD7fwM1tmJ791TUWqmKaTnP49U493VanKpUAfzIiOiIbhg"
- },
- }
- },
-}
-
-MOCK_SETTINGS = {
- "index": {
- "routing": {"allocation": {"include": {"_tier_preference": "data_content"}}},
- "number_of_shards": "1",
- "provided_name": "test_case_search_index",
- "creation_date": "1692181190239",
- "analysis": {
- "filter": {"om_stemmer": {"name": "english", "type": "stemmer"}},
- "normalizer": {
- "lowercase_normalizer": {
- "filter": ["lowercase"],
- "type": "custom",
- "char_filter": [],
- }
- },
- "analyzer": {
- "om_ngram": {
- "filter": ["lowercase"],
- "min_gram": "1",
- "max_gram": "2",
- "tokenizer": "ngram",
- },
- "om_analyzer": {
- "filter": ["lowercase", "om_stemmer"],
- "tokenizer": "letter",
- },
- },
- },
- "number_of_replicas": "1",
- "uuid": "8HAGhnVkSy-X__XwWFdJqg",
- "version": {"created": "7160399"},
- }
-}
-
-MOCK_DETAILS = {
- "test_case_search_index": {
- "aliases": {},
- "mappings": {
- "properties": {
- "href": {"type": "text"},
- "name": {
- "type": "text",
- "fields": {
- "keyword": {"type": "keyword", "ignore_above": 256},
- "ngram": {"type": "text", "analyzer": "om_ngram"},
- },
- "analyzer": "om_analyzer",
- },
- "owner": {
- "properties": {
- "deleted": {"type": "text"},
- "description": {"type": "text"},
- "displayName": {
- "type": "text",
- "fields": {
- "keyword": {"type": "keyword", "ignore_above": 256}
- },
- },
- "fullyQualifiedName": {"type": "text"},
- "href": {"type": "text"},
- "id": {"type": "text"},
- "name": {
- "type": "keyword",
- "normalizer": "lowercase_normalizer",
- "fields": {
- "keyword": {"type": "keyword", "ignore_above": 256}
- },
- },
- "type": {"type": "keyword"},
- }
- },
- }
- },
- "settings": MOCK_SETTINGS,
- }
-}
-
-MOCK_SEARCH_SERVICE = SearchService(
- id="85811038-099a-11ed-861d-0242ac120002",
- name="es_source",
- fullyQualifiedName="es_source",
- connection=SearchConnection(),
- serviceType=SearchServiceType.ElasticSearch,
-)
-
-EXPECTED_RESULT = CreateSearchIndexRequest(
- name="test_case_search_index",
- displayName="test_case_search_index",
- searchIndexSettings=MOCK_SETTINGS,
- service="es_source",
- fields=[
- SearchIndexField(
- name="href",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="name",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="owner",
- dataType=DataType.OBJECT,
- children=[
- SearchIndexField(
- name="deleted",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="description",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="displayName",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="fullyQualifiedName",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="href",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="id",
- dataType=DataType.TEXT,
- ),
- SearchIndexField(
- name="name",
- dataType=DataType.KEYWORD,
- ),
- SearchIndexField(
- name="type",
- dataType=DataType.KEYWORD,
- ),
- ],
- ),
- ],
-)
-
-
-class ElasticSearchUnitTest(TestCase):
- @patch(
- "metadata.ingestion.source.search.search_service.SearchServiceSource.test_connection"
- )
- def __init__(self, methodName, test_connection) -> None:
- super().__init__(methodName)
- test_connection.return_value = False
- self.config = OpenMetadataWorkflowConfig.parse_obj(mock_es_config)
- self.es_source = ElasticsearchSource.create(
- mock_es_config["source"],
- self.config.workflowConfig.openMetadataServerConfig,
- )
- self.es_source.context.__dict__["search_service"] = MOCK_SEARCH_SERVICE
-
- def test_partition_parse_columns(self):
- actual_index = self.es_source.yield_search_index(MOCK_DETAILS)
- self.assertEqual(list(actual_index), [EXPECTED_RESULT])
diff --git a/openmetadata-airflow-apis/setup.py b/openmetadata-airflow-apis/setup.py
index 656cee745926..b6f715971cff 100644
--- a/openmetadata-airflow-apis/setup.py
+++ b/openmetadata-airflow-apis/setup.py
@@ -69,7 +69,7 @@ def get_long_description():
packages=find_packages(include=[f"{PLUGIN_NAME}.*", PLUGIN_NAME]),
include_package_data=True,
package_data={PLUGIN_NAME: get_package_data()},
- version="1.2.0.0.dev0",
+ version="1.1.5.0",
url="https://open-metadata.org/",
author="OpenMetadata Committers",
license="Apache License 2.0",
diff --git a/openmetadata-clients/openmetadata-java-client/pom.xml b/openmetadata-clients/openmetadata-java-client/pom.xml
index 071c045f8b23..6a43e8f617ae 100644
--- a/openmetadata-clients/openmetadata-java-client/pom.xml
+++ b/openmetadata-clients/openmetadata-java-client/pom.xml
@@ -5,7 +5,7 @@
openmetadata-clients
org.open-metadata
- 1.2.0-SNAPSHOT
+ 1.1.5
4.0.0
diff --git a/openmetadata-clients/pom.xml b/openmetadata-clients/pom.xml
index 9cdfc403afdf..dc812a02c3ea 100644
--- a/openmetadata-clients/pom.xml
+++ b/openmetadata-clients/pom.xml
@@ -5,7 +5,7 @@
platform
org.open-metadata
- 1.2.0-SNAPSHOT
+ 1.1.5
4.0.0
diff --git a/openmetadata-dist/pom.xml b/openmetadata-dist/pom.xml
index 1790aa5d8674..e3e53c5adb8f 100644
--- a/openmetadata-dist/pom.xml
+++ b/openmetadata-dist/pom.xml
@@ -20,7 +20,7 @@
platform
org.open-metadata
- 1.2.0-SNAPSHOT
+ 1.1.5
openmetadata-dist
diff --git a/openmetadata-service/pom.xml b/openmetadata-service/pom.xml
index 7703b087b177..9f8dad39eb17 100644
--- a/openmetadata-service/pom.xml
+++ b/openmetadata-service/pom.xml
@@ -5,7 +5,7 @@
platform
org.open-metadata
- 1.2.0-SNAPSHOT
+ 1.1.5
4.0.0
openmetadata-service
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
index 3f973f607d2f..82902048722c 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/Entity.java
@@ -72,13 +72,7 @@ public final class Entity {
public static final String FIELD_DISPLAY_NAME = "displayName";
public static final String FIELD_EXTENSION = "extension";
public static final String FIELD_USAGE_SUMMARY = "usageSummary";
- public static final String FIELD_CHILDREN = "children";
- public static final String FIELD_PARENT = "parent";
public static final String FIELD_REVIEWERS = "reviewers";
- public static final String FIELD_EXPERTS = "experts";
- public static final String FIELD_DOMAIN = "domain";
- public static final String FIELD_DATA_PRODUCTS = "dataProducts";
- public static final String FIELD_ASSETS = "assets";
//
// Service entities
@@ -90,7 +84,6 @@ public final class Entity {
public static final String STORAGE_SERVICE = "storageService";
public static final String MLMODEL_SERVICE = "mlmodelService";
public static final String METADATA_SERVICE = "metadataService";
- public static final String SEARCH_SERVICE = "searchService";
//
// Data asset entities
//
@@ -104,7 +97,6 @@ public final class Entity {
public static final String CHART = "chart";
public static final String REPORT = "report";
public static final String TOPIC = "topic";
- public static final String SEARCH_INDEX = "searchIndex";
public static final String MLMODEL = "mlmodel";
public static final String CONTAINER = "container";
public static final String QUERY = "query";
@@ -141,12 +133,6 @@ public final class Entity {
//
public static final String INGESTION_PIPELINE = "ingestionPipeline";
- //
- // Domain related entities
- //
- public static final String DOMAIN = "domain";
- public static final String DATA_PRODUCT = "dataProduct";
-
//
// Other entities
public static final String EVENT_SUBSCRIPTION = "eventsubscription";
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java
index 46c87036a614..2db69b59ea0b 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java
@@ -464,11 +464,12 @@ private void initializeWebsockets(OpenMetadataApplicationConfig catalogConfig, E
WebSocketUpgradeFilter.configure(environment.getApplicationContext());
NativeWebSocketServletContainerInitializer.configure(
environment.getApplicationContext(),
- (context, container) ->
- container.addMapping(
- new ServletPathSpec(pathSpec),
- (servletUpgradeRequest, servletUpgradeResponse) ->
- new JettyWebSocketHandler(WebSocketManager.getInstance().getEngineIoServer())));
+ (context, container) -> {
+ container.addMapping(
+ new ServletPathSpec(pathSpec),
+ (servletUpgradeRequest, servletUpgradeResponse) ->
+ new JettyWebSocketHandler(WebSocketManager.getInstance().getEngineIoServer()));
+ });
} catch (ServletException ex) {
LOG.error("Websocket Upgrade Filter error : " + ex.getMessage());
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertsRuleEvaluator.java b/openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertsRuleEvaluator.java
index d50ba7c0aedc..5960a4d1919f 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertsRuleEvaluator.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertsRuleEvaluator.java
@@ -10,6 +10,7 @@
import static org.openmetadata.service.Entity.USER;
import java.io.IOException;
+import java.util.List;
import java.util.Set;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@@ -19,6 +20,7 @@
import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatusType;
import org.openmetadata.schema.entity.teams.Team;
import org.openmetadata.schema.entity.teams.User;
+import org.openmetadata.schema.tests.TestCase;
import org.openmetadata.schema.tests.type.TestCaseResult;
import org.openmetadata.schema.tests.type.TestCaseStatus;
import org.openmetadata.schema.type.ChangeEvent;
@@ -27,6 +29,7 @@
import org.openmetadata.schema.type.Include;
import org.openmetadata.service.Entity;
import org.openmetadata.service.formatter.util.FormatterUtil;
+import org.openmetadata.service.resources.feeds.MessageParser;
import org.openmetadata.service.util.JsonUtils;
@Slf4j
@@ -100,6 +103,10 @@ public boolean matchAnyEntityFqn(String... entityNames) throws IOException {
}
EntityInterface entity = getEntity(changeEvent);
for (String name : entityNames) {
+ if (changeEvent.getEntityType().equals(TEST_CASE)
+ && (MessageParser.EntityLink.parse(((TestCase) entity).getEntityLink()).getEntityFQN().equals(name))) {
+ return true;
+ }
if (entity.getFullyQualifiedName().equals(name)) {
return true;
}
@@ -159,7 +166,14 @@ public boolean matchTestResult(String... testResults) {
// in case the entity is not test case return since the filter doesn't apply
return true;
}
- for (FieldChange fieldChange : changeEvent.getChangeDescription().getFieldsUpdated()) {
+
+ // we need to handle both fields updated and fields added
+ List fieldChanges = changeEvent.getChangeDescription().getFieldsUpdated();
+ if (!changeEvent.getChangeDescription().getFieldsAdded().isEmpty()) {
+ fieldChanges.addAll(changeEvent.getChangeDescription().getFieldsAdded());
+ }
+
+ for (FieldChange fieldChange : fieldChanges) {
if (fieldChange.getName().equals("testCaseResult") && fieldChange.getNewValue() != null) {
TestCaseResult testCaseResult = (TestCaseResult) fieldChange.getNewValue();
TestCaseStatus status = testCaseResult.getTestCaseStatus();
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java
index 38d10c811926..9bc123e4ba4c 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java
@@ -13,8 +13,6 @@
package org.openmetadata.service.jdbi3;
-import static org.openmetadata.schema.type.Include.ALL;
-
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.entity.data.Chart;
@@ -61,12 +59,6 @@ public void storeRelationships(Chart chart) {
addRelationship(service.getId(), chart.getId(), service.getType(), Entity.CHART, Relationship.CONTAINS);
}
- @Override
- public Chart setInheritedFields(Chart chart, Fields fields) {
- DashboardService dashboardService = Entity.getEntity(chart.getService(), "domain", ALL);
- return inheritDomain(chart, fields, dashboardService);
- }
-
@Override
public Chart setFields(Chart chart, Fields fields) {
return chart.withService(getContainer(chart.getId()));
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
index 18bb0badcd82..00d6a3fd71e8 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java
@@ -79,11 +79,8 @@
import org.openmetadata.schema.entity.data.Pipeline;
import org.openmetadata.schema.entity.data.Query;
import org.openmetadata.schema.entity.data.Report;
-import org.openmetadata.schema.entity.data.SearchIndex;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.data.Topic;
-import org.openmetadata.schema.entity.domains.DataProduct;
-import org.openmetadata.schema.entity.domains.Domain;
import org.openmetadata.schema.entity.events.EventSubscription;
import org.openmetadata.schema.entity.policies.Policy;
import org.openmetadata.schema.entity.services.DashboardService;
@@ -92,7 +89,6 @@
import org.openmetadata.schema.entity.services.MetadataService;
import org.openmetadata.schema.entity.services.MlModelService;
import org.openmetadata.schema.entity.services.PipelineService;
-import org.openmetadata.schema.entity.services.SearchService;
import org.openmetadata.schema.entity.services.StorageService;
import org.openmetadata.schema.entity.services.connections.TestConnectionDefinition;
import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline;
@@ -104,6 +100,7 @@
import org.openmetadata.schema.tests.TestCase;
import org.openmetadata.schema.tests.TestDefinition;
import org.openmetadata.schema.tests.TestSuite;
+import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.Relationship;
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.schema.type.TaskStatus;
@@ -145,6 +142,15 @@ public interface CollectionDAO {
@CreateSqlObject
EntityExtensionTimeSeriesDAO entityExtensionTimeSeriesDao();
+ @CreateSqlObject
+ ReportDataTimeSeriesDAO reportDataTimeSeriesDao();
+
+ @CreateSqlObject
+ ProfilerDataTimeSeriesDAO profilerDataTimeSeriesDao();
+
+ @CreateSqlObject
+ DataQualityDataTimeSeriesDAO dataQualityDataTimeSeriesDao();
+
@CreateSqlObject
RoleDAO roleDAO();
@@ -190,9 +196,6 @@ public interface CollectionDAO {
@CreateSqlObject
MlModelDAO mlModelDAO();
- @CreateSqlObject
- SearchIndexDAO searchIndexDAO();
-
@CreateSqlObject
GlossaryDAO glossaryDAO();
@@ -202,12 +205,6 @@ public interface CollectionDAO {
@CreateSqlObject
BotDAO botDAO();
- @CreateSqlObject
- DomainDAO domainDAO();
-
- @CreateSqlObject
- DataProductDAO dataProductDAO();
-
@CreateSqlObject
EventSubscriptionDAO eventSubscriptionDAO();
@@ -238,9 +235,6 @@ public interface CollectionDAO {
@CreateSqlObject
StorageServiceDAO storageServiceDAO();
- @CreateSqlObject
- SearchServiceDAO searchServiceDAO();
-
@CreateSqlObject
ContainerDAO containerDAO();
@@ -452,6 +446,7 @@ default List listBefore(ListFilter filter, int limit, String before) {
}
String sqlCondition = String.format("%s AND er.toId is NULL", condition);
+
return listBefore(getTableName(), getNameColumn(), sqlCondition, limit, before);
}
@@ -538,40 +533,6 @@ int listCount(
@Define("sqlCondition") String mysqlCond);
}
- interface SearchServiceDAO extends EntityDAO {
- @Override
- default String getTableName() {
- return "search_service_entity";
- }
-
- @Override
- default Class getEntityClass() {
- return SearchService.class;
- }
-
- @Override
- default String getNameHashColumn() {
- return "nameHash";
- }
- }
-
- interface SearchIndexDAO extends EntityDAO {
- @Override
- default String getTableName() {
- return "search_index_entity";
- }
-
- @Override
- default Class getEntityClass() {
- return SearchIndex.class;
- }
-
- @Override
- default String getNameHashColumn() {
- return "fqnHash";
- }
- }
-
interface EntityExtensionDAO {
@ConnectionAwareSqlUpdate(
value =
@@ -1381,50 +1342,6 @@ default String getNameHashColumn() {
}
}
- interface DomainDAO extends EntityDAO {
- @Override
- default String getTableName() {
- return "domain_entity";
- }
-
- @Override
- default Class getEntityClass() {
- return Domain.class;
- }
-
- @Override
- default String getNameHashColumn() {
- return "fqnHash";
- }
-
- @Override
- default boolean supportsSoftDelete() {
- return false;
- }
- }
-
- interface DataProductDAO extends EntityDAO {
- @Override
- default String getTableName() {
- return "data_product_entity";
- }
-
- @Override
- default Class getEntityClass() {
- return DataProduct.class;
- }
-
- @Override
- default String getNameHashColumn() {
- return "fqnHash";
- }
-
- @Override
- default boolean supportsSoftDelete() {
- return false;
- }
- }
-
interface EventSubscriptionDAO extends EntityDAO {
@Override
default String getTableName() {
@@ -2797,12 +2714,16 @@ List listAfter(
@Bind("after") String after,
@Bind("relation") int relation);
- @ConnectionAwareSqlQuery(value = "SELECT count(*) FROM user_entity WHERE email = :email", connectionType = MYSQL)
- @ConnectionAwareSqlQuery(value = "SELECT count(*) FROM user_entity WHERE email = :email", connectionType = POSTGRES)
+ @SqlQuery("SELECT COUNT(*) FROM user_entity WHERE LOWER(email) = LOWER(:email)")
int checkEmailExists(@Bind("email") String email);
- @SqlQuery("SELECT json FROM user_entity WHERE email = :email")
+ @SqlQuery("SELECT json FROM user_entity WHERE LOWER(email) = LOWER(:email)")
String findUserByEmail(@Bind("email") String email);
+
+ @Override
+ default User findEntityByName(String fqn, Include include) {
+ return EntityDAO.super.findEntityByName(fqn.toLowerCase(), include);
+ }
}
interface ChangeEventDAO {
@@ -3120,281 +3041,44 @@ default String getNameHashColumn() {
}
}
- interface EntityExtensionTimeSeriesDAO {
- enum OrderBy {
- ASC,
- DESC
- }
-
- @ConnectionAwareSqlUpdate(
- value =
- "INSERT INTO entity_extension_time_series(entityFQNHash, extension, jsonSchema, json) "
- + "VALUES (:entityFQNHash, :extension, :jsonSchema, :json)",
- connectionType = MYSQL)
- @ConnectionAwareSqlUpdate(
- value =
- "INSERT INTO entity_extension_time_series(entityFQNHash, extension, jsonSchema, json) "
- + "VALUES (:entityFQNHash, :extension, :jsonSchema, (:json :: jsonb))",
- connectionType = POSTGRES)
- void insert(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("jsonSchema") String jsonSchema,
- @Bind("json") String json);
-
- @ConnectionAwareSqlUpdate(
- value =
- "UPDATE entity_extension_time_series set json = :json where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp",
- connectionType = MYSQL)
- @ConnectionAwareSqlUpdate(
- value =
- "UPDATE entity_extension_time_series set json = (:json :: jsonb) where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp",
- connectionType = POSTGRES)
- void update(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("json") String json,
- @Bind("timestamp") Long timestamp);
-
- @ConnectionAwareSqlUpdate(
- value =
- "UPDATE entity_extension_time_series set json = :json where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp and json -> '$.operation' = :operation",
- connectionType = MYSQL)
- @ConnectionAwareSqlUpdate(
- value =
- "UPDATE entity_extension_time_series set json = (:json :: jsonb) where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp and json #>>'{operation}' = :operation",
- connectionType = POSTGRES)
- void updateExtensionByOperation(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("json") String json,
- @Bind("timestamp") Long timestamp,
- @Bind("operation") String operation);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension")
- String getExtension(@BindFQN("entityFQNHash") String entityId, @Bind("extension") String extension);
-
- @SqlQuery("SELECT count(*) FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash")
- int listCount(@BindFQN("entityFQNHash") String entityFQNHash);
-
- @SqlQuery("SELECT COUNT(DISTINCT entityFQN) FROM entity_extension_time_series")
- @Deprecated
- int listDistinctCount();
-
- @ConnectionAwareSqlQuery(
- value =
- "WITH data AS (SELECT ROW_NUMBER() OVER(ORDER BY timestamp ASC) AS row_num, json "
- + "FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash) "
- + "SELECT row_num, json FROM data WHERE row_num > :after LIMIT :limit",
- connectionType = MYSQL)
- @ConnectionAwareSqlQuery(
- value =
- "WITH data AS (SELECT ROW_NUMBER() OVER(ORDER BY timestamp ASC) AS row_num, json "
- + "FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash) "
- + "SELECT row_num, json FROM data WHERE row_num > (:after :: integer) LIMIT :limit",
- connectionType = POSTGRES)
- @RegisterRowMapper(ReportDataMapper.class)
- List getAfterExtension(
- @BindFQN("entityFQNHash") String entityFQNHash, @Bind("limit") int limit, @Bind("after") String after);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp")
- String getExtensionAtTimestamp(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("timestamp") long timestamp);
-
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp AND json -> '$.operation' = :operation",
- connectionType = MYSQL)
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp AND json #>>'{operation}' = :operation",
- connectionType = POSTGRES)
- String getExtensionAtTimestampWithOperation(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("timestamp") long timestamp,
- @Bind("operation") String operation);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension "
- + "ORDER BY timestamp DESC LIMIT 1")
- String getLatestExtension(@BindFQN("entityFQNHash") String entityFQNHash, @Bind("extension") String extension);
-
- @SqlQuery(
- "SELECT ranked.json FROM (SELECT json, ROW_NUMBER() OVER(PARTITION BY entityFQNHash ORDER BY timestamp DESC) AS row_num "
- + "FROM entity_extension_time_series WHERE entityFQNHash IN () AND extension = :extension) ranked WHERE ranked.row_num = 1")
- List getLatestExtensionByFQNs(
- @BindList("entityFQNHashes") List entityFQNHashes, @Bind("extension") String extension);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series WHERE extension = :extension "
- + "ORDER BY timestamp DESC LIMIT 1")
- String getLatestByExtension(@Bind("extension") String extension);
-
- @SqlQuery("SELECT json FROM entity_extension_time_series WHERE extension = :extension " + "ORDER BY timestamp DESC")
- List getAllByExtension(@Bind("extension") String extension);
-
- @RegisterRowMapper(ExtensionMapper.class)
- @SqlQuery(
- "SELECT extension, json FROM entity_extension WHERE id = :id AND extension "
- + "LIKE CONCAT (:extensionPrefix, '.%') "
- + "ORDER BY extension")
- List getExtensions(@Bind("id") String id, @Bind("extensionPrefix") String extensionPrefix);
-
- @SqlUpdate("DELETE FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash")
- void deleteAll(@Bind("entityFQNHash") String entityFQNHash);
-
- @SqlUpdate(
- "DELETE FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension")
- void delete(@BindFQN("entityFQNHash") String entityFQNHash, @Bind("extension") String extension);
-
- // This just saves the limit number of records, and remove all other with given extension
- @SqlUpdate(
- "DELETE FROM entity_extension_time_series WHERE extension = :extension AND entityFQNHash NOT IN(SELECT entityFQNHash FROM (select * from entity_extension_time_series WHERE extension = :extension ORDER BY timestamp DESC LIMIT :records) AS subquery)")
- void deleteLastRecords(@Bind("extension") String extension, @Bind("records") int noOfRecord);
-
- @SqlUpdate(
- "DELETE FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp")
- void deleteAtTimestamp(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("timestamp") Long timestamp);
-
- @SqlUpdate(
- "DELETE FROM entity_extension_time_series WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp < :timestamp")
- void deleteBeforeTimestamp(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("timestamp") Long timestamp);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series where entityFQNHash = :entityFQNHash and extension = :extension "
- + " AND timestamp >= :startTs and timestamp <= :endTs ORDER BY timestamp DESC")
- List listBetweenTimestamps(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("startTs") Long startTs,
- @Bind("endTs") long endTs);
-
- @SqlQuery(
- "SELECT json FROM entity_extension_time_series where entityFQNHash = :entityFQNHash and extension = :extension "
- + " AND timestamp >= :startTs and timestamp <= :endTs ORDER BY timestamp ")
- List listBetweenTimestampsByOrder(
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("startTs") Long startTs,
- @Bind("endTs") long endTs,
- @Define("orderBy") OrderBy orderBy);
-
- default void updateExtensionByKey(String key, String value, String entityFQN, String extension, String json) {
- String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
- String psqlCond = String.format("AND json->>'%s' = :value", key);
- updateExtensionByKeyInternal(value, entityFQN, extension, json, mysqlCond, psqlCond);
+ interface EntityExtensionTimeSeriesDAO extends EntityTimeSeriesDAO {
+ @Override
+ default String getTimeSeriesTableName() {
+ return "entity_extension_time_series";
}
+ }
- default String getExtensionByKey(String key, String value, String entityFQN, String extension) {
- String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
- String psqlCond = String.format("AND json->>'%s' = :value", key);
- return getExtensionByKeyInternal(value, entityFQN, extension, mysqlCond, psqlCond);
+ interface ReportDataTimeSeriesDAO extends EntityTimeSeriesDAO {
+ @Override
+ default String getTimeSeriesTableName() {
+ return "report_data_time_series";
}
- default String getLatestExtensionByKey(String key, String value, String entityFQN, String extension) {
- String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
- String psqlCond = String.format("AND json->>'%s' = :value", key);
- return getLatestExtensionByKeyInternal(value, entityFQN, extension, mysqlCond, psqlCond);
- }
+ @SqlQuery("SELECT json FROM report_data_time_series WHERE entityFQNHash = :reportDataType and date = :date")
+ List listReportDataAtDate(@BindFQN("reportDataType") String reportDataType, @Bind("date") String date);
- /*
- * Support updating data filtering by top-level keys in the JSON
- */
@ConnectionAwareSqlUpdate(
- value =
- "UPDATE entity_extension_time_series SET json = :json "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + "",
+ value = "DELETE FROM report_data_time_series WHERE entityFQNHash = :reportDataType and date = :date",
connectionType = MYSQL)
@ConnectionAwareSqlUpdate(
value =
- "UPDATE entity_extension_time_series SET json = (:json :: jsonb) "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + "",
+ "DELETE FROM report_data_time_series WHERE entityFQNHash = :reportDataType and DATE(TO_TIMESTAMP((json ->> 'timestamp')::bigint/1000)) = DATE(:date)",
connectionType = POSTGRES)
- void updateExtensionByKeyInternal(
- @Bind("value") String value,
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Bind("json") String json,
- @Define("mysqlCond") String mysqlCond,
- @Define("psqlCond") String psqlCond);
-
- /*
- * Support selecting data filtering by top-level keys in the JSON
- */
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json from entity_extension_time_series "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + "",
- connectionType = MYSQL)
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json from entity_extension_time_series "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + "",
- connectionType = POSTGRES)
- String getExtensionByKeyInternal(
- @Bind("value") String value,
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Define("mysqlCond") String mysqlCond,
- @Define("psqlCond") String psqlCond);
-
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json from entity_extension_time_series "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + " "
- + "ORDER BY timestamp DESC LIMIT 1",
- connectionType = MYSQL)
- @ConnectionAwareSqlQuery(
- value =
- "SELECT json from entity_extension_time_series "
- + "WHERE entityFQNHash = :entityFQNHash "
- + "AND extension = :extension "
- + " "
- + "ORDER BY timestamp DESC LIMIT 1",
- connectionType = POSTGRES)
- String getLatestExtensionByKeyInternal(
- @Bind("value") String value,
- @BindFQN("entityFQNHash") String entityFQNHash,
- @Bind("extension") String extension,
- @Define("mysqlCond") String mysqlCond,
- @Define("psqlCond") String psqlCond);
+ void deleteReportDataTypeAtDate(@BindFQN("reportDataType") String reportDataType, @Bind("date") String date);
+ }
- class ReportDataMapper implements RowMapper {
- @Override
- public ReportDataRow map(ResultSet rs, StatementContext ctx) throws SQLException {
- String rowNumber = rs.getString("row_num");
- String json = rs.getString("json");
- ReportData reportData;
- reportData = JsonUtils.readValue(json, ReportData.class);
- return new ReportDataRow(rowNumber, reportData);
- }
+ interface ProfilerDataTimeSeriesDAO extends EntityTimeSeriesDAO {
+ @Override
+ default String getTimeSeriesTableName() {
+ return "profiler_data_time_series";
}
+ }
- @SqlQuery(
- "SELECT DISTINCT entityFQN FROM entity_extension_time_series WHERE entityFQNHash = '' or entityFQNHash is null LIMIT :limit")
- @Deprecated
- List migrationListDistinctWithOffset(@Bind("limit") int limit);
+ interface DataQualityDataTimeSeriesDAO extends EntityTimeSeriesDAO {
+ @Override
+ default String getTimeSeriesTableName() {
+ return "data_quality_data_time_series";
+ }
}
class EntitiesCountRowMapper implements RowMapper {
@@ -3438,7 +3122,6 @@ interface SystemDAO {
+ "(SELECT COUNT(*) FROM pipeline_entity ) as pipelineCount, "
+ "(SELECT COUNT(*) FROM ml_model_entity ) as mlmodelCount, "
+ "(SELECT COUNT(*) FROM storage_container_entity ) as storageContainerCount, "
- + "(SELECT COUNT(*) FROM search_index_entity ) as searchIndexCount, "
+ "(SELECT COUNT(*) FROM glossary_entity ) as glossaryCount, "
+ "(SELECT COUNT(*) FROM glossary_term_entity ) as glossaryTermCount, "
+ "(SELECT (SELECT COUNT(*) FROM metadata_service_entity ) + "
@@ -3447,7 +3130,6 @@ interface SystemDAO {
+ "(SELECT COUNT(*) FROM dashboard_service_entity )+ "
+ "(SELECT COUNT(*) FROM pipeline_service_entity )+ "
+ "(SELECT COUNT(*) FROM mlmodel_service_entity )+ "
- + "(SELECT COUNT(*) FROM search_service_entity )+ "
+ "(SELECT COUNT(*) FROM storage_service_entity )) as servicesCount, "
+ "(SELECT COUNT(*) FROM user_entity AND (JSON_EXTRACT(json, '$.isBot') IS NULL OR JSON_EXTRACT(json, '$.isBot') = FALSE)) as userCount, "
+ "(SELECT COUNT(*) FROM team_entity ) as teamCount, "
@@ -3461,7 +3143,6 @@ interface SystemDAO {
+ "(SELECT COUNT(*) FROM pipeline_entity ) as pipelineCount, "
+ "(SELECT COUNT(*) FROM ml_model_entity ) as mlmodelCount, "
+ "(SELECT COUNT(*) FROM storage_container_entity ) as storageContainerCount, "
- + "(SELECT COUNT(*) FROM search_index_entity ) as searchIndexCount, "
+ "(SELECT COUNT(*) FROM glossary_entity ) as glossaryCount, "
+ "(SELECT COUNT(*) FROM glossary_term_entity ) as glossaryTermCount, "
+ "(SELECT (SELECT COUNT(*) FROM metadata_service_entity ) + "
@@ -3470,7 +3151,6 @@ interface SystemDAO {
+ "(SELECT COUNT(*) FROM dashboard_service_entity )+ "
+ "(SELECT COUNT(*) FROM pipeline_service_entity )+ "
+ "(SELECT COUNT(*) FROM mlmodel_service_entity )+ "
- + "(SELECT COUNT(*) FROM search_service_entity )+ "
+ "(SELECT COUNT(*) FROM storage_service_entity )) as servicesCount, "
+ "(SELECT COUNT(*) FROM user_entity AND (json#>'{isBot}' IS NULL OR ((json#>'{isBot}')::boolean) = FALSE)) as userCount, "
+ "(SELECT COUNT(*) FROM team_entity ) as teamCount, "
@@ -3485,8 +3165,7 @@ interface SystemDAO {
+ "(SELECT COUNT(*) FROM dashboard_service_entity ) as dashboardServiceCount, "
+ "(SELECT COUNT(*) FROM pipeline_service_entity ) as pipelineServiceCount, "
+ "(SELECT COUNT(*) FROM mlmodel_service_entity ) as mlModelServiceCount, "
- + "(SELECT COUNT(*) FROM storage_service_entity ) as storageServiceCount, "
- + "(SELECT COUNT(*) FROM search_service_entity ) as searchServiceCount")
+ + "(SELECT COUNT(*) FROM storage_service_entity ) as storageServiceCount")
@RegisterRowMapper(ServicesCountRowMapper.class)
ServicesCount getAggregatedServicesCount(@Define("cond") String cond) throws StatementException;
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java
index 6c768285c992..c28ff23d6698 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java
@@ -4,7 +4,6 @@
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.CONTAINER;
-import static org.openmetadata.service.Entity.FIELD_PARENT;
import static org.openmetadata.service.Entity.FIELD_TAGS;
import static org.openmetadata.service.Entity.STORAGE_SERVICE;
@@ -48,7 +47,7 @@ public ContainerRepository(CollectionDAO dao) {
@Override
public Container setFields(Container container, EntityUtil.Fields fields) {
setDefaultFields(container);
- container.setParent(fields.contains(FIELD_PARENT) ? getParent(container) : container.getParent());
+ container.setParent(fields.contains("parent") ? getParent(container) : container.getParent());
if (container.getDataModel() != null) {
populateDataModelColumnTags(fields.contains(FIELD_TAGS), container.getDataModel().getColumns());
}
@@ -57,7 +56,8 @@ public Container setFields(Container container, EntityUtil.Fields fields) {
@Override
public Container clearFields(Container container, EntityUtil.Fields fields) {
- container.setParent(fields.contains(FIELD_PARENT) ? container.getParent() : null);
+ container.setChildren(fields.contains("children") ? getChildren(container) : null);
+ container.setParent(fields.contains("parent") ? container.getParent() : null);
return container.withDataModel(fields.contains("dataModel") ? container.getDataModel() : null);
}
@@ -121,7 +121,9 @@ public void prepare(Container container) {
public void storeEntity(Container container, boolean update) {
EntityReference storageService = container.getService();
EntityReference parent = container.getParent();
- container.withService(null).withParent(null);
+ List children = container.getChildren();
+
+ container.withService(null).withParent(null).withChildren(null);
// Don't store datamodel column tags as JSON but build it on the fly based on relationships
List columnWithTags = Lists.newArrayList();
@@ -134,7 +136,7 @@ public void storeEntity(Container container, boolean update) {
store(container, update);
// Restore the relationships
- container.withService(storageService).withParent(parent);
+ container.withService(storageService).withParent(parent).withChildren(children);
if (container.getDataModel() != null) {
container.getDataModel().setColumns(columnWithTags);
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardDataModelRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardDataModelRepository.java
index 26e5b4c53a99..93c508bdecd4 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardDataModelRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardDataModelRepository.java
@@ -14,7 +14,6 @@
package org.openmetadata.service.jdbi3;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.FIELD_TAGS;
import java.util.List;
@@ -123,12 +122,6 @@ public void storeRelationships(DashboardDataModel dashboardDataModel) {
Relationship.CONTAINS);
}
- @Override
- public DashboardDataModel setInheritedFields(DashboardDataModel dataModel, Fields fields) {
- DashboardService dashboardService = Entity.getEntity(dataModel.getService(), "domain", ALL);
- return inheritDomain(dataModel, fields, dashboardService);
- }
-
@Override
public DashboardDataModel setFields(DashboardDataModel dashboardDataModel, Fields fields) {
getColumnTags(fields.contains(FIELD_TAGS), dashboardDataModel.getColumns());
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardRepository.java
index eb21e0214042..be7db38d399e 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardRepository.java
@@ -14,7 +14,6 @@
package org.openmetadata.service.jdbi3;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
import static org.openmetadata.service.Entity.FIELD_TAGS;
@@ -164,12 +163,6 @@ public void storeRelationships(Dashboard dashboard) {
}
}
- @Override
- public Dashboard setInheritedFields(Dashboard dashboard, Fields fields) {
- DashboardService dashboardService = Entity.getEntity(dashboard.getService(), "domain", ALL);
- return inheritDomain(dashboard, fields, dashboardService);
- }
-
@Override
public EntityUpdater getUpdater(Dashboard original, Dashboard updated, Operation operation) {
return new DashboardUpdater(original, updated, operation);
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataProductRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataProductRepository.java
deleted file mode 100644
index 12f2855ed763..000000000000
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataProductRepository.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2021 Collate
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.openmetadata.service.jdbi3;
-
-import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
-import static org.openmetadata.service.Entity.DATA_PRODUCT;
-import static org.openmetadata.service.Entity.FIELD_ASSETS;
-import static org.openmetadata.service.util.EntityUtil.entityReferenceMatch;
-
-import java.util.ArrayList;
-import java.util.List;
-import lombok.extern.slf4j.Slf4j;
-import org.openmetadata.schema.entity.domains.DataProduct;
-import org.openmetadata.schema.type.EntityReference;
-import org.openmetadata.schema.type.Relationship;
-import org.openmetadata.service.Entity;
-import org.openmetadata.service.resources.domains.DataProductResource;
-import org.openmetadata.service.util.EntityUtil;
-import org.openmetadata.service.util.EntityUtil.Fields;
-import org.openmetadata.service.util.FullyQualifiedName;
-
-@Slf4j
-public class DataProductRepository extends EntityRepository {
- private static final String UPDATE_FIELDS = "experts,assets"; // Domain field can't be updated
-
- public DataProductRepository(CollectionDAO dao) {
- super(
- DataProductResource.COLLECTION_PATH,
- Entity.DATA_PRODUCT,
- DataProduct.class,
- dao.dataProductDAO(),
- dao,
- UPDATE_FIELDS,
- UPDATE_FIELDS);
- }
-
- @Override
- public DataProduct setFields(DataProduct entity, Fields fields) {
- return entity.withAssets(fields.contains(FIELD_ASSETS) ? getAssets(entity) : null);
- }
-
- @Override
- public DataProduct clearFields(DataProduct entity, Fields fields) {
- return entity.withAssets(fields.contains(FIELD_ASSETS) ? entity.getAssets() : null);
- }
-
- private List getAssets(DataProduct entity) {
- return findTo(entity.getId(), Entity.DATA_PRODUCT, Relationship.HAS, null);
- }
-
- @Override
- public void prepare(DataProduct entity) {
- // Parent, Experts, Owner, Assets are already validated
- }
-
- @Override
- public void storeEntity(DataProduct entity, boolean update) {
- store(entity, update);
- }
-
- @Override
- public void storeRelationships(DataProduct entity) {
- addRelationship(
- entity.getDomain().getId(), entity.getId(), Entity.DOMAIN, Entity.DATA_PRODUCT, Relationship.CONTAINS);
- for (EntityReference expert : listOrEmpty(entity.getExperts())) {
- addRelationship(entity.getId(), expert.getId(), Entity.DATA_PRODUCT, Entity.USER, Relationship.EXPERT);
- }
- for (EntityReference asset : listOrEmpty(entity.getAssets())) {
- addRelationship(entity.getId(), asset.getId(), Entity.DATA_PRODUCT, asset.getType(), Relationship.HAS);
- }
- }
-
- @Override
- public EntityUpdater getUpdater(DataProduct original, DataProduct updated, Operation operation) {
- return new DataProductUpdater(original, updated, operation);
- }
-
- @Override
- public void restorePatchAttributes(DataProduct original, DataProduct updated) {
- updated.withDomain(original.getDomain()); // Domain can't be changed
- }
-
- @Override
- public void setFullyQualifiedName(DataProduct entity) {
- EntityReference domain = entity.getDomain();
- entity.setFullyQualifiedName(FullyQualifiedName.add(domain.getFullyQualifiedName(), entity.getName()));
- }
-
- public class DataProductUpdater extends EntityUpdater {
- public DataProductUpdater(DataProduct original, DataProduct updated, Operation operation) {
- super(original, updated, operation);
- }
-
- @Override
- public void entitySpecificUpdate() {
- updateAssets();
- }
-
- private void updateAssets() {
- List origToRefs = listOrEmpty(original.getAssets());
- List updatedToRefs = listOrEmpty(updated.getAssets());
- List added = new ArrayList<>();
- List deleted = new ArrayList<>();
-
- if (!recordListChange(FIELD_ASSETS, origToRefs, updatedToRefs, added, deleted, entityReferenceMatch)) {
- return; // No changes between original and updated.
- }
- // Remove assets that were deleted
- for (EntityReference asset : deleted) {
- deleteRelationship(original.getId(), DATA_PRODUCT, asset.getId(), asset.getType(), Relationship.HAS);
- }
- // Add new assets
- for (EntityReference asset : added) {
- addRelationship(original.getId(), asset.getId(), DATA_PRODUCT, asset.getType(), Relationship.HAS, false);
- }
- updatedToRefs.sort(EntityUtil.compareEntityReference);
- origToRefs.sort(EntityUtil.compareEntityReference);
- }
- }
-}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseRepository.java
index ce8278691a67..cdc78c37c98b 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseRepository.java
@@ -13,9 +13,6 @@
package org.openmetadata.service.jdbi3;
-import static org.openmetadata.schema.type.Include.ALL;
-import static org.openmetadata.service.Entity.DATABASE_SERVICE;
-
import java.util.List;
import org.openmetadata.schema.entity.data.Database;
import org.openmetadata.schema.entity.services.DatabaseService;
@@ -58,12 +55,6 @@ public void storeRelationships(Database database) {
addRelationship(service.getId(), database.getId(), service.getType(), Entity.DATABASE, Relationship.CONTAINS);
}
- @Override
- public Database setInheritedFields(Database database, Fields fields) {
- DatabaseService service = Entity.getEntity(DATABASE_SERVICE, database.getService().getId(), "domain", ALL);
- return inheritDomain(database, fields, service);
- }
-
private List getSchemas(Database database) {
return database == null
? null
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java
index 78bb0849ef48..b30f1821ded3 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java
@@ -14,9 +14,11 @@
package org.openmetadata.service.jdbi3;
import static org.openmetadata.schema.type.Include.ALL;
+import static org.openmetadata.service.Entity.FIELD_OWNER;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import org.openmetadata.schema.entity.data.Database;
import org.openmetadata.schema.entity.data.DatabaseSchema;
import org.openmetadata.schema.type.EntityReference;
@@ -95,11 +97,19 @@ private void setDefaultFields(DatabaseSchema schema) {
@Override
public DatabaseSchema setInheritedFields(DatabaseSchema schema, Fields fields) {
- Database database = Entity.getEntity(Entity.DATABASE, schema.getDatabase().getId(), "owner,domain", ALL);
- inheritOwner(schema, fields, database);
- inheritDomain(schema, fields, database);
- schema.withRetentionPeriod(
- schema.getRetentionPeriod() == null ? database.getRetentionPeriod() : schema.getRetentionPeriod());
+ Database database = null;
+ UUID databaseId = schema.getDatabase().getId();
+ // If schema does not have owner, then inherit parent database owner
+ if (fields.contains(FIELD_OWNER) && schema.getOwner() == null) {
+ database = Entity.getEntity(Entity.DATABASE, databaseId, "owner", ALL);
+ schema.withOwner(database.getOwner());
+ }
+
+ // If schema does not have its own retention period, then inherit parent database retention period
+ if (schema.getRetentionPeriod() == null) {
+ database = database == null ? Entity.getEntity(Entity.DATABASE, databaseId, "", ALL) : database;
+ schema.withRetentionPeriod(database.getRetentionPeriod());
+ }
return schema;
}
@@ -125,6 +135,9 @@ private void populateDatabase(DatabaseSchema schema) {
.withDatabase(database.getEntityReference())
.withService(database.getService())
.withServiceType(database.getServiceType());
+
+ // Carry forward ownership from database, if necessary
+ schema.withOwner(schema.getOwner() == null ? database.getOwner() : schema.getOwner());
}
public class DatabaseSchemaUpdater extends EntityUpdater {
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DomainRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DomainRepository.java
deleted file mode 100644
index fa454abb8776..000000000000
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DomainRepository.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2021 Collate
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.openmetadata.service.jdbi3;
-
-import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
-import static org.openmetadata.service.Entity.DOMAIN;
-import static org.openmetadata.service.Entity.FIELD_EXPERTS;
-
-import java.util.List;
-import lombok.extern.slf4j.Slf4j;
-import org.openmetadata.schema.entity.domains.Domain;
-import org.openmetadata.schema.type.EntityReference;
-import org.openmetadata.schema.type.Relationship;
-import org.openmetadata.service.Entity;
-import org.openmetadata.service.resources.domains.DomainResource;
-import org.openmetadata.service.util.EntityUtil.Fields;
-import org.openmetadata.service.util.FullyQualifiedName;
-
-@Slf4j
-public class DomainRepository extends EntityRepository {
- private static final String UPDATE_FIELDS = "parent,children,experts";
-
- public DomainRepository(CollectionDAO dao) {
- super(DomainResource.COLLECTION_PATH, DOMAIN, Domain.class, dao.domainDAO(), dao, UPDATE_FIELDS, UPDATE_FIELDS);
- }
-
- @Override
- public Domain setFields(Domain entity, Fields fields) {
- return entity.withParent(fields.contains("parent") ? getParent(entity) : entity.getParent());
- }
-
- @Override
- public Domain clearFields(Domain entity, Fields fields) {
- entity.withParent(fields.contains("parent") ? entity.getParent() : null);
- return entity;
- }
-
- @Override
- public void prepare(Domain entity) {
- // Parent, Experts, Owner are already validated
- }
-
- @Override
- public void storeEntity(Domain entity, boolean update) {
- EntityReference parent = entity.getParent();
- List children = entity.getChildren();
- entity.withParent(null);
- store(entity, update);
- entity.withParent(parent);
- }
-
- @Override
- public void storeRelationships(Domain entity) {
- if (entity.getParent() != null) {
- addRelationship(entity.getParent().getId(), entity.getId(), DOMAIN, DOMAIN, Relationship.CONTAINS);
- }
- for (EntityReference expert : listOrEmpty(entity.getExperts())) {
- addRelationship(entity.getId(), expert.getId(), DOMAIN, Entity.USER, Relationship.EXPERT);
- }
- }
-
- @Override
- public Domain setInheritedFields(Domain domain, Fields fields) {
- // If subdomain does not have owner and experts, then inherit it from parent domain
- EntityReference parentRef = domain.getParent() != null ? domain.getParent() : getParent(domain);
- if (parentRef != null) {
- Domain parent = Entity.getEntity(DOMAIN, parentRef.getId(), "owner,experts", ALL);
- inheritOwner(domain, fields, parent);
- inheritExperts(domain, fields, parent);
- }
- return domain;
- }
-
- @Override
- public EntityUpdater getUpdater(Domain original, Domain updated, Operation operation) {
- return new DomainUpdater(original, updated, operation);
- }
-
- @Override
- public void restorePatchAttributes(Domain original, Domain updated) {
- updated.withParent(original.getParent()); // Parent can't be changed
- updated.withChildren(original.getChildren()); // Children can't be changed
- }
-
- @Override
- public void setFullyQualifiedName(Domain entity) {
- // Validate parent
- if (entity.getParent() == null) { // Top level domain
- entity.setFullyQualifiedName(FullyQualifiedName.build(entity.getName()));
- } else { // Sub domain
- EntityReference parent = entity.getParent();
- entity.setFullyQualifiedName(FullyQualifiedName.add(parent.getFullyQualifiedName(), entity.getName()));
- }
- }
-
- public class DomainUpdater extends EntityUpdater {
- public DomainUpdater(Domain original, Domain updated, Operation operation) {
- super(original, updated, operation);
- }
-
- @Override
- public void entitySpecificUpdate() {
- updateExperts();
- }
-
- private void updateExperts() {
- List origExperts = listOrEmpty(original.getExperts());
- List updatedExperts = listOrEmpty(updated.getExperts());
- updateToRelationships(
- FIELD_EXPERTS,
- DOMAIN,
- original.getId(),
- Relationship.EXPERT,
- Entity.USER,
- origExperts,
- updatedExperts,
- false);
- }
- }
-}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java
index 3448db276022..f42ba85c9db0 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java
@@ -51,7 +51,7 @@ default String getNameColumn() {
default String getNameHashColumn() {
return "nameHash";
- }
+ };
default boolean supportsSoftDelete() {
return true;
@@ -64,7 +64,7 @@ default boolean supportsSoftDelete() {
@ConnectionAwareSqlUpdate(
value = "INSERT INTO (, json) VALUES (:nameHashColumnValue, :json :: jsonb)",
connectionType = POSTGRES)
- int insert(
+ void insert(
@Define("table") String table,
@Define("nameHashColumn") String nameHashColumn,
@BindFQN("nameHashColumnValue") String nameHashColumnValue,
@@ -288,7 +288,10 @@ default String getCondition(Include include) {
if (include == null || include == Include.NON_DELETED) {
return "AND deleted = FALSE";
}
- return include == Include.DELETED ? " AND deleted = TRUE" : "";
+ if (include == Include.DELETED) {
+ return " AND deleted = TRUE";
+ }
+ return "";
}
default T findEntityById(UUID id, Include include) {
@@ -315,7 +318,11 @@ default T findEntityByName(String fqn, String nameHashColumn, Include include) {
default T jsonToEntity(String json, String identity) {
Class clz = getEntityClass();
- T entity = json != null ? JsonUtils.readValue(json, clz) : null;
+ T entity = null;
+ if (json != null) {
+
+ entity = JsonUtils.readValue(json, clz);
+ }
if (entity == null) {
String entityType = Entity.getEntityTypeFromClass(clz);
throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entityNotFound(entityType, identity));
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java
index 2f23e85dad4c..edd9e44332dd 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java
@@ -20,19 +20,12 @@
import static org.openmetadata.schema.type.Include.NON_DELETED;
import static org.openmetadata.schema.utils.EntityInterfaceUtil.quoteName;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
-import static org.openmetadata.service.Entity.DATA_PRODUCT;
-import static org.openmetadata.service.Entity.DOMAIN;
-import static org.openmetadata.service.Entity.FIELD_CHILDREN;
-import static org.openmetadata.service.Entity.FIELD_DATA_PRODUCTS;
import static org.openmetadata.service.Entity.FIELD_DELETED;
import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
import static org.openmetadata.service.Entity.FIELD_DISPLAY_NAME;
-import static org.openmetadata.service.Entity.FIELD_DOMAIN;
-import static org.openmetadata.service.Entity.FIELD_EXPERTS;
import static org.openmetadata.service.Entity.FIELD_EXTENSION;
import static org.openmetadata.service.Entity.FIELD_FOLLOWERS;
import static org.openmetadata.service.Entity.FIELD_OWNER;
-import static org.openmetadata.service.Entity.FIELD_REVIEWERS;
import static org.openmetadata.service.Entity.FIELD_TAGS;
import static org.openmetadata.service.Entity.FIELD_VOTES;
import static org.openmetadata.service.Entity.USER;
@@ -192,8 +185,6 @@ public abstract class EntityRepository {
protected final boolean supportsFollower;
protected final boolean supportsExtension;
protected final boolean supportsVotes;
- @Getter protected final boolean supportsDomain;
- protected final boolean supportsDataProducts;
protected boolean quoteFqn = false; // Entity fqns not hierarchical such user, teams, services need to be quoted
/** Fields that can be updated during PATCH operation */
@@ -241,12 +232,6 @@ public abstract class EntityRepository {
this.putFields.addField(allowedFields, FIELD_EXTENSION);
}
this.supportsVotes = allowedFields.contains(FIELD_VOTES);
- this.supportsDomain = allowedFields.contains(FIELD_DOMAIN);
- if (supportsDomain) {
- this.patchFields.addField(allowedFields, FIELD_DOMAIN);
- this.putFields.addField(allowedFields, FIELD_DOMAIN);
- }
- this.supportsDataProducts = allowedFields.contains(FIELD_DATA_PRODUCTS);
}
/**
@@ -672,8 +657,6 @@ public void prepareInternal(T entity) {
public void storeRelationshipsInternal(T entity) {
storeOwner(entity, entity.getOwner());
applyTags(entity);
- storeDomain(entity, entity.getDomain());
- storeDataProducts(entity, entity.getDataProducts());
storeRelationships(entity);
}
@@ -681,12 +664,9 @@ public T setFieldsInternal(T entity, Fields fields) {
entity.setOwner(fields.contains(FIELD_OWNER) ? getOwner(entity) : entity.getOwner());
entity.setTags(fields.contains(FIELD_TAGS) ? getTags(entity) : entity.getTags());
entity.setExtension(fields.contains(FIELD_EXTENSION) ? getExtension(entity) : entity.getExtension());
- entity.setDomain(fields.contains(FIELD_DOMAIN) ? getDomain(entity) : entity.getDomain());
- entity.setDataProducts(fields.contains(FIELD_DATA_PRODUCTS) ? getDataProducts(entity) : entity.getDataProducts());
entity.setFollowers(fields.contains(FIELD_FOLLOWERS) ? getFollowers(entity) : entity.getFollowers());
- entity.setChildren(fields.contains(FIELD_CHILDREN) ? getChildren(entity) : entity.getChildren());
- entity.setExperts(fields.contains(FIELD_EXPERTS) ? getExperts(entity) : entity.getExperts());
- entity.setReviewers(fields.contains(FIELD_REVIEWERS) ? getReviewers(entity) : entity.getReviewers());
+ entity.setChildren(fields.contains("children") ? getChildren(entity) : entity.getChildren());
+ entity.setReviewers(fields.contains("reviewers") ? getReviewers(entity) : entity.getReviewers());
setFields(entity, fields);
return entity;
}
@@ -695,12 +675,9 @@ public T clearFieldsInternal(T entity, Fields fields) {
entity.setOwner(fields.contains(FIELD_OWNER) ? entity.getOwner() : null);
entity.setTags(fields.contains(FIELD_TAGS) ? entity.getTags() : null);
entity.setExtension(fields.contains(FIELD_EXTENSION) ? entity.getExtension() : null);
- entity.setDomain(fields.contains(FIELD_DOMAIN) ? entity.getDomain() : null);
- entity.setDataProducts(fields.contains(FIELD_DATA_PRODUCTS) ? entity.getDataProducts() : null);
entity.setFollowers(fields.contains(FIELD_FOLLOWERS) ? entity.getFollowers() : null);
- entity.setChildren(fields.contains(FIELD_CHILDREN) ? entity.getChildren() : null);
- entity.setExperts(fields.contains(FIELD_EXPERTS) ? entity.getExperts() : null);
- entity.setReviewers(fields.contains(FIELD_REVIEWERS) ? entity.getReviewers() : null);
+ entity.setChildren(fields.contains("children") ? entity.getChildren() : null);
+ entity.setReviewers(fields.contains("reviewers") ? entity.getReviewers() : null);
clearFields(entity, fields);
return entity;
}
@@ -1035,18 +1012,10 @@ protected void store(T entity, boolean update) {
entity.withHref(null);
EntityReference owner = entity.getOwner();
entity.setOwner(null);
- List children = entity.getChildren();
- entity.setChildren(null);
List tags = entity.getTags();
entity.setTags(null);
- EntityReference domain = entity.getDomain();
- entity.setDomain(null);
- List dataProducts = entity.getDataProducts();
- entity.setDataProducts(null);
List followers = entity.getFollowers();
entity.setFollowers(null);
- List experts = entity.getExperts();
- entity.setExperts(null);
if (update) {
dao.update(entity.getId(), entity.getFullyQualifiedName(), JsonUtils.pojoToJson(entity));
@@ -1059,45 +1028,18 @@ protected void store(T entity, boolean update) {
// Restore the relationships
entity.setOwner(owner);
- entity.setChildren(children);
entity.setTags(tags);
- entity.setDomain(domain);
- entity.setDataProducts(dataProducts);
entity.setFollowers(followers);
- entity.setExperts(experts);
}
protected void storeTimeSeries(String fqn, String extension, String jsonSchema, String entityJson, Long timestamp) {
daoCollection.entityExtensionTimeSeriesDao().insert(fqn, extension, jsonSchema, entityJson);
}
- protected void storeTimeSeriesWithOperation(
- String fqn,
- String extension,
- String jsonSchema,
- String entityJson,
- Long timestamp,
- String operation,
- boolean update) {
- if (update) {
- daoCollection
- .entityExtensionTimeSeriesDao()
- .updateExtensionByOperation(fqn, extension, entityJson, timestamp, operation);
- } else {
- daoCollection.entityExtensionTimeSeriesDao().insert(fqn, extension, jsonSchema, entityJson);
- }
- }
-
public String getExtensionAtTimestamp(String fqn, String extension, Long timestamp) {
return daoCollection.entityExtensionTimeSeriesDao().getExtensionAtTimestamp(fqn, extension, timestamp);
}
- public String getExtensionAtTimestampWithOperation(String fqn, String extension, Long timestamp, String operation) {
- return daoCollection
- .entityExtensionTimeSeriesDao()
- .getExtensionAtTimestampWithOperation(fqn, extension, timestamp, operation);
- }
-
public String getLatestExtensionFromTimeseries(String fqn, String extension) {
return daoCollection.entityExtensionTimeSeriesDao().getLatestExtension(fqn, extension);
}
@@ -1512,14 +1454,6 @@ public EntityReference getOwner(T entity) {
return !supportsOwner ? null : getFromEntityRef(entity.getId(), Relationship.OWNS, null, false);
}
- public EntityReference getDomain(T entity) {
- return getFromEntityRef(entity.getId(), Relationship.HAS, DOMAIN, false);
- }
-
- private List getDataProducts(T entity) {
- return !supportsDataProducts ? null : findFrom(entity.getId(), entityType, Relationship.HAS, DATA_PRODUCT);
- }
-
protected EntityReference getParent(T entity) {
return getFromEntityRef(entity.getId(), Relationship.CONTAINS, entityType, false);
}
@@ -1532,42 +1466,10 @@ protected List getReviewers(T entity) {
return findFrom(entity.getId(), entityType, Relationship.REVIEWS, Entity.USER);
}
- protected List getExperts(T entity) {
- return findTo(entity.getId(), entityType, Relationship.EXPERT, Entity.USER);
- }
-
public EntityReference getOwner(EntityReference ref) {
return !supportsOwner ? null : Entity.getEntityReferenceById(ref.getType(), ref.getId(), ALL);
}
- public T inheritDomain(T entity, Fields fields, EntityInterface parent) {
- if (fields.contains(FIELD_DOMAIN) && entity.getDomain() == null) {
- entity.setDomain(parent.getDomain());
- }
- return entity;
- }
-
- public T inheritOwner(T entity, Fields fields, EntityInterface parent) {
- if (fields.contains(FIELD_OWNER) && entity.getOwner() == null) {
- entity.setOwner(parent.getOwner());
- }
- return entity;
- }
-
- public T inheritExperts(T entity, Fields fields, EntityInterface parent) {
- if (fields.contains(FIELD_EXPERTS) && nullOrEmpty(entity.getExperts())) {
- entity.setExperts(parent.getExperts());
- }
- return entity;
- }
-
- public T inheritReviewers(T entity, Fields fields, EntityInterface parent) {
- if (fields.contains(FIELD_REVIEWERS) && nullOrEmpty(entity.getReviewers())) {
- entity.setReviewers(parent.getReviewers());
- }
- return entity;
- }
-
protected void populateOwner(EntityReference owner) {
if (owner == null) {
return;
@@ -1589,25 +1491,6 @@ protected void storeOwner(T entity, EntityReference owner) {
}
}
- protected void storeDomain(T entity, EntityReference domain) {
- if (supportsDomain && domain != null) {
- // Add relationship domain --- has ---> entity
- LOG.info("Adding domain {} for entity {}:{}", domain.getFullyQualifiedName(), entityType, entity.getId());
- addRelationship(domain.getId(), entity.getId(), Entity.DOMAIN, entityType, Relationship.HAS);
- }
- }
-
- protected void storeDataProducts(T entity, List dataProducts) {
- if (supportsDataProducts && !nullOrEmpty(dataProducts)) {
- for (EntityReference dataProduct : dataProducts) {
- // Add relationship dataProduct --- has ---> entity
- LOG.info(
- "Adding dataProduct {} for entity {}:{}", dataProduct.getFullyQualifiedName(), entityType, entity.getId());
- addRelationship(dataProduct.getId(), entity.getId(), Entity.DATA_PRODUCT, entityType, Relationship.HAS);
- }
- }
- }
-
/** Remove owner relationship for a given entity */
private void removeOwner(T entity, EntityReference owner) {
if (EntityUtil.getId(owner) != null) {
@@ -1623,7 +1506,7 @@ public void updateOwner(T ownedEntity, EntityReference originalOwner, EntityRefe
}
public final Fields getFields(String fields) {
- if ("*".equals(fields)) {
+ if (fields != null && fields.equals("*")) {
return new Fields(allowedFields, String.join(",", allowedFields));
}
return new Fields(allowedFields, fields);
@@ -1688,13 +1571,6 @@ public EntityReference validateOwner(EntityReference owner) {
return Entity.getEntityReferenceById(owner.getType(), owner.getId(), ALL);
}
- public EntityReference validateDomain(String domainFqn) {
- if (!supportsDomain || domainFqn == null) {
- return null;
- }
- return Entity.getEntityReferenceByName(Entity.DOMAIN, domainFqn, NON_DELETED);
- }
-
/** Override this method to support downloading CSV functionality */
public String exportToCsv(String name, String user) throws IOException {
throw new IllegalArgumentException(csvNotSupported(entityType));
@@ -1769,9 +1645,6 @@ public final void update() {
updateOwner();
updateExtension();
updateTags(updated.getFullyQualifiedName(), FIELD_TAGS, original.getTags(), updated.getTags());
- updateDomain();
- updateDataProducts();
- updateExperts();
entitySpecificUpdate();
}
@@ -1903,63 +1776,6 @@ private void updateExtension() {
storeExtension(updated);
}
- private void updateDomain() {
- if (original.getDomain() == updated.getDomain()) {
- return;
- }
-
- EntityReference origDomain = original.getDomain();
- EntityReference updatedDomain = updated.getDomain();
- if ((operation.isPatch() || updatedDomain != null)
- && recordChange(FIELD_DOMAIN, origDomain, updatedDomain, true, entityReferenceMatch)) {
- if (origDomain != null) {
- LOG.info(
- "Removing domain {} for entity {}", origDomain.getFullyQualifiedName(), original.getFullyQualifiedName());
- deleteRelationship(origDomain.getId(), Entity.DOMAIN, original.getId(), entityType, Relationship.HAS);
- }
- if (updatedDomain != null) {
- // Add relationship owner --- owns ---> ownedEntity
- LOG.info(
- "Adding domain {} for entity {}",
- updatedDomain.getFullyQualifiedName(),
- original.getFullyQualifiedName());
- addRelationship(updatedDomain.getId(), original.getId(), Entity.DOMAIN, entityType, Relationship.HAS);
- }
- } else {
- updated.setDomain(original.getDomain());
- }
- }
-
- private void updateDataProducts() {
- if (!supportsDataProducts) {
- return;
- }
- List origDataProducts = listOrEmpty(original.getDataProducts());
- List updatedDataProducts = listOrEmpty(updated.getDataProducts());
- updateFromRelationships(
- FIELD_DATA_PRODUCTS,
- DATA_PRODUCT,
- origDataProducts,
- updatedDataProducts,
- Relationship.HAS,
- entityType,
- original.getId());
- }
-
- private void updateExperts() {
- List origExperts = listOrEmpty(original.getExperts());
- List updatedExperts = listOrEmpty(updated.getExperts());
- updateToRelationships(
- FIELD_EXPERTS,
- Entity.DATA_PRODUCT,
- original.getId(),
- Relationship.EXPERT,
- Entity.USER,
- origExperts,
- updatedExperts,
- false);
- }
-
public final boolean updateVersion(Double oldVersion) {
Double newVersion = oldVersion;
if (majorVersionChange) {
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java
new file mode 100644
index 000000000000..0ae1150be040
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java
@@ -0,0 +1,419 @@
+package org.openmetadata.service.jdbi3;
+
+import static org.openmetadata.service.jdbi3.locator.ConnectionType.MYSQL;
+import static org.openmetadata.service.jdbi3.locator.ConnectionType.POSTGRES;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import org.jdbi.v3.core.mapper.RowMapper;
+import org.jdbi.v3.core.statement.StatementContext;
+import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
+import org.jdbi.v3.sqlobject.customizer.Bind;
+import org.jdbi.v3.sqlobject.customizer.BindList;
+import org.jdbi.v3.sqlobject.customizer.Define;
+import org.jdbi.v3.sqlobject.statement.SqlQuery;
+import org.jdbi.v3.sqlobject.statement.SqlUpdate;
+import org.openmetadata.schema.analytics.ReportData;
+import org.openmetadata.service.jdbi3.locator.ConnectionAwareSqlQuery;
+import org.openmetadata.service.jdbi3.locator.ConnectionAwareSqlUpdate;
+import org.openmetadata.service.util.JsonUtils;
+import org.openmetadata.service.util.jdbi.BindFQN;
+
+public interface EntityTimeSeriesDAO {
+ String getTimeSeriesTableName();
+
+ enum OrderBy {
+ ASC,
+ DESC
+ }
+
+ class ReportDataMapper implements RowMapper {
+ @Override
+ public CollectionDAO.ReportDataRow map(ResultSet rs, StatementContext ctx) throws SQLException {
+ String rowNumber = rs.getString("row_num");
+ String json = rs.getString("json");
+ ReportData reportData;
+ reportData = JsonUtils.readValue(json, ReportData.class);
+ return new CollectionDAO.ReportDataRow(rowNumber, reportData);
+ }
+ }
+
+ @ConnectionAwareSqlUpdate(
+ value =
+ "INSERT INTO (entityFQNHash, extension, jsonSchema, json) "
+ + "VALUES (:entityFQNHash, :extension, :jsonSchema, :json)",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlUpdate(
+ value =
+ "INSERT INTO (entityFQNHash, extension, jsonSchema, json) "
+ + "VALUES (:entityFQNHash, :extension, :jsonSchema, (:json :: jsonb))",
+ connectionType = POSTGRES)
+ void insert(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("jsonSchema") String jsonSchema,
+ @Bind("json") String json);
+
+ default void insert(String entityFQNHash, String extension, String jsonSchema, String json) {
+ insert(getTimeSeriesTableName(), entityFQNHash, extension, jsonSchema, json);
+ }
+
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE set json = :json where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE set json = (:json :: jsonb) where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp",
+ connectionType = POSTGRES)
+ void update(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("json") String json,
+ @Bind("timestamp") Long timestamp);
+
+ default void update(String entityFQNHash, String extension, String json, Long timestamp) {
+ update(getTimeSeriesTableName(), entityFQNHash, extension, json, timestamp);
+ }
+
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE set json = :json where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp and json -> '$.operation' = :operation",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE set json = (:json :: jsonb) where entityFQNHash=:entityFQNHash and extension=:extension and timestamp=:timestamp and json #>>'{operation}' = :operation",
+ connectionType = POSTGRES)
+ void updateExtensionByOperation(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("json") String json,
+ @Bind("timestamp") Long timestamp,
+ @Bind("operation") String operation);
+
+ default void updateExtensionByOperation(
+ String entityFQNHash, String extension, String json, Long timestamp, String operation) {
+ updateExtensionByOperation(getTimeSeriesTableName(), entityFQNHash, extension, json, timestamp, operation);
+ }
+
+ @SqlQuery("SELECT json FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension")
+ String getExtension(
+ @Define("table") String table, @BindFQN("entityFQNHash") String entityId, @Bind("extension") String extension);
+
+ default String getExtension(String entityId, String extension) {
+ return getExtension(getTimeSeriesTableName(), entityId, extension);
+ }
+
+ @SqlQuery("SELECT count(*) FROM WHERE entityFQNHash = :entityFQNHash")
+ int listCount(@Define("table") String table, @BindFQN("entityFQNHash") String entityFQNHash);
+
+ default int listCount(String entityFQNHash) {
+ return listCount(getTimeSeriesTableName(), entityFQNHash);
+ }
+
+ /** @deprecated */
+ @SqlQuery("SELECT COUNT(DISTINCT entityFQN) FROM ")
+ @Deprecated(since = "1.1.1")
+ int listDistinctCount(@Define("table") String table);
+
+ default int listDistinctCount() {
+ return listDistinctCount(getTimeSeriesTableName());
+ }
+
+ @ConnectionAwareSqlQuery(
+ value =
+ "WITH data AS (SELECT ROW_NUMBER() OVER(ORDER BY timestamp ASC) AS row_num, json "
+ + "FROM WHERE entityFQNHash = :entityFQNHash) "
+ + "SELECT row_num, json FROM data WHERE row_num > :after LIMIT :limit",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlQuery(
+ value =
+ "WITH data AS (SELECT ROW_NUMBER() OVER(ORDER BY timestamp ASC) AS row_num, json "
+ + "FROM WHERE entityFQNHash = :entityFQNHash) "
+ + "SELECT row_num, json FROM data WHERE row_num > (:after :: integer) LIMIT :limit",
+ connectionType = POSTGRES)
+ @RegisterRowMapper(ReportDataMapper.class)
+ List getAfterExtension(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("limit") int limit,
+ @Bind("after") String after);
+
+ default List getAfterExtension(String entityFQNHash, int limit, String after) {
+ return getAfterExtension(getTimeSeriesTableName(), entityFQNHash, limit, after);
+ }
+
+ @SqlQuery(
+ "SELECT json FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp")
+ String getExtensionAtTimestamp(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("timestamp") long timestamp);
+
+ default String getExtensionAtTimestamp(String entityFQNHash, String extension, long timestamp) {
+ return getExtensionAtTimestamp(getTimeSeriesTableName(), entityFQNHash, extension, timestamp);
+ }
+
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp AND json -> '$.operation' = :operation",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp AND json #>>'{operation}' = :operation",
+ connectionType = POSTGRES)
+ String getExtensionAtTimestampWithOperation(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("timestamp") long timestamp,
+ @Bind("operation") String operation);
+
+ default String getExtensionAtTimestampWithOperation(
+ String entityFQNHash, String extension, long timestamp, String operation) {
+ return getExtensionAtTimestampWithOperation(
+ getTimeSeriesTableName(), entityFQNHash, extension, timestamp, operation);
+ }
+
+ @SqlQuery(
+ "SELECT json FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension "
+ + "ORDER BY timestamp DESC LIMIT 1")
+ String getLatestExtension(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension);
+
+ default String getLatestExtension(String entityFQNHash, String extension) {
+ return getLatestExtension(getTimeSeriesTableName(), entityFQNHash, extension);
+ }
+
+ @SqlQuery(
+ "SELECT ranked.json FROM (SELECT json, ROW_NUMBER() OVER(PARTITION BY entityFQNHash ORDER BY timestamp DESC) AS row_num "
+ + "FROM WHERE entityFQNHash IN () AND extension = :extension) ranked WHERE ranked.row_num = 1")
+ List getLatestExtensionByFQNs(
+ @Define("table") String table,
+ @BindList("entityFQNHashes") List entityFQNHashes,
+ @Bind("extension") String extension);
+
+ default List getLatestExtensionByFQNs(List entityFQNHashes, String extension) {
+ return getLatestExtensionByFQNs(getTimeSeriesTableName(), entityFQNHashes, extension);
+ }
+
+ @SqlQuery("SELECT json FROM WHERE extension = :extension " + "ORDER BY timestamp DESC LIMIT 1")
+ String getLatestByExtension(@Define("table") String table, @Bind("extension") String extension);
+
+ default String getLatestByExtension(String extension) {
+ return getLatestByExtension(getTimeSeriesTableName(), extension);
+ }
+
+ @SqlQuery("SELECT json FROM WHERE extension = :extension " + "ORDER BY timestamp DESC")
+ List getAllByExtension(@Define("table") String table, @Bind("extension") String extension);
+
+ default List getAllByExtension(String extension) {
+ return getAllByExtension(getTimeSeriesTableName(), extension);
+ }
+
+ @SqlUpdate("DELETE FROM WHERE entityFQNHash = :entityFQNHash")
+ void deleteAll(@Define("table") String table, @Bind("entityFQNHash") String entityFQNHash);
+
+ default void deleteAll(String entityFQNHash) {
+ deleteAll(getTimeSeriesTableName(), entityFQNHash);
+ }
+
+ @SqlUpdate("DELETE FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension")
+ void delete(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension);
+
+ default void delete(String entityFQNHash, String extension) {
+ delete(getTimeSeriesTableName(), entityFQNHash, extension);
+ }
+
+ // This just saves the limit number of records, and remove all other with given extension
+ @SqlUpdate(
+ "DELETE FROM WHERE extension = :extension AND entityFQNHash NOT IN(SELECT entityFQNHash FROM (select * from WHERE extension = :extension ORDER BY timestamp DESC LIMIT :records) AS subquery)")
+ void deleteLastRecords(
+ @Define("table") String table, @Bind("extension") String extension, @Bind("records") int noOfRecord);
+
+ default void deleteLastRecords(String extension, int noOfRecord) {
+ deleteLastRecords(getTimeSeriesTableName(), extension, noOfRecord);
+ }
+
+ @SqlUpdate(
+ "DELETE FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp")
+ void deleteAtTimestamp(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("timestamp") Long timestamp);
+
+ default void deleteAtTimestamp(String entityFQNHash, String extension, Long timestamp) {
+ deleteAtTimestamp(getTimeSeriesTableName(), entityFQNHash, extension, timestamp);
+ }
+
+ @SqlUpdate(
+ "DELETE FROM WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp < :timestamp")
+ void deleteBeforeTimestamp(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("timestamp") Long timestamp);
+
+ default void deleteBeforeTimestamp(String entityFQNHash, String extension, Long timestamp) {
+ deleteBeforeTimestamp(getTimeSeriesTableName(), entityFQNHash, extension, timestamp);
+ }
+
+ @SqlQuery(
+ "SELECT json FROM where entityFQNHash = :entityFQNHash and extension = :extension "
+ + " AND timestamp >= :startTs and timestamp <= :endTs ORDER BY timestamp DESC")
+ List listBetweenTimestamps(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("startTs") Long startTs,
+ @Bind("endTs") long endTs);
+
+ default List listBetweenTimestamps(String entityFQNHash, String extension, Long startTs, long endTs) {
+ return listBetweenTimestamps(getTimeSeriesTableName(), entityFQNHash, extension, startTs, endTs);
+ }
+
+ @SqlQuery(
+ "SELECT json FROM where entityFQNHash = :entityFQNHash and extension = :extension "
+ + " AND timestamp >= :startTs and timestamp <= :endTs ORDER BY timestamp ")
+ List listBetweenTimestampsByOrder(
+ @Define("table") String table,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("startTs") Long startTs,
+ @Bind("endTs") long endTs,
+ @Define("orderBy") CollectionDAO.EntityExtensionTimeSeriesDAO.OrderBy orderBy);
+
+ default List listBetweenTimestampsByOrder(
+ String entityFQNHash,
+ String extension,
+ Long startTs,
+ long endTs,
+ CollectionDAO.EntityExtensionTimeSeriesDAO.OrderBy orderBy) {
+ return listBetweenTimestampsByOrder(getTimeSeriesTableName(), entityFQNHash, extension, startTs, endTs, orderBy);
+ }
+
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE SET json = :json "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + "",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlUpdate(
+ value =
+ "UPDATE SET json = (:json :: jsonb) "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + "",
+ connectionType = POSTGRES)
+ void updateExtensionByKeyInternal(
+ @Define("table") String table,
+ @Bind("value") String value,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Bind("json") String json,
+ @Define("mysqlCond") String mysqlCond,
+ @Define("psqlCond") String psqlCond);
+
+ default void updateExtensionByKey(String key, String value, String entityFQN, String extension, String json) {
+ String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
+ String psqlCond = String.format("AND json->>'%s' = :value", key);
+ updateExtensionByKeyInternal(getTimeSeriesTableName(), value, entityFQN, extension, json, mysqlCond, psqlCond);
+ }
+
+ /*
+ * Support selecting data filtering by top-level keys in the JSON
+ */
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json from "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + "",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json from "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + "",
+ connectionType = POSTGRES)
+ String getExtensionByKeyInternal(
+ @Define("table") String table,
+ @Bind("value") String value,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Define("mysqlCond") String mysqlCond,
+ @Define("psqlCond") String psqlCond);
+
+ default String getExtensionByKey(String key, String value, String entityFQN, String extension) {
+ String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
+ String psqlCond = String.format("AND json->>'%s' = :value", key);
+ return getExtensionByKeyInternal(getTimeSeriesTableName(), value, entityFQN, extension, mysqlCond, psqlCond);
+ }
+
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json from "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + " "
+ + "ORDER BY timestamp DESC LIMIT 1",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlQuery(
+ value =
+ "SELECT json from "
+ + "WHERE entityFQNHash = :entityFQNHash "
+ + "AND extension = :extension "
+ + " "
+ + "ORDER BY timestamp DESC LIMIT 1",
+ connectionType = POSTGRES)
+ String getLatestExtensionByKeyInternal(
+ @Define("table") String table,
+ @Bind("value") String value,
+ @BindFQN("entityFQNHash") String entityFQNHash,
+ @Bind("extension") String extension,
+ @Define("mysqlCond") String mysqlCond,
+ @Define("psqlCond") String psqlCond);
+
+ default String getLatestExtensionByKey(String key, String value, String entityFQN, String extension) {
+ String mysqlCond = String.format("AND JSON_UNQUOTE(JSON_EXTRACT(json, '$.%s')) = :value", key);
+ String psqlCond = String.format("AND json->>'%s' = :value", key);
+ return getLatestExtensionByKeyInternal(getTimeSeriesTableName(), value, entityFQN, extension, mysqlCond, psqlCond);
+ }
+
+ default void storeTimeSeriesWithOperation(
+ String fqn,
+ String extension,
+ String jsonSchema,
+ String entityJson,
+ Long timestamp,
+ String operation,
+ boolean update) {
+ if (update) {
+ updateExtensionByOperation(fqn, extension, entityJson, timestamp, operation);
+ } else {
+ insert(fqn, extension, jsonSchema, entityJson);
+ }
+ }
+
+ /** @deprecated */
+ @SqlQuery("SELECT DISTINCT entityFQN FROM WHERE entityFQNHash = '' or entityFQNHash is null LIMIT :limit")
+ @Deprecated(since = "1.1.1")
+ List migrationListDistinctWithOffset(@Define("table") String table, @Bind("limit") int limit);
+
+ default List migrationListDistinctWithOffset(int limit) {
+ return migrationListDistinctWithOffset(getTimeSeriesTableName(), limit);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java
index 32e345f57d58..c98e4a3a032a 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java
@@ -17,7 +17,9 @@
package org.openmetadata.service.jdbi3;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
+import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.Include.ALL;
+import static org.openmetadata.service.Entity.FIELD_OWNER;
import static org.openmetadata.service.Entity.FIELD_REVIEWERS;
import static org.openmetadata.service.Entity.GLOSSARY;
import static org.openmetadata.service.Entity.GLOSSARY_TERM;
@@ -33,7 +35,6 @@
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.api.data.TermReference;
import org.openmetadata.schema.entity.data.Glossary;
import org.openmetadata.schema.entity.data.GlossaryTerm;
@@ -82,15 +83,31 @@ public GlossaryTerm clearFields(GlossaryTerm entity, Fields fields) {
@Override
public GlossaryTerm setInheritedFields(GlossaryTerm glossaryTerm, Fields fields) {
- EntityInterface parent;
- if (glossaryTerm.getParent() != null) {
- parent = get(null, glossaryTerm.getParent().getId(), getFields("owner,reviewers,domain"));
- } else {
- parent = Entity.getEntity(glossaryTerm.getGlossary(), "owner,reviewers,domain", ALL);
+ Glossary glossary = null;
+ GlossaryTerm parentTerm = null;
+ if (fields.contains(FIELD_OWNER) && glossaryTerm.getOwner() == null) {
+ if (glossaryTerm.getParent() != null) {
+ parentTerm = get(null, glossaryTerm.getParent().getId(), getFields("owner,reviewers"));
+ glossaryTerm.setOwner(parentTerm.getOwner());
+ } else {
+ glossary = Entity.getEntity(glossaryTerm.getGlossary(), "owner,reviewers", ALL);
+ glossaryTerm.setOwner(glossary.getOwner());
+ }
+ }
+
+ if (fields.contains(FIELD_REVIEWERS) && nullOrEmpty(glossaryTerm.getReviewers())) {
+ if (glossaryTerm.getParent() != null) {
+ if (parentTerm == null) {
+ parentTerm = get(null, glossaryTerm.getParent().getId(), getFields(FIELD_REVIEWERS));
+ }
+ glossaryTerm.setReviewers(parentTerm.getReviewers());
+ } else {
+ if (glossary == null) {
+ glossary = Entity.getEntity(glossaryTerm.getGlossary(), FIELD_REVIEWERS, ALL);
+ }
+ glossaryTerm.setReviewers(glossary.getReviewers());
+ }
}
- inheritOwner(glossaryTerm, fields, parent);
- inheritDomain(glossaryTerm, fields, parent);
- inheritReviewers(glossaryTerm, fields, parent);
return glossaryTerm;
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ListFilter.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ListFilter.java
index 7ba804ca3f64..af773ef08202 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ListFilter.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ListFilter.java
@@ -5,6 +5,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import lombok.Getter;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.Relationship;
@@ -55,7 +56,8 @@ public String getCondition(String tableName) {
condition = addCondition(condition, getWebhookCondition(tableName));
condition = addCondition(condition, getWebhookTypeCondition(tableName));
condition = addCondition(condition, getTestCaseCondition());
- condition = addCondition(condition, getTestSuiteCondition());
+ condition = addCondition(condition, getTestSuiteTypeCondition());
+ condition = addCondition(condition, getTestSuiteFQNCondition());
return condition.isEmpty() ? "WHERE TRUE" : "WHERE " + condition;
}
@@ -85,6 +87,13 @@ public String getServiceCondition(String tableName) {
return service == null ? "" : getFqnPrefixCondition(tableName, EntityInterfaceUtil.quoteName(service));
}
+ public String getTestSuiteFQNCondition() {
+ String testSuiteName = queryParams.get("testSuite");
+ return testSuiteName == null
+ ? ""
+ : String.format("fqnHash LIKE '%s%s%%'", FullyQualifiedName.buildHash(testSuiteName), Entity.SEPARATOR);
+ }
+
public String getParentCondition(String tableName) {
String parentFqn = queryParams.get("parent");
return parentFqn == null ? "" : getFqnPrefixCondition(tableName, parentFqn);
@@ -158,7 +167,7 @@ private String getTestCaseCondition() {
return addCondition(condition1, condition2);
}
- private String getTestSuiteCondition() {
+ private String getTestSuiteTypeCondition() {
String testSuiteType = getQueryParam("testSuiteType");
if (testSuiteType == null) {
@@ -197,17 +206,22 @@ private String getWebhookTypePrefixCondition(String tableName, String typePrefix
private String getPipelineTypePrefixCondition(String tableName, String pipelineType) {
pipelineType = escape(pipelineType);
+ String inCondition = getInConditionFromString(pipelineType);
if (DatasourceConfig.getInstance().isMySQL()) {
return tableName == null
? String.format(
- "JSON_UNQUOTE(JSON_EXTRACT(ingestion_pipeline_entity.json, '$.pipelineType')) = '%s'", pipelineType)
+ "JSON_UNQUOTE(JSON_EXTRACT(ingestion_pipeline_entity.json, '$.pipelineType')) IN (%s)", inCondition)
: String.format(
- "%s.JSON_UNQUOTE(JSON_EXTRACT(ingestion_pipeline_entity.json, '$.pipelineType')) = '%s%%'",
- tableName, pipelineType);
+ "%s.JSON_UNQUOTE(JSON_EXTRACT(ingestion_pipeline_entity.json, '$.pipelineType')) IN (%s)",
+ tableName, inCondition);
}
return tableName == null
- ? String.format("ingestion_pipeline_entity.json->>'pipelineType' = '%s'", pipelineType)
- : String.format("%s.json->>'pipelineType' = '%s%%'", tableName, pipelineType);
+ ? String.format("ingestion_pipeline_entity.json->>'pipelineType' IN (%s)", inCondition)
+ : String.format("%s.json->>'pipelineType' IN (%s)", tableName, inCondition);
+ }
+
+ private String getInConditionFromString(String condition) {
+ return Arrays.stream(condition.split(",")).map(s -> String.format("'%s'", s)).collect(Collectors.joining(","));
}
private String getCategoryPrefixCondition(String tableName, String category) {
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MigrationDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MigrationDAO.java
index eef51bc43da5..db2051425b78 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MigrationDAO.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MigrationDAO.java
@@ -32,6 +32,14 @@ public interface MigrationDAO {
connectionType = POSTGRES)
String getVersionMigrationChecksum(@Bind("version") String version) throws StatementException;
+ @ConnectionAwareSqlQuery(
+ value = "SELECT sqlStatement FROM SERVER_MIGRATION_SQL_LOGS where version = :version and checksum = :checksum",
+ connectionType = MYSQL)
+ @ConnectionAwareSqlQuery(
+ value = "SELECT sqlStatement FROM SERVER_MIGRATION_SQL_LOGS where version = :version and checksum = :checksum",
+ connectionType = POSTGRES)
+ String getSqlQuery(@Bind("version") String version, @Bind("checksum") String checksum) throws StatementException;
+
@ConnectionAwareSqlUpdate(
value =
"INSERT INTO SERVER_CHANGE_LOG (version, migrationFileName, checksum, installed_on)"
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java
index 3276aeae954c..2087cd1dbb0f 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java
@@ -15,10 +15,8 @@
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.DASHBOARD;
import static org.openmetadata.service.Entity.MLMODEL;
-import static org.openmetadata.service.Entity.MLMODEL_SERVICE;
import static org.openmetadata.service.util.EntityUtil.entityReferenceMatch;
import static org.openmetadata.service.util.EntityUtil.mlFeatureMatch;
import static org.openmetadata.service.util.EntityUtil.mlHyperParameterMatch;
@@ -181,13 +179,6 @@ public void storeRelationships(MlModel mlModel) {
setMlFeatureSourcesLineage(mlModel);
}
- @Override
- public MlModel setInheritedFields(MlModel mlModel, Fields fields) {
- // If mlModel does not have domain, then inherit it from parent MLModel service
- MlModelService service = Entity.getEntity(MLMODEL_SERVICE, mlModel.getService().getId(), "domain", ALL);
- return inheritDomain(mlModel, fields, service);
- }
-
/**
* If we have the properties MLFeatures -> MlFeatureSources and the feature sources have properly informed the Data
* Source EntityRef, then we will automatically build the lineage between tables and ML Model.
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java
index 76b2dad68c88..cb3d80516b70 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PipelineRepository.java
@@ -15,9 +15,7 @@
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.FIELD_TAGS;
-import static org.openmetadata.service.Entity.PIPELINE_SERVICE;
import static org.openmetadata.service.util.EntityUtil.taskMatch;
import java.util.ArrayList;
@@ -207,13 +205,6 @@ public void storeRelationships(Pipeline pipeline) {
addRelationship(service.getId(), pipeline.getId(), service.getType(), Entity.PIPELINE, Relationship.CONTAINS);
}
- @Override
- public Pipeline setInheritedFields(Pipeline pipeline, Fields fields) {
- // If pipeline does not have domain, then inherit it from parent Pipeline service
- PipelineService service = Entity.getEntity(PIPELINE_SERVICE, pipeline.getService().getId(), "domain", ALL);
- return inheritDomain(pipeline, fields, service);
- }
-
@Override
public void applyTags(Pipeline pipeline) {
// Add table level tags by adding tag to table relationship
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/QueryRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/QueryRepository.java
index 69cda89e47de..6e7dd4accac0 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/QueryRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/QueryRepository.java
@@ -4,14 +4,12 @@
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.service.Entity.USER;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import lombok.SneakyThrows;
import org.openmetadata.schema.entity.data.Query;
+import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.type.ChangeDescription;
import org.openmetadata.schema.type.ChangeEvent;
import org.openmetadata.schema.type.EntityReference;
@@ -22,10 +20,15 @@
import org.openmetadata.service.Entity;
import org.openmetadata.service.resources.query.QueryResource;
import org.openmetadata.service.util.EntityUtil;
+import org.openmetadata.service.util.JsonUtils;
import org.openmetadata.service.util.RestUtil;
public class QueryRepository extends EntityRepository {
private static final String QUERY_USED_IN_FIELD = "queryUsedIn";
+
+ private static final String QUERY_USERS_FIELD = "users";
+
+ private static final String QUERY_USED_BY_FIELD = "usedBy";
private static final String QUERY_PATCH_FIELDS = "users,query,queryUsedIn";
private static final String QUERY_UPDATE_FIELDS = "users,votes,queryUsedIn";
@@ -116,6 +119,36 @@ private void storeQueryUsedIn(
}
}
+ public RestUtil.PutResponse> AddQueryUser(
+ UriInfo uriInfo, String updatedBy, UUID queryId, List userFqnList) {
+ Query query = Entity.getEntity(Entity.QUERY, queryId, QUERY_USERS_FIELD, Include.NON_DELETED);
+ List oldValue = query.getUsers();
+
+ for (String userFqn : userFqnList) {
+ User user = Entity.getEntityByName(USER, userFqn, "", Include.NON_DELETED);
+ EntityReference entityRef = user.getEntityReference();
+ addRelationship(entityRef.getId(), queryId, entityRef.getType(), Entity.QUERY, Relationship.USES);
+ }
+ // Populate Fields
+ setFieldsInternal(query, new EntityUtil.Fields(allowedFields, QUERY_USERS_FIELD));
+ Entity.withHref(uriInfo, query.getUsers());
+ ChangeEvent changeEvent =
+ getQueryChangeEvent(updatedBy, QUERY_USERS_FIELD, oldValue, query.getUsers(), withHref(uriInfo, query));
+ return new RestUtil.PutResponse<>(Response.Status.CREATED, changeEvent, RestUtil.ENTITY_FIELDS_CHANGED);
+ }
+
+ public RestUtil.PutResponse> AddQueryUsedBy(
+ UriInfo uriInfo, String updatedBy, UUID queryId, List userList) {
+ Query query = Entity.getEntity(Entity.QUERY, queryId, QUERY_UPDATE_FIELDS, Include.NON_DELETED);
+ Query oldQuery = JsonUtils.readValue(JsonUtils.pojoToJson(query), Query.class);
+ query.getUsedBy().addAll(userList);
+ ChangeEvent changeEvent =
+ getQueryChangeEvent(
+ updatedBy, QUERY_USERS_FIELD, oldQuery.getUsedBy(), query.getUsers(), withHref(uriInfo, query));
+ update(uriInfo, oldQuery, query);
+ return new RestUtil.PutResponse<>(Response.Status.CREATED, changeEvent, RestUtil.ENTITY_FIELDS_CHANGED);
+ }
+
public RestUtil.PutResponse> addQueryUsage(
UriInfo uriInfo, String updatedBy, UUID queryId, List entityIds) {
Query query = Entity.getEntity(Entity.QUERY, queryId, QUERY_USED_IN_FIELD, Include.NON_DELETED);
@@ -187,6 +220,7 @@ public void entitySpecificUpdate() {
deleted,
EntityUtil.entityReferenceMatch);
// Store Query Used in Relation
+ recordChange("usedBy", original.getUsedBy(), updated.getUsedBy(), true);
storeQueryUsedIn(updated.getId(), added, deleted);
String originalChecksum = EntityUtil.hash(original.getQuery());
String updatedChecksum = EntityUtil.hash(updated.getQuery());
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ReportDataRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ReportDataRepository.java
index f91b2df5366f..9d2e1bce23f5 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ReportDataRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ReportDataRepository.java
@@ -1,5 +1,6 @@
package org.openmetadata.service.jdbi3;
+import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.ws.rs.core.Response;
@@ -22,7 +23,7 @@ public ReportDataRepository(CollectionDAO dao) {
public Response addReportData(ReportData reportData) {
reportData.setId(UUID.randomUUID());
daoCollection
- .entityExtensionTimeSeriesDao()
+ .reportDataTimeSeriesDao()
.insert(
reportData.getReportDataType().value(),
REPORT_DATA_EXTENSION,
@@ -37,10 +38,15 @@ public ResultList getReportData(ReportDataType reportDataType, Long
reportData =
JsonUtils.readObjects(
daoCollection
- .entityExtensionTimeSeriesDao()
+ .reportDataTimeSeriesDao()
.listBetweenTimestamps(reportDataType.value(), REPORT_DATA_EXTENSION, startTs, endTs),
ReportData.class);
return new ResultList<>(reportData, String.valueOf(startTs), String.valueOf(endTs), reportData.size());
}
+
+ public void deleteReportDataAtDate(ReportDataType reportDataType, String date) throws IOException {
+ // We'll check if we have data to delete before we delete it
+ daoCollection.reportDataTimeSeriesDao().deleteReportDataTypeAtDate(reportDataType.value(), date);
+ }
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchIndexRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchIndexRepository.java
deleted file mode 100644
index 4377f5af66a1..000000000000
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchIndexRepository.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright 2021 Collate
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.openmetadata.service.jdbi3;
-
-import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
-import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
-import static org.openmetadata.schema.type.Include.ALL;
-import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
-import static org.openmetadata.service.Entity.FIELD_DISPLAY_NAME;
-import static org.openmetadata.service.Entity.FIELD_FOLLOWERS;
-import static org.openmetadata.service.Entity.FIELD_TAGS;
-import static org.openmetadata.service.Entity.SEARCH_SERVICE;
-import static org.openmetadata.service.util.EntityUtil.getSearchIndexField;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.function.BiPredicate;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.json.JsonPatch;
-import org.jdbi.v3.sqlobject.transaction.Transaction;
-import org.openmetadata.schema.EntityInterface;
-import org.openmetadata.schema.entity.data.SearchIndex;
-import org.openmetadata.schema.entity.services.SearchService;
-import org.openmetadata.schema.type.EntityReference;
-import org.openmetadata.schema.type.Relationship;
-import org.openmetadata.schema.type.SearchIndexField;
-import org.openmetadata.schema.type.TagLabel;
-import org.openmetadata.schema.type.TaskDetails;
-import org.openmetadata.schema.type.searchindex.SearchIndexSampleData;
-import org.openmetadata.service.Entity;
-import org.openmetadata.service.exception.CatalogExceptionMessage;
-import org.openmetadata.service.resources.feeds.MessageParser;
-import org.openmetadata.service.resources.searchindex.SearchIndexResource;
-import org.openmetadata.service.security.mask.PIIMasker;
-import org.openmetadata.service.util.EntityUtil;
-import org.openmetadata.service.util.EntityUtil.Fields;
-import org.openmetadata.service.util.FullyQualifiedName;
-import org.openmetadata.service.util.JsonUtils;
-
-public class SearchIndexRepository extends EntityRepository {
- @Override
- public void setFullyQualifiedName(SearchIndex searchIndex) {
- searchIndex.setFullyQualifiedName(
- FullyQualifiedName.add(searchIndex.getService().getFullyQualifiedName(), searchIndex.getName()));
- if (searchIndex.getFields() != null) {
- setFieldFQN(searchIndex.getFullyQualifiedName(), searchIndex.getFields());
- }
- }
-
- public SearchIndexRepository(CollectionDAO dao) {
- super(
- SearchIndexResource.COLLECTION_PATH, Entity.SEARCH_INDEX, SearchIndex.class, dao.searchIndexDAO(), dao, "", "");
- }
-
- @Override
- public void prepare(SearchIndex searchIndex) {
- SearchService searchService = Entity.getEntity(searchIndex.getService(), "", ALL);
- searchIndex.setService(searchService.getEntityReference());
- searchIndex.setServiceType(searchService.getServiceType());
- // Validate field tags
- if (searchIndex.getFields() != null) {
- addDerivedFieldTags(searchIndex.getFields());
- validateSchemaFieldTags(searchIndex.getFields());
- }
- }
-
- @Override
- public void storeEntity(SearchIndex searchIndex, boolean update) {
- // Relationships and fields such as service are derived and not stored as part of json
- EntityReference service = searchIndex.getService();
- searchIndex.withService(null);
-
- // Don't store fields tags as JSON but build it on the fly based on relationships
- List fieldsWithTags = null;
- if (searchIndex.getFields() != null) {
- fieldsWithTags = searchIndex.getFields();
- searchIndex.setFields(cloneWithoutTags(fieldsWithTags));
- searchIndex.getFields().forEach(field -> field.setTags(null));
- }
-
- store(searchIndex, update);
-
- // Restore the relationships
- if (fieldsWithTags != null) {
- searchIndex.setFields(fieldsWithTags);
- }
- searchIndex.withService(service);
- }
-
- @Override
- public void storeRelationships(SearchIndex searchIndex) {
- setService(searchIndex, searchIndex.getService());
- }
-
- @Override
- public SearchIndex setInheritedFields(SearchIndex searchIndex, Fields fields) {
- // If searchIndex does not have domain, then inherit it from parent messaging service
- SearchService service = Entity.getEntity(SEARCH_SERVICE, searchIndex.getService().getId(), "domain", ALL);
- return inheritDomain(searchIndex, fields, service);
- }
-
- @Override
- public SearchIndex setFields(SearchIndex searchIndex, Fields fields) {
- searchIndex.setService(getContainer(searchIndex.getId()));
- searchIndex.setFollowers(fields.contains(FIELD_FOLLOWERS) ? getFollowers(searchIndex) : null);
- if (searchIndex.getFields() != null) {
- getFieldTags(fields.contains(FIELD_TAGS), searchIndex.getFields());
- }
- return searchIndex;
- }
-
- @Override
- public SearchIndex clearFields(SearchIndex searchIndex, Fields fields) {
- return searchIndex;
- }
-
- @Override
- public SearchIndexUpdater getUpdater(SearchIndex original, SearchIndex updated, Operation operation) {
- return new SearchIndexUpdater(original, updated, operation);
- }
-
- public void setService(SearchIndex searchIndex, EntityReference service) {
- if (service != null && searchIndex != null) {
- addRelationship(
- service.getId(), searchIndex.getId(), service.getType(), Entity.SEARCH_INDEX, Relationship.CONTAINS);
- searchIndex.setService(service);
- }
- }
-
- public SearchIndex getSampleData(UUID searchIndexId, boolean authorizePII) {
- // Validate the request content
- SearchIndex searchIndex = dao.findEntityById(searchIndexId);
-
- SearchIndexSampleData sampleData =
- JsonUtils.readValue(
- daoCollection.entityExtensionDAO().getExtension(searchIndex.getId().toString(), "searchIndex.sampleData"),
- SearchIndexSampleData.class);
- searchIndex.setSampleData(sampleData);
- setFieldsInternal(searchIndex, Fields.EMPTY_FIELDS);
-
- // Set the fields tags. Will be used to mask the sample data
- if (!authorizePII) {
- getFieldTags(true, searchIndex.getFields());
- searchIndex.setTags(getTags(searchIndex.getFullyQualifiedName()));
- return PIIMasker.getSampleData(searchIndex);
- }
-
- return searchIndex;
- }
-
- @Transaction
- public SearchIndex addSampleData(UUID searchIndexId, SearchIndexSampleData sampleData) {
- // Validate the request content
- SearchIndex searchIndex = daoCollection.searchIndexDAO().findEntityById(searchIndexId);
-
- daoCollection
- .entityExtensionDAO()
- .insert(
- searchIndexId.toString(),
- "searchIndex.sampleData",
- "searchIndexSampleData",
- JsonUtils.pojoToJson(sampleData));
- setFieldsInternal(searchIndex, Fields.EMPTY_FIELDS);
- return searchIndex.withSampleData(sampleData);
- }
-
- private void setFieldFQN(String parentFQN, List fields) {
- fields.forEach(
- c -> {
- String fieldFqn = FullyQualifiedName.add(parentFQN, c.getName());
- c.setFullyQualifiedName(fieldFqn);
- if (c.getChildren() != null) {
- setFieldFQN(fieldFqn, c.getChildren());
- }
- });
- }
-
- private void getFieldTags(boolean setTags, List fields) {
- for (SearchIndexField f : listOrEmpty(fields)) {
- f.setTags(setTags ? getTags(f.getFullyQualifiedName()) : null);
- getFieldTags(setTags, f.getChildren());
- }
- }
-
- private void addDerivedFieldTags(List fields) {
- if (nullOrEmpty(fields)) {
- return;
- }
-
- for (SearchIndexField field : fields) {
- field.setTags(addDerivedTags(field.getTags()));
- if (field.getChildren() != null) {
- addDerivedFieldTags(field.getChildren());
- }
- }
- }
-
- List cloneWithoutTags(List fields) {
- if (nullOrEmpty(fields)) {
- return fields;
- }
- List copy = new ArrayList<>();
- fields.forEach(f -> copy.add(cloneWithoutTags(f)));
- return copy;
- }
-
- private SearchIndexField cloneWithoutTags(SearchIndexField field) {
- List children = cloneWithoutTags(field.getChildren());
- return new SearchIndexField()
- .withDescription(field.getDescription())
- .withName(field.getName())
- .withDisplayName(field.getDisplayName())
- .withFullyQualifiedName(field.getFullyQualifiedName())
- .withDataType(field.getDataType())
- .withDataTypeDisplay(field.getDataTypeDisplay())
- .withChildren(children);
- }
-
- private void validateSchemaFieldTags(List fields) {
- // Add field level tags by adding tag to field relationship
- for (SearchIndexField field : fields) {
- checkMutuallyExclusive(field.getTags());
- if (field.getChildren() != null) {
- validateSchemaFieldTags(field.getChildren());
- }
- }
- }
-
- private void applyTags(List fields) {
- // Add field level tags by adding tag to field relationship
- for (SearchIndexField field : fields) {
- applyTags(field.getTags(), field.getFullyQualifiedName());
- if (field.getChildren() != null) {
- applyTags(field.getChildren());
- }
- }
- }
-
- @Override
- public void applyTags(SearchIndex searchIndex) {
- // Add table level tags by adding tag to table relationship
- super.applyTags(searchIndex);
- if (searchIndex.getFields() != null) {
- applyTags(searchIndex.getFields());
- }
- }
-
- @Override
- public List getAllTags(EntityInterface entity) {
- List allTags = new ArrayList<>();
- SearchIndex searchIndex = (SearchIndex) entity;
- EntityUtil.mergeTags(allTags, searchIndex.getTags());
- List schemaFields = searchIndex.getFields() != null ? searchIndex.getFields() : null;
- for (SearchIndexField schemaField : listOrEmpty(schemaFields)) {
- EntityUtil.mergeTags(allTags, schemaField.getTags());
- }
- return allTags;
- }
-
- @Override
- public void update(TaskDetails task, MessageParser.EntityLink entityLink, String newValue, String user) {
- if (entityLink.getFieldName().equals("fields")) {
- String schemaName = entityLink.getArrayFieldName();
- String childrenSchemaName = "";
- if (entityLink.getArrayFieldName().contains(".")) {
- String fieldNameWithoutQuotes =
- entityLink.getArrayFieldName().substring(1, entityLink.getArrayFieldName().length() - 1);
- schemaName = fieldNameWithoutQuotes.substring(0, fieldNameWithoutQuotes.indexOf("."));
- childrenSchemaName = fieldNameWithoutQuotes.substring(fieldNameWithoutQuotes.lastIndexOf(".") + 1);
- }
- SearchIndex searchIndex = getByName(null, entityLink.getEntityFQN(), getFields("tags"), ALL, false);
- SearchIndexField schemaField = null;
- for (SearchIndexField field : searchIndex.getFields()) {
- if (field.getName().equals(schemaName)) {
- schemaField = field;
- break;
- }
- }
- if (!"".equals(childrenSchemaName) && schemaField != null) {
- schemaField = getChildrenSchemaField(schemaField.getChildren(), childrenSchemaName);
- }
- if (schemaField == null) {
- throw new IllegalArgumentException(
- CatalogExceptionMessage.invalidFieldName("schema", entityLink.getArrayFieldName()));
- }
-
- String origJson = JsonUtils.pojoToJson(searchIndex);
- if (EntityUtil.isDescriptionTask(task.getType())) {
- schemaField.setDescription(newValue);
- } else if (EntityUtil.isTagTask(task.getType())) {
- List tags = JsonUtils.readObjects(newValue, TagLabel.class);
- schemaField.setTags(tags);
- }
- String updatedEntityJson = JsonUtils.pojoToJson(searchIndex);
- JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson);
- patch(null, searchIndex.getId(), user, patch);
- return;
- }
- super.update(task, entityLink, newValue, user);
- }
-
- private static SearchIndexField getChildrenSchemaField(List fields, String childrenSchemaName) {
- SearchIndexField childrenSchemaField = null;
- for (SearchIndexField field : fields) {
- if (field.getName().equals(childrenSchemaName)) {
- childrenSchemaField = field;
- break;
- }
- }
- if (childrenSchemaField == null) {
- for (SearchIndexField field : fields) {
- if (field.getChildren() != null) {
- childrenSchemaField = getChildrenSchemaField(field.getChildren(), childrenSchemaName);
- if (childrenSchemaField != null) {
- break;
- }
- }
- }
- }
- return childrenSchemaField;
- }
-
- public static Set getAllFieldTags(SearchIndexField field) {
- Set tags = new HashSet<>();
- if (!listOrEmpty(field.getTags()).isEmpty()) {
- tags.addAll(field.getTags());
- }
- for (SearchIndexField c : listOrEmpty(field.getChildren())) {
- tags.addAll(getAllFieldTags(c));
- }
- return tags;
- }
-
- public class SearchIndexUpdater extends EntityUpdater {
- public static final String FIELD_DATA_TYPE_DISPLAY = "dataTypeDisplay";
-
- public SearchIndexUpdater(SearchIndex original, SearchIndex updated, Operation operation) {
- super(original, updated, operation);
- }
-
- @Override
- public void entitySpecificUpdate() {
- if (updated.getFields() != null) {
- updateSearchIndexFields(
- "fields",
- original.getFields() == null ? null : original.getFields(),
- updated.getFields(),
- EntityUtil.searchIndexFieldMatch);
- }
- recordChange("searchIndexSettings", original.getSearchIndexSettings(), updated.getSearchIndexSettings());
- }
-
- private void updateSearchIndexFields(
- String fieldName,
- List origFields,
- List updatedFields,
- BiPredicate fieldMatch) {
- List deletedFields = new ArrayList<>();
- List addedFields = new ArrayList<>();
- recordListChange(fieldName, origFields, updatedFields, addedFields, deletedFields, fieldMatch);
- // carry forward tags and description if deletedFields matches added field
- Map addedFieldMap =
- addedFields.stream().collect(Collectors.toMap(SearchIndexField::getName, Function.identity()));
-
- for (SearchIndexField deleted : deletedFields) {
- if (addedFieldMap.containsKey(deleted.getName())) {
- SearchIndexField addedField = addedFieldMap.get(deleted.getName());
- if (nullOrEmpty(addedField.getDescription()) && nullOrEmpty(deleted.getDescription())) {
- addedField.setDescription(deleted.getDescription());
- }
- if (nullOrEmpty(addedField.getTags()) && nullOrEmpty(deleted.getTags())) {
- addedField.setTags(deleted.getTags());
- }
- }
- }
-
- // Delete tags related to deleted fields
- deletedFields.forEach(deleted -> daoCollection.tagUsageDAO().deleteTagsByTarget(deleted.getFullyQualifiedName()));
-
- // Add tags related to newly added fields
- for (SearchIndexField added : addedFields) {
- applyTags(added.getTags(), added.getFullyQualifiedName());
- }
-
- // Carry forward the user generated metadata from existing fields to new fields
- for (SearchIndexField updated : updatedFields) {
- // Find stored field matching name, data type and ordinal position
- SearchIndexField stored = origFields.stream().filter(c -> fieldMatch.test(c, updated)).findAny().orElse(null);
- if (stored == null) { // New field added
- continue;
- }
- updateFieldDescription(stored, updated);
- updateFieldDataTypeDisplay(stored, updated);
- updateFieldDisplayName(stored, updated);
- updateTags(
- stored.getFullyQualifiedName(),
- EntityUtil.getFieldName(fieldName, updated.getName(), FIELD_TAGS),
- stored.getTags(),
- updated.getTags());
-
- if (updated.getChildren() != null && stored.getChildren() != null) {
- String childrenFieldName = EntityUtil.getFieldName(fieldName, updated.getName());
- updateSearchIndexFields(childrenFieldName, stored.getChildren(), updated.getChildren(), fieldMatch);
- }
- }
- majorVersionChange = majorVersionChange || !deletedFields.isEmpty();
- }
-
- private void updateFieldDescription(SearchIndexField origField, SearchIndexField updatedField) {
- if (operation.isPut() && !nullOrEmpty(origField.getDescription()) && updatedByBot()) {
- // Revert the non-empty field description if being updated by a bot
- updatedField.setDescription(origField.getDescription());
- return;
- }
- String field = getSearchIndexField(original, origField, FIELD_DESCRIPTION);
- recordChange(field, origField.getDescription(), updatedField.getDescription());
- }
-
- private void updateFieldDisplayName(SearchIndexField origField, SearchIndexField updatedField) {
- if (operation.isPut() && !nullOrEmpty(origField.getDescription()) && updatedByBot()) {
- // Revert the non-empty field description if being updated by a bot
- updatedField.setDisplayName(origField.getDisplayName());
- return;
- }
- String field = getSearchIndexField(original, origField, FIELD_DISPLAY_NAME);
- recordChange(field, origField.getDisplayName(), updatedField.getDisplayName());
- }
-
- private void updateFieldDataTypeDisplay(SearchIndexField origField, SearchIndexField updatedField) {
- if (operation.isPut() && !nullOrEmpty(origField.getDataTypeDisplay()) && updatedByBot()) {
- // Revert the non-empty field dataTypeDisplay if being updated by a bot
- updatedField.setDataTypeDisplay(origField.getDataTypeDisplay());
- return;
- }
- String field = getSearchIndexField(original, origField, FIELD_DATA_TYPE_DISPLAY);
- recordChange(field, origField.getDataTypeDisplay(), updatedField.getDataTypeDisplay());
- }
- }
-}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchServiceRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchServiceRepository.java
deleted file mode 100644
index 2cbcba49127c..000000000000
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SearchServiceRepository.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.openmetadata.service.jdbi3;
-
-import org.openmetadata.schema.entity.services.SearchService;
-import org.openmetadata.schema.entity.services.ServiceType;
-import org.openmetadata.schema.type.SearchConnection;
-import org.openmetadata.service.Entity;
-import org.openmetadata.service.resources.services.storage.StorageServiceResource;
-
-public class SearchServiceRepository extends ServiceEntityRepository {
- public SearchServiceRepository(CollectionDAO dao) {
- super(
- StorageServiceResource.COLLECTION_PATH,
- Entity.SEARCH_SERVICE,
- dao,
- dao.searchServiceDAO(),
- SearchConnection.class,
- ServiceType.SEARCH);
- }
-}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java
index 6131a28ad773..0cb688ac4f11 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java
@@ -49,9 +49,9 @@ protected ServiceEntityRepository(
CollectionDAO dao,
EntityDAO entityDAO,
Class serviceConnectionClass,
- String updateFields,
+ String updatedFields,
ServiceType serviceType) {
- super(collectionPath, service, entityDAO.getEntityClass(), entityDAO, dao, "", updateFields);
+ super(collectionPath, service, entityDAO.getEntityClass(), entityDAO, dao, "", updatedFields);
this.serviceConnectionClass = serviceConnectionClass;
this.serviceType = serviceType;
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java
index 14d26d2306e4..34a749076cfb 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java
@@ -145,12 +145,16 @@ public Table clearFields(Table table, Fields fields) {
@Override
public Table setInheritedFields(Table table, Fields fields) {
- DatabaseSchema schema = Entity.getEntity(DATABASE_SCHEMA, table.getDatabaseSchema().getId(), "owner,domain", ALL);
- inheritOwner(table, fields, schema);
- inheritDomain(table, fields, schema);
+ setInheritedProperties(table, table.getDatabaseSchema().getId());
+ return table;
+ }
+
+ public void setInheritedProperties(Table table, UUID schemaId) {
// If table does not have retention period, then inherit it from parent databaseSchema
- return table.withRetentionPeriod(
- table.getRetentionPeriod() == null ? schema.getRetentionPeriod() : table.getRetentionPeriod());
+ if (table.getRetentionPeriod() == null) {
+ DatabaseSchema schema = Entity.getEntity(DATABASE_SCHEMA, schemaId, "", ALL);
+ table.withRetentionPeriod(schema.getRetentionPeriod());
+ }
}
private void setDefaultFields(Table table) {
@@ -353,12 +357,13 @@ private Column getColumnNameForProfiler(List columnList, ColumnProfile c
public Table addTableProfileData(UUID tableId, CreateTableProfile createTableProfile) {
// Validate the request content
Table table = dao.findEntityById(tableId);
- storeTimeSeries(
- table.getFullyQualifiedName(),
- TABLE_PROFILE_EXTENSION,
- "tableProfile",
- JsonUtils.pojoToJson(createTableProfile.getTableProfile()),
- createTableProfile.getTableProfile().getTimestamp());
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .insert(
+ table.getFullyQualifiedName(),
+ TABLE_PROFILE_EXTENSION,
+ "tableProfile",
+ JsonUtils.pojoToJson(createTableProfile.getTableProfile()));
for (ColumnProfile columnProfile : createTableProfile.getColumnProfile()) {
// Validate all the columns
@@ -366,31 +371,38 @@ public Table addTableProfileData(UUID tableId, CreateTableProfile createTablePro
if (column == null) {
throw new IllegalArgumentException("Invalid column name " + columnProfile.getName());
}
- storeTimeSeries(
- column.getFullyQualifiedName(),
- TABLE_COLUMN_PROFILE_EXTENSION,
- "columnProfile",
- JsonUtils.pojoToJson(columnProfile),
- columnProfile.getTimestamp());
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .insert(
+ column.getFullyQualifiedName(),
+ TABLE_COLUMN_PROFILE_EXTENSION,
+ "columnProfile",
+ JsonUtils.pojoToJson(columnProfile));
}
List systemProfiles = createTableProfile.getSystemProfile();
if (systemProfiles != null && !systemProfiles.isEmpty()) {
for (SystemProfile systemProfile : createTableProfile.getSystemProfile()) {
+ // system metrics timestamp is the one of the operation. We'll need to
+ // update the entry if it already exists in the database
String storedSystemProfile =
- getExtensionAtTimestampWithOperation(
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .getExtensionAtTimestampWithOperation(
+ table.getFullyQualifiedName(),
+ SYSTEM_PROFILE_EXTENSION,
+ systemProfile.getTimestamp(),
+ systemProfile.getOperation().value());
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .storeTimeSeriesWithOperation(
table.getFullyQualifiedName(),
SYSTEM_PROFILE_EXTENSION,
+ "systemProfile",
+ JsonUtils.pojoToJson(systemProfile),
systemProfile.getTimestamp(),
- systemProfile.getOperation().value());
- storeTimeSeriesWithOperation(
- table.getFullyQualifiedName(),
- SYSTEM_PROFILE_EXTENSION,
- "systemProfile",
- JsonUtils.pojoToJson(systemProfile),
- systemProfile.getTimestamp(),
- systemProfile.getOperation().value(),
- storedSystemProfile != null);
+ systemProfile.getOperation().value(),
+ storedSystemProfile != null);
}
}
@@ -416,11 +428,13 @@ public void deleteTableProfile(String fqn, String entityType, Long timestamp) {
throw new IllegalArgumentException("entityType must be table, column or system");
}
- Object storedTableProfile = JsonUtils.readValue(getExtensionAtTimestamp(fqn, extension, timestamp), classMapper);
+ Object storedTableProfile =
+ JsonUtils.readValue(
+ daoCollection.profilerDataTimeSeriesDao().getExtensionAtTimestamp(fqn, extension, timestamp), classMapper);
if (storedTableProfile == null) {
throw new EntityNotFoundException(String.format("Failed to find table profile for %s at %s", fqn, timestamp));
}
- deleteExtensionAtTimestamp(fqn, extension, timestamp);
+ daoCollection.profilerDataTimeSeriesDao().deleteAtTimestamp(fqn, extension, timestamp);
}
@Transaction
@@ -428,7 +442,11 @@ public ResultList getTableProfiles(String fqn, Long startTs, Long
List tableProfiles;
tableProfiles =
JsonUtils.readObjects(
- getResultsFromAndToTimestamps(fqn, TABLE_PROFILE_EXTENSION, startTs, endTs), TableProfile.class);
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .listBetweenTimestampsByOrder(
+ fqn, TABLE_PROFILE_EXTENSION, startTs, endTs, EntityTimeSeriesDAO.OrderBy.DESC),
+ TableProfile.class);
return new ResultList<>(tableProfiles, startTs.toString(), endTs.toString(), tableProfiles.size());
}
@@ -437,7 +455,11 @@ public ResultList getColumnProfiles(String fqn, Long startTs, Lon
List columnProfiles;
columnProfiles =
JsonUtils.readObjects(
- getResultsFromAndToTimestamps(fqn, TABLE_COLUMN_PROFILE_EXTENSION, startTs, endTs), ColumnProfile.class);
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .listBetweenTimestampsByOrder(
+ fqn, TABLE_COLUMN_PROFILE_EXTENSION, startTs, endTs, EntityTimeSeriesDAO.OrderBy.DESC),
+ ColumnProfile.class);
return new ResultList<>(columnProfiles, startTs.toString(), endTs.toString(), columnProfiles.size());
}
@@ -446,7 +468,11 @@ public ResultList getSystemProfiles(String fqn, Long startTs, Lon
List systemProfiles;
systemProfiles =
JsonUtils.readObjects(
- getResultsFromAndToTimestamps(fqn, SYSTEM_PROFILE_EXTENSION, startTs, endTs), SystemProfile.class);
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .listBetweenTimestampsByOrder(
+ fqn, SYSTEM_PROFILE_EXTENSION, startTs, endTs, EntityTimeSeriesDAO.OrderBy.DESC),
+ SystemProfile.class);
return new ResultList<>(systemProfiles, startTs.toString(), endTs.toString(), systemProfiles.size());
}
@@ -454,7 +480,9 @@ private void setColumnProfile(List columnList) {
for (Column column : columnList) {
ColumnProfile columnProfile =
JsonUtils.readValue(
- getLatestExtensionFromTimeseries(column.getFullyQualifiedName(), TABLE_COLUMN_PROFILE_EXTENSION),
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .getLatestExtension(column.getFullyQualifiedName(), TABLE_COLUMN_PROFILE_EXTENSION),
ColumnProfile.class);
column.setProfile(columnProfile);
if (column.getChildren() != null) {
@@ -468,7 +496,9 @@ public Table getLatestTableProfile(String fqn, boolean authorizePII) {
Table table = dao.findEntityByName(fqn, ALL);
TableProfile tableProfile =
JsonUtils.readValue(
- getLatestExtensionFromTimeseries(table.getFullyQualifiedName(), TABLE_PROFILE_EXTENSION),
+ daoCollection
+ .profilerDataTimeSeriesDao()
+ .getLatestExtension(table.getFullyQualifiedName(), TABLE_PROFILE_EXTENSION),
TableProfile.class);
table.setProfile(tableProfile);
setColumnProfile(table.getColumns());
@@ -602,13 +632,16 @@ private void addDerivedColumnTags(List columns) {
@Override
public void prepare(Table table) {
- DatabaseSchema schema = Entity.getEntity(table.getDatabaseSchema(), "", ALL);
+ DatabaseSchema schema = Entity.getEntity(table.getDatabaseSchema(), "owner", ALL);
table
.withDatabaseSchema(schema.getEntityReference())
.withDatabase(schema.getDatabase())
.withService(schema.getService())
.withServiceType(schema.getServiceType());
+ // Carry forward ownership from database schema
+ table.setOwner(table.getOwner() == null ? schema.getOwner() : table.getOwner());
+
// Validate column tags
addDerivedColumnTags(table.getColumns());
validateColumnTags(table.getColumns());
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TagRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TagRepository.java
index f49cfa944341..13e8795037b6 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TagRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TagRepository.java
@@ -31,7 +31,6 @@
import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.jdbi3.CollectionDAO.EntityRelationshipRecord;
-import org.openmetadata.service.jdbi3.EntityRepository.EntityUpdater;
import org.openmetadata.service.resources.tags.TagResource;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.FullyQualifiedName;
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java
index 3482a57e5083..bae7ed4519a8 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java
@@ -23,9 +23,7 @@
import static org.openmetadata.schema.api.teams.CreateTeam.TeamType.DIVISION;
import static org.openmetadata.schema.api.teams.CreateTeam.TeamType.GROUP;
import static org.openmetadata.schema.api.teams.CreateTeam.TeamType.ORGANIZATION;
-import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
-import static org.openmetadata.service.Entity.FIELD_DOMAIN;
import static org.openmetadata.service.Entity.ORGANIZATION_NAME;
import static org.openmetadata.service.Entity.POLICY;
import static org.openmetadata.service.Entity.ROLE;
@@ -136,15 +134,25 @@ public void storeEntity(Team team, boolean update) {
List users = team.getUsers();
List defaultRoles = team.getDefaultRoles();
List parents = team.getParents();
+ List children = team.getChildren();
List policies = team.getPolicies();
// Don't store users, defaultRoles, href as JSON. Build it on the fly based on relationships
- team.withUsers(null).withDefaultRoles(null).withParents(null).withPolicies(null).withInheritedRoles(null);
+ team.withUsers(null)
+ .withDefaultRoles(null)
+ .withParents(null)
+ .withChildren(null)
+ .withPolicies(null)
+ .withInheritedRoles(null);
store(team, update);
// Restore the relationships
- team.withUsers(users).withDefaultRoles(defaultRoles).withParents(parents).withPolicies(policies);
+ team.withUsers(users)
+ .withDefaultRoles(defaultRoles)
+ .withParents(parents)
+ .withChildren(children)
+ .withPolicies(policies);
}
@Override
@@ -169,20 +177,6 @@ public void storeRelationships(Team team) {
}
}
- @Override
- public Team setInheritedFields(Team team, Fields fields) {
- // If user does not have domain, then inherit it from parent Team
- // TODO have default team when a user belongs to multiple teams
- if (fields.contains(FIELD_DOMAIN) && team.getDomain() == null) {
- List parents = !fields.contains(PARENTS_FIELD) ? getParents(team) : team.getParents();
- if (!nullOrEmpty(parents)) {
- Team parent = Entity.getEntity(TEAM, parents.get(0).getId(), "domain", ALL);
- team.withDomain(parent.getDomain());
- }
- }
- return team;
- }
-
@Override
public TeamUpdater getUpdater(Team original, Team updated, Operation operation) {
return new TeamUpdater(original, updated, operation);
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java
index f52c4dec8e4c..bf1e72d7f206 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java
@@ -81,7 +81,7 @@ public RestUtil.PatchResponse patchTestCaseResults(
TestCaseResult original =
JsonUtils.readValue(
daoCollection
- .entityExtensionTimeSeriesDao()
+ .dataQualityDataTimeSeriesDao()
.getExtensionAtTimestamp(fqn, TESTCASE_RESULT_EXTENSION, timestamp),
TestCaseResult.class);
@@ -91,7 +91,7 @@ public RestUtil.PatchResponse patchTestCaseResults(
updated.getTestCaseFailureStatus().setUpdatedBy(user);
updated.getTestCaseFailureStatus().setUpdatedAt(System.currentTimeMillis());
daoCollection
- .entityExtensionTimeSeriesDao()
+ .dataQualityDataTimeSeriesDao()
.update(fqn, TESTCASE_RESULT_EXTENSION, JsonUtils.pojoToJson(updated), timestamp);
change = ENTITY_UPDATED;
}
@@ -122,27 +122,27 @@ public void prepare(TestCase test) {
validateTestParameters(test.getParameterValues(), testDefinition.getParameterDefinition());
}
- private EntityReference getTestSuite(TestCase test) {
+ private EntityReference getTestSuite(TestCase test) throws EntityNotFoundException {
// `testSuite` field returns the executable `testSuite` linked to that testCase
List records =
findFromRecords(test.getId(), entityType, Relationship.CONTAINS, TEST_SUITE);
- ensureSingleRelationship(entityType, test.getId(), records, Relationship.CONTAINS.value(), true);
for (CollectionDAO.EntityRelationshipRecord testSuiteId : records) {
TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId.getId(), "", Include.ALL);
if (Boolean.TRUE.equals(testSuite.getExecutable())) {
return testSuite.getEntityReference();
}
}
- return null;
+ throw new EntityNotFoundException(
+ String.format("Error occurred when retrieving executable test suite for testCase %s. ", test.getName())
+ + "No executable test suite was found.");
}
private List getTestSuites(TestCase test) {
// `testSuites` field returns all the `testSuite` (executable and logical) linked to that testCase
List records =
findFromRecords(test.getId(), entityType, Relationship.CONTAINS, TEST_SUITE);
- ensureSingleRelationship(entityType, test.getId(), records, Relationship.CONTAINS.value(), true);
return records.stream()
- .map(testSuiteId -> Entity.getEntity(TEST_SUITE, testSuiteId.getId(), "", Include.ALL))
+ .map(testSuiteId -> Entity.getEntity(TEST_SUITE, testSuiteId.getId(), "", Include.ALL, false))
.collect(Collectors.toList());
}
@@ -202,12 +202,13 @@ public RestUtil.PutResponse> addTestCaseResult(
// Validate the request content
TestCase testCase = dao.findEntityByName(fqn);
- storeTimeSeries(
- testCase.getFullyQualifiedName(),
- TESTCASE_RESULT_EXTENSION,
- TEST_CASE_RESULT_FIELD,
- JsonUtils.pojoToJson(testCaseResult),
- testCaseResult.getTimestamp());
+ daoCollection
+ .dataQualityDataTimeSeriesDao()
+ .insert(
+ testCase.getFullyQualifiedName(),
+ TESTCASE_RESULT_EXTENSION,
+ TEST_CASE_RESULT_FIELD,
+ JsonUtils.pojoToJson(testCaseResult));
setFieldsInternal(testCase, new EntityUtil.Fields(allowedFields, TEST_SUITE_FIELD));
setTestSuiteSummary(testCase, testCaseResult.getTimestamp(), testCaseResult.getTestCaseStatus());
@@ -223,10 +224,14 @@ public RestUtil.PutResponse> deleteTestCaseResult(String updatedBy, String fqn
// Validate the request content
TestCase testCase = dao.findEntityByName(fqn);
TestCaseResult storedTestCaseResult =
- JsonUtils.readValue(getExtensionAtTimestamp(fqn, TESTCASE_RESULT_EXTENSION, timestamp), TestCaseResult.class);
+ JsonUtils.readValue(
+ daoCollection
+ .dataQualityDataTimeSeriesDao()
+ .getExtensionAtTimestamp(fqn, TESTCASE_RESULT_EXTENSION, timestamp),
+ TestCaseResult.class);
if (storedTestCaseResult != null) {
- deleteExtensionAtTimestamp(fqn, TESTCASE_RESULT_EXTENSION, timestamp);
+ daoCollection.dataQualityDataTimeSeriesDao().deleteAtTimestamp(fqn, TESTCASE_RESULT_EXTENSION, timestamp);
testCase.setTestCaseResult(storedTestCaseResult);
ChangeDescription change = deleteTestCaseChangeDescription(testCase.getVersion(), storedTestCaseResult);
ChangeEvent changeEvent = getChangeEvent(updatedBy, testCase, change, entityType, testCase.getVersion());
@@ -236,27 +241,38 @@ public RestUtil.PutResponse> deleteTestCaseResult(String updatedBy, String fqn
String.format("Failed to find testCase result for %s at %s", testCase.getName(), timestamp));
}
+ private ResultSummary getResultSummary(TestCase testCase, Long timestamp, TestCaseStatus testCaseStatus) {
+ return new ResultSummary()
+ .withTestCaseName(testCase.getFullyQualifiedName())
+ .withStatus(testCaseStatus)
+ .withTimestamp(timestamp);
+ }
+
private void setTestSuiteSummary(TestCase testCase, Long timestamp, TestCaseStatus testCaseStatus) {
- ResultSummary resultSummary =
- new ResultSummary()
- .withTestCaseName(testCase.getFullyQualifiedName())
- .withStatus(testCaseStatus)
- .withTimestamp(timestamp);
- EntityReference ref = testCase.getTestSuite();
- TestSuite testSuite = Entity.getEntity(ref.getType(), ref.getId(), "", Include.ALL, false);
- List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary());
- if (resultSummaries.isEmpty()) {
- resultSummaries.add(resultSummary);
- } else {
- // We'll remove the existing summary for this test case and add the new one
- resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName()));
- resultSummaries.add(resultSummary);
- }
+ ResultSummary resultSummary = getResultSummary(testCase, timestamp, testCaseStatus);
+
+ // list all executable and logical test suite linked to the test case
+ List testSuites = getTestSuites(testCase);
+
+ // update the summary for each test suite
+ for (TestSuite testSuite : testSuites) {
+ testSuite.setSummary(null); // we don't want to store the summary in the database
+ List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary());
+ if (resultSummaries.isEmpty()) {
+ resultSummaries.add(resultSummary);
+ } else {
+ // We'll remove the existing summary for this test case and add the new one
+ resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName()));
+ resultSummaries.add(resultSummary);
+ }
- testSuite.setTestCaseResultSummary(resultSummaries);
- daoCollection
- .testSuiteDAO()
- .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
+ // set test case result summary for the test suite
+ // and update it in the database
+ testSuite.setTestCaseResultSummary(resultSummaries);
+ daoCollection
+ .testSuiteDAO()
+ .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
+ }
}
private ChangeDescription addTestCaseChangeDescription(Double version, Object newValue) {
@@ -290,7 +306,9 @@ private ChangeEvent getChangeEvent(
private TestCaseResult getTestCaseResult(TestCase testCase) {
return JsonUtils.readValue(
- getLatestExtensionFromTimeseries(testCase.getFullyQualifiedName(), TESTCASE_RESULT_EXTENSION),
+ daoCollection
+ .dataQualityDataTimeSeriesDao()
+ .getLatestExtension(testCase.getFullyQualifiedName(), TESTCASE_RESULT_EXTENSION),
TestCaseResult.class);
}
@@ -298,7 +316,11 @@ public ResultList getTestCaseResults(String fqn, Long startTs, L
List testCaseResults;
testCaseResults =
JsonUtils.readObjects(
- getResultsFromAndToTimestamps(fqn, TESTCASE_RESULT_EXTENSION, startTs, endTs), TestCaseResult.class);
+ daoCollection
+ .dataQualityDataTimeSeriesDao()
+ .listBetweenTimestampsByOrder(
+ fqn, TESTCASE_RESULT_EXTENSION, startTs, endTs, EntityTimeSeriesDAO.OrderBy.DESC),
+ TestCaseResult.class);
return new ResultList<>(testCaseResults, String.valueOf(startTs), String.valueOf(endTs), testCaseResults.size());
}
@@ -319,8 +341,21 @@ public void isTestSuiteExecutable(String testSuiteFqn) {
public RestUtil.PutResponse addTestCasesToLogicalTestSuite(TestSuite testSuite, List testCaseIds) {
bulkAddToRelationship(testSuite.getId(), testCaseIds, TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
List testCasesEntityReferences = new ArrayList<>();
+ List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary());
for (UUID testCaseId : testCaseIds) {
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, "", Include.ALL);
+ // Get the latest result to set the testSuite summary field
+ String result =
+ daoCollection
+ .dataQualityDataTimeSeriesDao()
+ .getLatestExtension(testCase.getFullyQualifiedName(), TESTCASE_RESULT_EXTENSION);
+ if (result != null) {
+ TestCaseResult testCaseResult = JsonUtils.readValue(result, TestCaseResult.class);
+ ResultSummary resultSummary =
+ getResultSummary(testCase, testCaseResult.getTimestamp(), testCaseResult.getTestCaseStatus());
+ resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName()));
+ resultSummaries.add(resultSummary);
+ }
testCasesEntityReferences.add(
new EntityReference()
.withId(testCase.getId())
@@ -331,6 +366,14 @@ public RestUtil.PutResponse addTestCasesToLogicalTestSuite(TestSuite
.withHref(testCase.getHref())
.withDeleted(testCase.getDeleted()));
}
+ // set test case result summary for logical test suite
+ // and update it in the database
+ testSuite.setTestCaseResultSummary(resultSummaries);
+ testSuite.setSummary(null); // we don't want to store the summary in the database
+ daoCollection
+ .testSuiteDAO()
+ .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
+
testSuite.setTests(testCasesEntityReferences);
return new RestUtil.PutResponse<>(Response.Status.OK, testSuite, LOGICAL_TEST_CASES_ADDED);
}
@@ -338,16 +381,42 @@ public RestUtil.PutResponse addTestCasesToLogicalTestSuite(TestSuite
public RestUtil.DeleteResponse deleteTestCaseFromLogicalTestSuite(UUID testSuiteId, UUID testCaseId) {
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, null, null);
deleteRelationship(testSuiteId, TEST_SUITE, testCaseId, TEST_CASE, Relationship.CONTAINS);
+ // remove test case from logical test suite summary and update test suite
+ removeTestCaseFromTestSuiteResultSummary(testSuiteId, testCase.getFullyQualifiedName());
EntityReference entityReference = Entity.getEntityReferenceById(TEST_SUITE, testSuiteId, Include.ALL);
testCase.setTestSuite(entityReference);
return new RestUtil.DeleteResponse<>(testCase, RestUtil.ENTITY_DELETED);
}
+ /** Remove test case from test suite summary and update test suite */
+ private void removeTestCaseFromTestSuiteResultSummary(UUID testSuiteId, String testCaseFqn) {
+ TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "*", Include.ALL, false);
+ testSuite.setSummary(null); // we don't want to store the summary in the database
+ List resultSummaries = testSuite.getTestCaseResultSummary();
+ resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(testCaseFqn));
+ testSuite.setTestCaseResultSummary(resultSummaries);
+ daoCollection
+ .testSuiteDAO()
+ .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
+ }
+
@Override
public EntityUpdater getUpdater(TestCase original, TestCase updated, Operation operation) {
return new TestUpdater(original, updated, operation);
}
+ @Override
+ protected void preDelete(TestCase entity) {
+ // delete test case from test suite summary when test case is deleted
+ // from an executable test suite
+ List testSuites = getTestSuites(entity);
+ if (!testSuites.isEmpty()) {
+ for (TestSuite testSuite : testSuites) {
+ removeTestCaseFromTestSuiteResultSummary(testSuite.getId(), entity.getFullyQualifiedName());
+ }
+ }
+ }
+
public class TestUpdater extends EntityUpdater {
public TestUpdater(TestCase original, TestCase updated, Operation operation) {
super(original, updated, operation);
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java
index fcc8f0222a24..aff85c0ee07f 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java
@@ -58,6 +58,7 @@ public TestSuite clearFields(TestSuite entity, EntityUtil.Fields fields) {
}
private TestSummary buildTestSummary(HashMap testCaseSummary, int total) {
+
return new TestSummary()
.withAborted(testCaseSummary.getOrDefault(TestCaseStatus.Aborted.toString(), 0))
.withFailed(testCaseSummary.getOrDefault(TestCaseStatus.Failed.toString(), 0))
@@ -115,13 +116,10 @@ public TestSummary getTestSummary(UUID testSuiteId) {
List testSuites = listAll(EntityUtil.Fields.EMPTY_FIELDS, filter);
testSummary = getTestCasesExecutionSummary(testSuites);
} else {
- TestSuite testSuite = find(testSuiteId, Include.ALL);
- if (!Boolean.TRUE.equals(testSuite.getExecutable())) {
- throw new IllegalArgumentException("Test Suite is not executable. Please provide an executable test suite.");
- }
+ // don't want to get it from the cache as test results summary may be stale
+ TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "", Include.ALL, false);
testSummary = getTestCasesExecutionSummary(testSuite);
}
-
return testSummary;
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java
index 1bf3bda1d6bf..2a62e8260543 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java
@@ -19,7 +19,6 @@
import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
import static org.openmetadata.service.Entity.FIELD_DISPLAY_NAME;
import static org.openmetadata.service.Entity.FIELD_TAGS;
-import static org.openmetadata.service.Entity.MESSAGING_SERVICE;
import static org.openmetadata.service.util.EntityUtil.getSchemaField;
import java.util.ArrayList;
@@ -106,13 +105,6 @@ public void storeRelationships(Topic topic) {
setService(topic, topic.getService());
}
- @Override
- public Topic setInheritedFields(Topic topic, Fields fields) {
- // If topic does not have domain, then inherit it from parent messaging service
- MessagingService service = Entity.getEntity(MESSAGING_SERVICE, topic.getService().getId(), "domain", ALL);
- return inheritDomain(topic, fields, service);
- }
-
@Override
public Topic setFields(Topic topic, Fields fields) {
topic.setService(getContainer(topic.getId()));
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java
index 43a2799e8f9b..36c8648635cb 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java
@@ -18,7 +18,7 @@
import static org.openmetadata.csv.CsvUtil.addEntityReferences;
import static org.openmetadata.csv.CsvUtil.addField;
import static org.openmetadata.schema.type.Include.ALL;
-import static org.openmetadata.service.Entity.FIELD_DOMAIN;
+import static org.openmetadata.schema.utils.EntityInterfaceUtil.quoteName;
import static org.openmetadata.service.Entity.ROLE;
import static org.openmetadata.service.Entity.TEAM;
import static org.openmetadata.service.Entity.USER;
@@ -78,6 +78,13 @@ public UserRepository(CollectionDAO dao) {
this.quoteFqn = true;
}
+ // with the introduction of fqnhash we added case sensitivity to all of the entities
+ // however usernames , emails cannot be case sensitive
+ @Override
+ public void setFullyQualifiedName(User user) {
+ user.setFullyQualifiedName(quoteName(user.getName().toLowerCase()));
+ }
+
public final Fields getFieldsWithUserAuth(String fields) {
Set tempFields = getAllowedFieldsCopy();
if (fields != null && fields.equals("*")) {
@@ -151,20 +158,6 @@ public void storeRelationships(User user) {
user.setInheritedRoles(getInheritedRoles(user));
}
- @Override
- public User setInheritedFields(User user, Fields fields) {
- // If user does not have domain, then inherit it from parent Team
- // TODO have default team when a user belongs to multiple teams
- if (fields.contains(FIELD_DOMAIN) && user.getDomain() == null) {
- List teams = !fields.contains("teams") ? getTeams(user) : user.getTeams();
- if (!nullOrEmpty(teams)) {
- Team team = Entity.getEntity(TEAM, teams.get(0).getId(), "domain", ALL);
- user.withDomain(team.getDomain());
- }
- }
- return user;
- }
-
@Override
public UserUpdater getUpdater(User original, User updated, Operation operation) {
return new UserUpdater(original, updated, operation);
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v112/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v112/Migration.java
index 4b1a6450f4ea..94e5d7302fc8 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v112/Migration.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v112/Migration.java
@@ -1,6 +1,7 @@
package org.openmetadata.service.migration.mysql.v112;
-import static org.openmetadata.service.migration.postgres.v112.Migration.unquoteTestSuiteMigration;
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.fixExecutableTestSuiteFQN;
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.lowerCaseUserNameAndEmail;
import lombok.SneakyThrows;
import org.jdbi.v3.core.Handle;
@@ -10,7 +11,6 @@
public class Migration extends MigrationProcessImpl {
private CollectionDAO collectionDAO;
- private Handle handle;
public Migration(MigrationFile migrationFile) {
super(migrationFile);
@@ -19,7 +19,6 @@ public Migration(MigrationFile migrationFile) {
@Override
public void initialize(Handle handle) {
super.initialize(handle);
- this.handle = handle;
this.collectionDAO = handle.attach(CollectionDAO.class);
}
@@ -27,6 +26,8 @@ public void initialize(Handle handle) {
@SneakyThrows
public void runDataMigration() {
// Run Data Migration to Remove the quoted Fqn`
- unquoteTestSuiteMigration(collectionDAO);
+ fixExecutableTestSuiteFQN(collectionDAO);
+ // Run UserName Migration to make lowercase
+ lowerCaseUserNameAndEmail(collectionDAO);
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v114/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v114/Migration.java
new file mode 100644
index 000000000000..eab684f2b844
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v114/Migration.java
@@ -0,0 +1,31 @@
+package org.openmetadata.service.migration.mysql.v114;
+
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.lowerCaseUserNameAndEmail;
+import static org.openmetadata.service.migration.utils.V114.MigrationUtil.fixTestSuites;
+
+import lombok.SneakyThrows;
+import org.jdbi.v3.core.Handle;
+import org.openmetadata.service.jdbi3.CollectionDAO;
+import org.openmetadata.service.migration.api.MigrationProcessImpl;
+import org.openmetadata.service.migration.utils.MigrationFile;
+
+public class Migration extends MigrationProcessImpl {
+ private CollectionDAO collectionDAO;
+
+ public Migration(MigrationFile migrationFile) {
+ super(migrationFile);
+ }
+
+ @Override
+ public void initialize(Handle handle) {
+ super.initialize(handle);
+ this.collectionDAO = handle.attach(CollectionDAO.class);
+ }
+
+ @Override
+ @SneakyThrows
+ public void runDataMigration() {
+ fixTestSuites(collectionDAO);
+ lowerCaseUserNameAndEmail(collectionDAO);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v112/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v112/Migration.java
index c30c89930daa..478ff8b6d307 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v112/Migration.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v112/Migration.java
@@ -1,18 +1,14 @@
package org.openmetadata.service.migration.postgres.v112;
-import java.util.List;
-import java.util.Set;
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.fixExecutableTestSuiteFQN;
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.lowerCaseUserNameAndEmail;
+
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.core.Handle;
-import org.openmetadata.schema.tests.TestSuite;
-import org.openmetadata.schema.type.Include;
import org.openmetadata.service.jdbi3.CollectionDAO;
-import org.openmetadata.service.jdbi3.ListFilter;
-import org.openmetadata.service.jdbi3.TestSuiteRepository;
import org.openmetadata.service.migration.api.MigrationProcessImpl;
import org.openmetadata.service.migration.utils.MigrationFile;
-import org.openmetadata.service.util.EntityUtil;
@Slf4j
public class Migration extends MigrationProcessImpl {
@@ -34,25 +30,8 @@ public void initialize(Handle handle) {
@SneakyThrows
public void runDataMigration() {
// Run Data Migration to Remove the quoted Fqn`
- unquoteTestSuiteMigration(collectionDAO);
- }
-
- public static void unquoteTestSuiteMigration(CollectionDAO collectionDAO) {
- TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
- List testSuites =
- testSuiteRepository.listAll(new EntityUtil.Fields(Set.of("id")), new ListFilter(Include.ALL));
- for (TestSuite suite : testSuites) {
- if (Boolean.TRUE.equals(suite.getExecutable())) {
- String fqn = suite.getFullyQualifiedName();
- String updatedFqn = fqn;
- if (fqn.startsWith("\"") && fqn.endsWith("\"")) {
- updatedFqn = fqn.substring(1, fqn.length() - 1);
- }
- // update the name and fqn
- suite.setName(updatedFqn);
- suite.setFullyQualifiedName(updatedFqn);
- collectionDAO.testSuiteDAO().update(suite);
- }
- }
+ fixExecutableTestSuiteFQN(collectionDAO);
+ // Run UserName Migration to make lowercase
+ lowerCaseUserNameAndEmail(collectionDAO);
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v114/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v114/Migration.java
new file mode 100644
index 000000000000..3f0cd4b69466
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v114/Migration.java
@@ -0,0 +1,35 @@
+package org.openmetadata.service.migration.postgres.v114;
+
+import static org.openmetadata.service.migration.utils.V112.MigrationUtil.lowerCaseUserNameAndEmail;
+import static org.openmetadata.service.migration.utils.V114.MigrationUtil.fixTestSuites;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.jdbi.v3.core.Handle;
+import org.openmetadata.service.jdbi3.CollectionDAO;
+import org.openmetadata.service.migration.api.MigrationProcessImpl;
+import org.openmetadata.service.migration.utils.MigrationFile;
+
+@Slf4j
+public class Migration extends MigrationProcessImpl {
+ private CollectionDAO collectionDAO;
+ private Handle handle;
+
+ public Migration(MigrationFile migrationFile) {
+ super(migrationFile);
+ }
+
+ @Override
+ public void initialize(Handle handle) {
+ super.initialize(handle);
+ this.handle = handle;
+ this.collectionDAO = handle.attach(CollectionDAO.class);
+ }
+
+ @Override
+ @SneakyThrows
+ public void runDataMigration() {
+ fixTestSuites(collectionDAO);
+ lowerCaseUserNameAndEmail(collectionDAO);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V112/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V112/MigrationUtil.java
new file mode 100644
index 000000000000..03901456efcc
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V112/MigrationUtil.java
@@ -0,0 +1,52 @@
+package org.openmetadata.service.migration.utils.V112;
+
+import java.util.List;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.openmetadata.schema.entity.teams.User;
+import org.openmetadata.schema.tests.TestSuite;
+import org.openmetadata.schema.type.Include;
+import org.openmetadata.schema.utils.EntityInterfaceUtil;
+import org.openmetadata.service.jdbi3.CollectionDAO;
+import org.openmetadata.service.jdbi3.ListFilter;
+import org.openmetadata.service.jdbi3.TestSuiteRepository;
+import org.openmetadata.service.util.EntityUtil;
+import org.openmetadata.service.util.JsonUtils;
+
+@Slf4j
+public class MigrationUtil {
+ private MigrationUtil() {}
+
+ public static void fixExecutableTestSuiteFQN(CollectionDAO collectionDAO) {
+ TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
+ List testSuites =
+ testSuiteRepository.listAll(new EntityUtil.Fields(Set.of("id")), new ListFilter(Include.ALL));
+ for (TestSuite suite : testSuites) {
+ if (Boolean.TRUE.equals(suite.getExecutable()) && suite.getExecutableEntityReference() != null) {
+ String tableFQN = suite.getExecutableEntityReference().getFullyQualifiedName();
+ String suiteFQN = tableFQN + ".testSuite";
+ suite.setName(suiteFQN);
+ suite.setFullyQualifiedName(suiteFQN);
+ collectionDAO.testSuiteDAO().update(suite);
+ }
+ }
+ }
+
+ public static void lowerCaseUserNameAndEmail(CollectionDAO daoCollection) {
+ LOG.debug("Starting Migration UserName and Email to Lowercase");
+ int total = daoCollection.userDAO().listTotalCount();
+ int offset = 0;
+ int limit = 200;
+ while (offset < total) {
+ List userEntities = daoCollection.userDAO().listAfterWithOffset(limit, offset);
+ for (String json : userEntities) {
+ User userEntity = JsonUtils.readValue(json, User.class);
+ userEntity.setFullyQualifiedName(
+ EntityInterfaceUtil.quoteName(userEntity.getFullyQualifiedName().toLowerCase()));
+ daoCollection.userDAO().update(userEntity);
+ }
+ offset = offset + limit;
+ }
+ LOG.debug("Completed Migrating UserName and Email to Lowercase");
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V114/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V114/MigrationUtil.java
new file mode 100644
index 000000000000..8292f4a317fa
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/V114/MigrationUtil.java
@@ -0,0 +1,110 @@
+package org.openmetadata.service.migration.utils.V114;
+
+import static org.openmetadata.service.Entity.*;
+import static org.openmetadata.service.migration.utils.v110.MigrationUtil.groupTestCasesByTable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.openmetadata.schema.tests.TestCase;
+import org.openmetadata.schema.tests.TestSuite;
+import org.openmetadata.schema.type.Include;
+import org.openmetadata.schema.type.Relationship;
+import org.openmetadata.service.exception.EntityNotFoundException;
+import org.openmetadata.service.jdbi3.CollectionDAO;
+import org.openmetadata.service.jdbi3.ListFilter;
+import org.openmetadata.service.jdbi3.TestSuiteRepository;
+import org.openmetadata.service.util.EntityUtil;
+
+public class MigrationUtil {
+ private MigrationUtil() {
+ /* Cannot create object util class*/
+ }
+
+ /**
+ * Step 1: re-run the fix for FQN to catch any issues from previous release where we were quoting the FQN Step 2:
+ * Group all the testCases with the table. We will create a Map with Table FQN as the key and all the test cases
+ * belonging to that Table Step 3: Iterate through the Map keySet, which is table names. For each table name we create
+ * a executable test suite FQN Step 4: Fetch executable testSuite using step 3 FQN Step 5: Iterate through the test
+ * case list associated with the current table FQN in the loop Step 6: for each test case fetch TestSuite
+ * relationships Step 7: Iterate through the testSuite relation to check if the executableTestSuite FQN matches. If it
+ * matches there exists a relation from testCase to a executable Test suite Step 8: If we can't find a match, create a
+ * relationship.
+ *
+ * @param collectionDAO
+ */
+ public static void fixTestSuites(CollectionDAO collectionDAO) {
+ // Fix any FQN issues for executable TestSuite
+ TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
+ List testSuites =
+ testSuiteRepository.listAll(new EntityUtil.Fields(Set.of("id")), new ListFilter(Include.ALL));
+ for (TestSuite suite : testSuites) {
+ if (suite.getExecutableEntityReference() != null
+ && (!suite.getExecutable() || !suite.getFullyQualifiedName().contains("testSuite"))) {
+ String tableFQN = suite.getExecutableEntityReference().getFullyQualifiedName();
+ String suiteFQN = tableFQN + ".testSuite";
+ suite.setName(suiteFQN);
+ suite.setFullyQualifiedName(suiteFQN);
+ suite.setExecutable(true);
+ collectionDAO.testSuiteDAO().update(suite);
+ }
+ }
+ // Let's iterate through the test cases and make sure there exists a relationship between testcases and its native
+ // TestSuite
+ Map> testCasesGroupByTable = groupTestCasesByTable(collectionDAO);
+ for (String tableFQN : testCasesGroupByTable.keySet()) {
+ List testCases = testCasesGroupByTable.get(tableFQN);
+ String executableTestSuiteFQN = tableFQN + ".testSuite";
+ TestSuite executableTestSuite =
+ testSuiteRepository.getDao().findEntityByName(executableTestSuiteFQN, "fqnHash", Include.ALL);
+ for (TestCase testCase : testCases) {
+ // we are setting mustHaveRelationship to "false" to not throw any error.
+ List existingRelations =
+ testSuiteRepository.findFromRecords(testCase.getId(), TEST_CASE, Relationship.CONTAINS, TEST_SUITE);
+ boolean relationWithExecutableTestSuiteExists = false;
+ if (existingRelations != null) {
+ for (CollectionDAO.EntityRelationshipRecord existingTestSuiteRel : existingRelations) {
+ try {
+ TestSuite existingTestSuite = testSuiteRepository.getDao().findEntityById(existingTestSuiteRel.getId());
+ if (existingTestSuite.getExecutable()
+ && existingTestSuite.getFullyQualifiedName().equals(executableTestSuiteFQN)) {
+ // There is a native test suite associated with this testCase.
+ relationWithExecutableTestSuiteExists = true;
+ }
+ } catch (EntityNotFoundException ex) {
+ // if testsuite cannot be retrieved but the relation exists, then this is orphaned relation, we will
+ // delete the relation
+ testSuiteRepository.deleteRelationship(
+ existingTestSuiteRel.getId(), TEST_SUITE, testCase.getId(), TEST_CASE, Relationship.CONTAINS);
+ }
+ }
+ }
+ // if we can't find any executable testSuite relationship add one
+ if (!relationWithExecutableTestSuiteExists) {
+ testSuiteRepository.addRelationship(
+ executableTestSuite.getId(), testCase.getId(), TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
+ }
+ }
+
+ // check from table -> nativeTestSuite there should only one relation
+ List testSuiteRels =
+ testSuiteRepository.findToRecords(
+ executableTestSuite.getExecutableEntityReference().getId(), TABLE, Relationship.CONTAINS, TEST_SUITE);
+ for (CollectionDAO.EntityRelationshipRecord testSuiteRel : testSuiteRels) {
+ try {
+ TestSuite existingTestSuite = testSuiteRepository.getDao().findEntityById(testSuiteRel.getId());
+ } catch (EntityNotFoundException ex) {
+ // if testsuite cannot be retrieved but the relation exists, then this is orphaned relation, we will
+ // delete the relation
+ testSuiteRepository.deleteRelationship(
+ executableTestSuite.getExecutableEntityReference().getId(),
+ TABLE,
+ testSuiteRel.getId(),
+ TEST_SUITE,
+ Relationship.CONTAINS);
+ }
+ }
+ }
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v110/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v110/MigrationUtil.java
index 46a1485a2e93..1bcc4130f021 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v110/MigrationUtil.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v110/MigrationUtil.java
@@ -4,11 +4,9 @@
import static org.openmetadata.service.Entity.INGESTION_PIPELINE;
import static org.openmetadata.service.Entity.TEST_CASE;
import static org.openmetadata.service.Entity.TEST_SUITE;
+import static org.openmetadata.service.util.EntityUtil.hash;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
@@ -61,7 +59,6 @@
import org.openmetadata.schema.type.Relationship;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
import org.openmetadata.service.Entity;
-import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.CollectionDAO;
import org.openmetadata.service.jdbi3.EntityDAO;
import org.openmetadata.service.jdbi3.IngestionPipelineRepository;
@@ -72,7 +69,6 @@
import org.openmetadata.service.jdbi3.TestSuiteRepository;
import org.openmetadata.service.resources.databases.DatasourceConfig;
import org.openmetadata.service.resources.feeds.MessageParser;
-import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.FullyQualifiedName;
import org.openmetadata.service.util.JsonUtils;
@@ -157,55 +153,60 @@ public static void readAndProcessEntity(
}
while (true) {
// Read from Database
- List jsons = dao.migrationListAfterWithOffset(limitParam, nameHashColumn);
- LOG.debug("[{}]Read a Batch of Size: {}", dao.getTableName(), jsons.size());
- if (jsons.isEmpty()) {
- break;
- }
- // Process Update
- for (String json : jsons) {
- // Update the Statements to Database
- T entity = JsonUtils.readValue(json, clazz);
- try {
- String hash;
- if (entity.getFullyQualifiedName() != null) {
- hash =
- withName
- ? FullyQualifiedName.buildHash(EntityInterfaceUtil.quoteName(entity.getFullyQualifiedName()))
- : FullyQualifiedName.buildHash(entity.getFullyQualifiedName());
- } else {
- LOG.info(
- "Failed in creating FQN Hash for Entity Name : {}, since the FQN is null. Auto Correcting.",
- entity.getName());
- hash =
- withName
- ? FullyQualifiedName.buildHash(EntityInterfaceUtil.quoteName(entity.getName()))
- : FullyQualifiedName.buildHash(entity.getName());
- entity.setFullyQualifiedName(entity.getName());
- dao.update(entity.getId(), entity.getName(), JsonUtils.pojoToJson(entity));
- }
- int result =
- handle
- .createUpdate(updateSql)
- .bind("nameHashColumnValue", hash)
- .bind("id", entity.getId().toString())
- .execute();
- if (result <= 0) {
- LOG.error("No Rows Affected for Updating Hash with Entity Name : {}", entity.getFullyQualifiedName());
+ try {
+ List jsons = dao.migrationListAfterWithOffset(limitParam, nameHashColumn);
+ LOG.debug("[{}]Read a Batch of Size: {}", dao.getTableName(), jsons.size());
+ if (jsons.isEmpty()) {
+ break;
+ }
+ // Process Update
+ for (String json : jsons) {
+ // Update the Statements to Database
+ T entity = JsonUtils.readValue(json, clazz);
+ try {
+ String hash;
+ if (entity.getFullyQualifiedName() != null) {
+ hash =
+ withName
+ ? FullyQualifiedName.buildHash(EntityInterfaceUtil.quoteName(entity.getFullyQualifiedName()))
+ : FullyQualifiedName.buildHash(entity.getFullyQualifiedName());
+ } else {
+ LOG.info(
+ "Failed in creating FQN Hash for Entity Name : {}, since the FQN is null. Auto Correcting.",
+ entity.getName());
+ hash =
+ withName
+ ? FullyQualifiedName.buildHash(EntityInterfaceUtil.quoteName(entity.getName()))
+ : FullyQualifiedName.buildHash(entity.getName());
+ entity.setFullyQualifiedName(entity.getName());
+ dao.update(entity.getId(), entity.getName(), JsonUtils.pojoToJson(entity));
+ }
+ int result =
+ handle
+ .createUpdate(updateSql)
+ .bind("nameHashColumnValue", hash)
+ .bind("id", entity.getId().toString())
+ .execute();
+ if (result <= 0) {
+ LOG.error("No Rows Affected for Updating Hash with Entity Name : {}", entity.getFullyQualifiedName());
+ }
+ } catch (Exception ex) {
+ LOG.error("Failed in creating FQN Hash for Entity Name : {}", entity.getFullyQualifiedName(), ex);
}
- } catch (Exception ex) {
- LOG.error("Failed in creating FQN Hash for Entity Name : {}", entity.getFullyQualifiedName(), ex);
}
+ } catch (Exception ex) {
+ LOG.warn("Failed to list the entities, they might already migrated ", ex);
+ break;
}
+ LOG.debug("End Migration for table : {}", dao.getTableName());
}
- LOG.debug("End Migration for table : {}", dao.getTableName());
}
public static MigrationDAO.ServerMigrationSQLTable buildServerMigrationTable(String version, String statement) {
MigrationDAO.ServerMigrationSQLTable result = new MigrationDAO.ServerMigrationSQLTable();
result.setVersion(String.valueOf(version));
result.setSqlStatement(statement);
- result.setCheckSum(EntityUtil.hash(statement));
+ result.setCheckSum(hash(statement));
return result;
}
@@ -406,11 +407,13 @@ public static void performSqlExecutionAndUpdate(
if (!nullOrEmpty(queryList)) {
for (String sql : queryList) {
try {
- handle.execute(sql);
- migrationDAO.upsertServerMigrationSQL(version, sql, EntityUtil.hash(sql));
+ String previouslyRanSql = migrationDAO.getSqlQuery(hash(sql), version);
+ if ((previouslyRanSql == null || previouslyRanSql.isEmpty())) {
+ handle.execute(sql);
+ migrationDAO.upsertServerMigrationSQL(version, sql, hash(sql));
+ }
} catch (Exception e) {
LOG.error(String.format("Failed to run sql %s due to %s", sql, e));
- throw e;
}
}
}
@@ -450,118 +453,101 @@ public static TestSuite copy(TestSuite entity, CreateEntity request, String upda
return entity;
}
+ /**
+ * Test Suites Migration in 1.0.x -> 1.1.4 1. This is the first time users are migrating from User created TestSuite
+ * to System created native TestSuite Per Table 2. Our Goal with this migration is to list all the test cases and
+ * create .testSuite with executable set to true and associate all of the respective test cases with new native test
+ * suite.
+ *
+ * @param collectionDAO
+ */
@SneakyThrows
public static void testSuitesMigration(CollectionDAO collectionDAO) {
- IngestionPipelineRepository ingestionPipelineRepository = new IngestionPipelineRepository(collectionDAO);
- TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
- TestCaseRepository testCaseRepository = new TestCaseRepository(collectionDAO);
- List testCases = testCaseRepository.listAll(new Fields(Set.of("id")), new ListFilter(Include.ALL));
-
- for (TestCase test : testCases) {
+ // Update existing test suites as logical test suites and delete any ingestion pipeline associated with the existing
+ // test suite
+ migrateExistingTestSuitesToLogical(collectionDAO);
- // Create New Executable Test Suites
- MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(test.getEntityLink());
- // Create new Logical Test Suite
- String testSuiteFqn = entityLink.getEntityFQN() + ".testSuite";
- TestSuite stored;
- try {
- // If entity is found by Hash it is already migrated
- testSuiteRepository
- .getDao()
- .findEntityByName(EntityInterfaceUtil.quoteName(testSuiteFqn), "nameHash", Include.ALL);
- } catch (EntityNotFoundException entityNotFoundException) {
+ // create native test suites
+ TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
+ Map> testCasesByTable = groupTestCasesByTable(collectionDAO);
+ for (String tableFQN : testCasesByTable.keySet()) {
+ String nativeTestSuiteFqn = tableFQN + ".testSuite";
+ List testCases = testCasesByTable.get(tableFQN);
+ if (testCases != null && !testCases.isEmpty()) {
+ MessageParser.EntityLink entityLink =
+ MessageParser.EntityLink.parse(testCases.stream().findFirst().get().getEntityLink());
+ TestSuite newExecutableTestSuite =
+ getTestSuite(
+ collectionDAO,
+ new CreateTestSuite()
+ .withName(FullyQualifiedName.buildHash(nativeTestSuiteFqn))
+ .withDisplayName(nativeTestSuiteFqn)
+ .withExecutableEntityReference(entityLink.getEntityFQN()),
+ "ingestion-bot")
+ .withExecutable(true)
+ .withFullyQualifiedName(nativeTestSuiteFqn);
+ testSuiteRepository.prepareInternal(newExecutableTestSuite);
try {
- // Check if the test Suite Exists, this brings the data on nameHash basis
- stored =
- testSuiteRepository
- .getDao()
- .findEntityByName(EntityInterfaceUtil.quoteName(testSuiteFqn), "nameHash", Include.ALL);
+ testSuiteRepository
+ .getDao()
+ .insert("nameHash", newExecutableTestSuite, newExecutableTestSuite.getFullyQualifiedName());
+ } catch (Exception ex) {
+ LOG.warn("TestSuite %s exists".format(nativeTestSuiteFqn));
+ }
+ // add relationship between executable TestSuite with Table
+ testSuiteRepository.addRelationship(
+ newExecutableTestSuite.getExecutableEntityReference().getId(),
+ newExecutableTestSuite.getId(),
+ Entity.TABLE,
+ TEST_SUITE,
+ Relationship.CONTAINS);
+
+ // add relationship between all the testCases that are created against a table with native test suite.
+ for (TestCase testCase : testCases) {
testSuiteRepository.addRelationship(
- stored.getId(), test.getId(), TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
- stored.setExecutable(true);
- stored.setName(FullyQualifiedName.buildHash(testSuiteFqn));
- // the update() method here internally calls FullyQualifiedName.buildHash so not adding it
- stored.setFullyQualifiedName(EntityInterfaceUtil.quoteName(FullyQualifiedName.buildHash(testSuiteFqn)));
- stored.setDisplayName(testSuiteFqn);
- testSuiteRepository.getDao().update(stored);
- } catch (EntityNotFoundException ex) {
- try {
- TestSuite newExecutableTestSuite =
- getTestSuite(
- collectionDAO,
- new CreateTestSuite()
- .withName(FullyQualifiedName.buildHash(testSuiteFqn))
- .withDisplayName(testSuiteFqn)
- .withExecutableEntityReference(entityLink.getEntityFQN()),
- "ingestion-bot")
- .withExecutable(false);
- // Create
- testSuiteRepository.prepareInternal(newExecutableTestSuite);
- testSuiteRepository
- .getDao()
- .insert("nameHash", newExecutableTestSuite, newExecutableTestSuite.getFullyQualifiedName());
- // Here we aer manually adding executable relationship since the table Repository is not registered and
- // result
- // into null for entity type table
- testSuiteRepository.addRelationship(
- newExecutableTestSuite.getExecutableEntityReference().getId(),
- newExecutableTestSuite.getId(),
- Entity.TABLE,
- TEST_SUITE,
- Relationship.CONTAINS);
-
- // add relationship from testSuite to TestCases
- testSuiteRepository.addRelationship(
- newExecutableTestSuite.getId(), test.getId(), TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
-
- // Not a good approach but executable cannot be set true before
- TestSuite temp =
- testSuiteRepository
- .getDao()
- .findEntityByName(
- EntityInterfaceUtil.quoteName(FullyQualifiedName.buildHash(testSuiteFqn)),
- "nameHash",
- Include.ALL);
- temp.setExecutable(true);
- testSuiteRepository.getDao().update("nameHash", temp);
- } catch (Exception exIgnore) {
- LOG.warn("Ignoring error since already added: {}", ex.getMessage());
- }
+ newExecutableTestSuite.getId(), testCase.getId(), TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
}
}
}
+ }
- // Update Test Suites
+ private static void migrateExistingTestSuitesToLogical(CollectionDAO collectionDAO) {
+ IngestionPipelineRepository ingestionPipelineRepository = new IngestionPipelineRepository(collectionDAO);
+ TestSuiteRepository testSuiteRepository = new TestSuiteRepository(collectionDAO);
ListFilter filter = new ListFilter(Include.ALL);
- filter.addQueryParam("testSuiteType", "logical");
List testSuites = testSuiteRepository.listAll(new Fields(Set.of("id")), filter);
-
- for (TestSuite testSuiteRecord : testSuites) {
- TestSuite temp = testSuiteRepository.getDao().findEntityById(testSuiteRecord.getId(), Include.ALL);
- if (Boolean.FALSE.equals(temp.getExecutable())) {
- temp.setExecutable(false);
- testSuiteRepository.getDao().update(temp);
+ for (TestSuite testSuite : testSuites) {
+ testSuite.setExecutable(false);
+ List ingestionPipelineRecords =
+ collectionDAO
+ .relationshipDAO()
+ .findTo(testSuite.getId().toString(), TEST_SUITE, Relationship.CONTAINS.ordinal(), INGESTION_PIPELINE);
+ for (CollectionDAO.EntityRelationshipRecord ingestionRecord : ingestionPipelineRecords) {
+ // remove relationship
+ collectionDAO.relationshipDAO().deleteAll(ingestionRecord.getId().toString(), INGESTION_PIPELINE);
+ // Cannot use Delete directly it uses other repos internally
+ ingestionPipelineRepository.getDao().delete(ingestionRecord.getId().toString());
}
+ }
+ }
- // get Ingestion Pipelines
- try {
- List ingestionPipelineRecords =
- collectionDAO
- .relationshipDAO()
- .findTo(
- testSuiteRecord.getId().toString(),
- TEST_SUITE,
- Relationship.CONTAINS.ordinal(),
- INGESTION_PIPELINE);
- for (CollectionDAO.EntityRelationshipRecord ingestionRecord : ingestionPipelineRecords) {
- // remove relationship
- collectionDAO.relationshipDAO().deleteAll(ingestionRecord.getId().toString(), INGESTION_PIPELINE);
- // Cannot use Delete directly it uses other repos internally
- ingestionPipelineRepository.getDao().delete(ingestionRecord.getId().toString());
- }
- } catch (EntityNotFoundException ex) {
- // Already Removed
+ public static Map> groupTestCasesByTable(CollectionDAO collectionDAO) {
+ Map> testCasesByTable = new HashMap<>();
+ TestCaseRepository testCaseRepository = new TestCaseRepository(collectionDAO);
+ List testCases = testCaseRepository.listAll(new Fields(Set.of("id")), new ListFilter(Include.ALL));
+ for (TestCase testCase : testCases) {
+ // Create New Executable Test Suites
+ MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(testCase.getEntityLink());
+ // Create new Logical Test Suite
+ ArrayList testCasesGroup = new ArrayList<>();
+ if (testCasesByTable.containsKey(entityLink.getEntityFQN())) {
+ testCasesGroup = testCasesByTable.get(entityLink.getEntityFQN());
+ testCasesGroup.add(testCase);
+ } else {
+ testCasesGroup.add(testCase);
}
+ testCasesByTable.put(entityLink.getEntityFQN(), testCasesGroup);
}
+ return testCasesByTable;
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java
index e51c9c0d19bd..f69d2df833cb 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java
@@ -238,7 +238,7 @@ private static Object createResource(
AuthenticatorHandler authHandler)
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
- Object resource = null;
+ Object resource;
Class> clz = Class.forName(resourceClass);
// Create the resource identified by resourceClass
@@ -252,8 +252,6 @@ private static Object createResource(
} catch (NoSuchMethodException ex) {
resource = Class.forName(resourceClass).getConstructor().newInstance();
}
- } catch (Exception ex) {
- LOG.warn("Exception encountered", ex);
}
// Call initialize method, if it exists
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java
index 0d3c328178d2..7bd500e9920a 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java
@@ -82,16 +82,7 @@ public final Fields getFields(String fields) {
return repository.getFields(fields);
}
- protected T addHref(UriInfo uriInfo, T entity) {
- Entity.withHref(uriInfo, entity.getOwner());
- Entity.withHref(uriInfo, entity.getFollowers());
- Entity.withHref(uriInfo, entity.getExperts());
- Entity.withHref(uriInfo, entity.getReviewers());
- Entity.withHref(uriInfo, entity.getChildren());
- Entity.withHref(uriInfo, entity.getDomain());
- Entity.withHref(uriInfo, entity.getDataProducts());
- return entity;
- }
+ public abstract T addHref(UriInfo uriInfo, T entity);
protected List getEntitySpecificOperations() {
return null;
@@ -284,14 +275,11 @@ protected CsvImportResult importCsvInternal(SecurityContext securityContext, Str
public T copy(T entity, CreateEntity request, String updatedBy) {
EntityReference owner = repository.validateOwner(request.getOwner());
- EntityReference domain = repository.validateDomain(request.getDomain());
entity.setId(UUID.randomUUID());
entity.setName(request.getName());
entity.setDisplayName(request.getDisplayName());
entity.setDescription(request.getDescription());
entity.setOwner(owner);
- entity.setDomain(domain);
- entity.setDataProducts(getEntityReferences(Entity.DATA_PRODUCT, request.getDataProducts()));
entity.setExtension(request.getExtension());
entity.setUpdatedBy(updatedBy);
entity.setUpdatedAt(System.currentTimeMillis());
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java
index 087cdf84770c..5d09b599c5e6 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/ReportDataResource.java
@@ -10,9 +10,11 @@
import java.io.IOException;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
@@ -45,12 +47,12 @@
@Collection(name = "analytics")
public class ReportDataResource {
public static final String COLLECTION_PATH = "v1/analytics/dataInsights/data";
- @Getter protected final ReportDataRepository dao;
+ @Getter protected final ReportDataRepository repository;
protected final Authorizer authorizer;
- public ReportDataResource(CollectionDAO dao, Authorizer authorizer) {
+ public ReportDataResource(CollectionDAO repository, Authorizer authorizer) {
this.authorizer = authorizer;
- this.dao = new ReportDataRepository(dao);
+ this.repository = new ReportDataRepository(repository);
}
public static class ReportDataResultList extends ResultList {
@@ -89,12 +91,11 @@ public ResultList list(
schema = @Schema(type = "number"))
@NonNull
@QueryParam("endTs")
- Long endTs)
- throws IOException {
+ Long endTs) {
OperationContext operationContext = new OperationContext(Entity.DATA_INSIGHT_CHART, MetadataOperation.VIEW_ALL);
ResourceContextInterface resourceContext = ReportDataContext.builder().build();
authorizer.authorize(securityContext, operationContext, resourceContext);
- return dao.getReportData(reportDataType, startTs, endTs);
+ return repository.getReportData(reportDataType, startTs, endTs);
}
@POST
@@ -109,11 +110,41 @@ public ResultList list(
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ReportData.class)))
})
public Response addReportData(
- @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid ReportData reportData)
- throws IOException {
+ @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid ReportData reportData) {
OperationContext operationContext = new OperationContext(Entity.DATA_INSIGHT_CHART, MetadataOperation.CREATE);
ResourceContextInterface resourceContext = ReportDataContext.builder().build();
authorizer.authorize(securityContext, operationContext, resourceContext);
- return dao.addReportData(reportData);
+ return repository.addReportData(reportData);
+ }
+
+ @DELETE
+ @Path("/{reportDataType}/{date}")
+ @Operation(
+ operationId = "deleteReportData",
+ summary = "Delete report data for a given report data type ando date",
+ description = "Delete report data for a given report data type and date.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Successfully deleted report data.",
+ content = @Content(mediaType = "application/json", schema = @Schema(implementation = ReportData.class)))
+ })
+ public Response deleteReportData(
+ @Context UriInfo uriInfo,
+ @Context SecurityContext securityContext,
+ @Parameter(description = "report data type", schema = @Schema(implementation = ReportDataType.class))
+ @NonNull
+ @PathParam("reportDataType")
+ ReportDataType reportDataType,
+ @Parameter(description = "date in format YYYY-MM-DD", schema = @Schema(type = "String"))
+ @NonNull
+ @PathParam("date")
+ String date)
+ throws IOException {
+ OperationContext operationContext = new OperationContext(Entity.DATA_INSIGHT_CHART, MetadataOperation.DELETE);
+ ResourceContextInterface resourceContext = ReportDataContext.builder().build();
+ authorizer.authorize(securityContext, operationContext, resourceContext);
+ repository.deleteReportDataAtDate(reportDataType, date);
+ return Response.ok().build();
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java
index 5355e3ad884d..da811b83f637 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java
@@ -43,6 +43,7 @@
import org.openmetadata.schema.type.EntityHistory;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.MetadataOperation;
+import org.openmetadata.service.Entity;
import org.openmetadata.service.OpenMetadataApplicationConfig;
import org.openmetadata.service.jdbi3.CollectionDAO;
import org.openmetadata.service.jdbi3.ListFilter;
@@ -51,6 +52,7 @@
import org.openmetadata.service.resources.EntityResource;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
+import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@Slf4j
@@ -64,6 +66,13 @@ public class WebAnalyticEventResource extends EntityResource {
public static final String COLLECTION_PATH = "v1/charts/";
- static final String FIELDS = "owner,followers,tags,domain,dataProducts";
+ static final String FIELDS = "owner,followers,tags";
@Override
public Chart addHref(UriInfo uriInfo, Chart chart) {
- super.addHref(uriInfo, chart);
+ chart.setHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, chart.getId()));
+ Entity.withHref(uriInfo, chart.getOwner());
Entity.withHref(uriInfo, chart.getService());
+ Entity.withHref(uriInfo, chart.getFollowers());
return chart;
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java
index 559bb3648930..b2663c70a6c9 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java
@@ -72,14 +72,14 @@
@Collection(name = "dashboards")
public class DashboardResource extends EntityResource {
public static final String COLLECTION_PATH = "v1/dashboards/";
- protected static final String FIELDS =
- "owner,charts,followers,tags,usageSummary,extension,dataModels," + "domain,dataProducts";
+ protected static final String FIELDS = "owner,charts,followers,tags,usageSummary,extension,dataModels";
@Override
public Dashboard addHref(UriInfo uriInfo, Dashboard dashboard) {
- super.addHref(uriInfo, dashboard);
+ Entity.withHref(uriInfo, dashboard.getOwner());
Entity.withHref(uriInfo, dashboard.getService());
Entity.withHref(uriInfo, dashboard.getCharts());
+ Entity.withHref(uriInfo, dashboard.getFollowers());
Entity.withHref(uriInfo, dashboard.getDataModels());
return dashboard;
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dataInsight/DataInsightChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dataInsight/DataInsightChartResource.java
index b3cc6ff6f0b6..59e64ca9d018 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dataInsight/DataInsightChartResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dataInsight/DataInsightChartResource.java
@@ -56,6 +56,7 @@
import org.openmetadata.service.search.SearchClient;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
+import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@Slf4j
@@ -71,6 +72,13 @@ public class DataInsightChartResource extends EntityResource