Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docker/airflow/dags/dot_projects.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@
"date_field":"departure_time"
}
]
},
{
"project_id":"EduProject",
"source_connid": "dot_data",
"source_schema": "education",
"earliest_date_to_sync":"2023-01-01",
"objects":[
{
"object":"students",
"id_field":"student_id",
"date_field":"enrollment_date"
},
{
"object":"courses",
"id_field":"course_id"
},
{
"object":"enrollments",
"id_field":"enrollment_id"
},
{
"object":"excel_grades",
"id_field":"grade_id"
}
]
}
]
}
93 changes: 62 additions & 31 deletions docker/airflow/dags/run_dot_project.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def get_object(
date_field,
source_conn_in,
columns_to_exclude,
):
source_schema_in,
):
"""

Extracts data from object in source Postgres DB and saves to target DOT database in data schema.
Expand All @@ -49,7 +50,7 @@ def get_object(

sql_stmt = (
"SELECT * FROM "
+ connection.schema
+ source_schema_in
+ "."
+ object_name_in
)
Expand All @@ -71,7 +72,7 @@ def get_object(
+ object_name_in
+ "'"
+ "AND table_schema = '"
+ connection.schema
+ source_schema_in
+ "' "
+ " ORDER BY ordinal_position "
)
Expand All @@ -93,7 +94,7 @@ def get_object(
+ " WHERE a.attnum > 0 "
+ " AND NOT a.attisdropped "
+ " AND t.relname = '" + object_name_in + "' "
+ " AND s.nspname = '" + connection.schema + "' "
+ " AND s.nspname = '" + source_schema_in + "' "
+ " ORDER BY a.attnum; "
)
print(sql_stmt)
Expand Down Expand Up @@ -136,8 +137,8 @@ def get_object(


def save_object(
object_name_in, target_conn_in, data_in, column_list_in, type_list_in, source_db_in
):
object_name_in, target_conn_in, data_in, column_list_in, type_list_in, source_db_in, source_schema_in
):
"""

Saves data to target DOT database in data schema.
Expand All @@ -156,7 +157,8 @@ def save_object(
List of table column types for target table
source_db_in: String
Name of source database (same as source connid string)

source_schema_in: String
Name of source schema
"""

# Temporary, replace existing data. TODO support delta loads
Expand Down Expand Up @@ -184,7 +186,7 @@ def save_object(
executemany_batch_page_size=200,
)

schema = "data_" + source_db_in.replace("-", "_")
schema = "data_" + source_db_in.replace("-", "_") + '_' + source_schema_in.replace("-", "_")

# Cascade drop target table if in replace mode.
# This will also drop any DOT model views onto this data
Expand Down Expand Up @@ -234,7 +236,8 @@ def sync_object(
source_conn_in,
target_conn_in,
columns_to_exclude,
):
source_schema_in,
):
"""

Extracts data from object in source Postgres DB and saves to target DOT database in data schema.
Expand Down Expand Up @@ -262,46 +265,72 @@ def sync_object(
date_field,
source_conn_in,
columns_to_exclude,
source_schema_in,
)

# Save the data
save_object(
object_name_in, target_conn_in, data, column_list, type_list, source_conn_in
object_name_in, target_conn_in, data, column_list, type_list, source_conn_in, source_schema_in
)

def drop_tables_in_dot_tests_schema(target_conn_in, schema_to_drop_from):
"""
We are syncing new data where new columns and columns types might change.
Postgres will prevent ALTER TABLE if any views exist, so we will drop all tables in the dot test schema.
These will be recreated in the dot run.
This assumes the dot tests schema is dot_data_tests (defined as variable "schema_to_drop_from").
Clear the DOT tests schema before syncing source data.

New columns / column types can change on sync. Postgres blocks ALTER TABLE
while dependent views exist, so this drops views first, then any leftover
base tables. dbt recreates the views on the next DOT run.

Input
-----
target_conn_in: Target database
schema_to_drop_from: Schema to, err, drop
schema_to_drop_from: Schema to clear (e.g. data_dot_data_public_tests)

Action
------
1) Select schema that holds the tables which will be dropped
2) All tables of the selected schema are dropped from the dot_db database.
1) Set search_path to the tests schema
2) DROP VIEW for every view in that schema
3) DROP TABLE for every BASE TABLE in that schema
"""

with PostgresHook(
postgres_conn_id=target_conn_in, schema=target_conn_in
).get_conn() as conn:
cur = conn.cursor()
query1 = f"SET search_path TO {schema_to_drop_from}"
query2 = f"DO $$ DECLARE " \
f"r RECORD; " \
f"BEGIN " \
f"FOR r IN (SELECT table_name FROM information_schema.tables WHERE table_schema = current_schema()) " \
f"LOOP " \
f"EXECUTE 'DROP TABLE IF EXISTS ' || QUOTE_IDENT(r.table_name) || ' CASCADE'; " \
f"END LOOP; " \
f"END $$; "
cur.execute(query1)
cur.execute(query2)
cur.execute(f"SET search_path TO {schema_to_drop_from}")
# information_schema.tables includes both views and base tables; use
# the matching DROP statement for each object type.
cur.execute(
"""
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN (
SELECT table_name
FROM information_schema.views
WHERE table_schema = current_schema()
)
LOOP
EXECUTE 'DROP VIEW IF EXISTS '
|| quote_ident(r.table_name)
|| ' CASCADE';
END LOOP;

FOR r IN (
SELECT table_name
FROM information_schema.tables
WHERE table_schema = current_schema()
AND table_type = 'BASE TABLE'
)
LOOP
EXECUTE 'DROP TABLE IF EXISTS '
|| quote_ident(r.table_name)
|| ' CASCADE';
END LOOP;
END $$;
"""
)

def run_dot_app(project_id_in):
"""
Expand Down Expand Up @@ -339,7 +368,7 @@ def default_config():
schedule_interval="@weekly",
start_date=datetime(year=2022, month=3, day=1),
catchup=False,
) as dag:
) as dag:
config = json.loads(Variable.get("dot_config", default_var=default_config().read()))

"""
Expand All @@ -364,13 +393,14 @@ def default_config():
objects_to_sync = project["objects"]
earliest_date_to_sync = project["earliest_date_to_sync"]
source_conn = project["source_connid"]
source_schema = project.get("source_schema", "public")

# Drop the tables in the DOT tests schema, so we can import new data, columns and types
schema_to_drop_from = "data_" + source_conn.replace("-", "_") + "_tests"
schema_to_drop_from = "data_" + source_conn.replace("-", "_") + "_" + source_schema.replace("-", "_") + "_tests"
print(schema_to_drop_from)
af_tasks.append(
PythonOperator(
task_id=f"drop_tables_from_schema__{schema_to_drop_from}",
task_id=f"drop_tables_from_schema_{project_id}_{schema_to_drop_from}",
python_callable=drop_tables_in_dot_tests_schema,
op_kwargs={
"target_conn_in": target_conn,
Expand Down Expand Up @@ -407,6 +437,7 @@ def default_config():
"source_conn_in": source_conn,
"target_conn_in": target_conn,
"columns_to_exclude": columns_to_exclude,
"source_schema_in": source_schema,
},
dag=dag,
)
Expand Down
11 changes: 10 additions & 1 deletion docker/dot/dot_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,14 @@ ScanProject1_db:
pass: "{{ env_var('POSTGRES_PASSWORD') }}"
port: 5432
dbname: dot_db
schema: public
schema: data_dot_data_public
threads: 4
EduProject_db:
type: postgres
host: dot_db
user: postgres
pass: "{{ env_var('POSTGRES_PASSWORD') }}"
port: 5432
dbname: dot_db
schema: data_dot_data_education
threads: 4
Loading