Class: DagMe::DDL
- Inherits:
-
Object
- Object
- DagMe::DDL
- Defined in:
- lib/dag_me/ddl.rb
Overview
Generates and executes the SQL objects for a dag_me model.
Everything is derived from the model's Configuration. For a tasks table
with prefix task_dag, installs:
task_dag_edges -- source of truth (parent_*, child_* [, scope cols])
task_dag_paths -- transitive closure incl. self-rows
(ancestor_*, descendant_*, min_depth, path_count [, scope cols])
task_dag_lock(text) -- isolation guard + per-scope pg_advisory_xact_lock
task_dag_edge_insert_check -- BEFORE INSERT: scope stamp + lock + cycle rejection
task_dag_edge_insert_apply -- AFTER INSERT: incremental closure expansion
task_dag_edge_delete_apply -- AFTER DELETE: path_count decrement + min_depth repair
task_dag_node_insert -- AFTER INSERT on tasks: self-row
task_dag_node_update -- BEFORE UPDATE on tasks: scope-change guard (scoped only)
task_dag_node_delete -- BEFORE DELETE on tasks: drop edges through triggers
task_dag_rebuild_paths() -- full closure rebuild from edges
task_dag_validate_paths() -- closure vs recursive-CTE truth diff
Node identity is an ordered column list (Configuration#node_pk_columns): single-key models get the classic parent_id / child_id / ancestor_id / descendant_id columns, composite keys get one column per key column (parent_org_id, parent_serial, ...). All joins and comparisons are generated as per-column AND lists, so both shapes share one code path.
Integrity violations RAISE with the DagMe::SQLSTATE_* codes, so the Ruby layer translates them without depending on message wording.
With scope columns, edges may only connect nodes whose scope values match; the trigger stamps the node's scope onto edge and closure rows, so raw SQL writers cannot cross tenants either.
The paths table and its triggers are skipped for maintain: :recursive_cte; cycle rejection then uses a recursive CTE in the BEFORE INSERT trigger.
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Class Method Summary collapse
-
.install!(model) ⇒ Object
Installs / removes the SQL objects for every dag the model declares.
- .uninstall!(model) ⇒ Object
Instance Method Summary collapse
-
#backfill_self_rows_sql ⇒ Object
Graph#backfill_self_rows! reuses this to repair nodes inserted with triggers disabled (e.g. Rails fixture loading).
-
#initialize(config) ⇒ DDL
constructor
A new instance of DDL.
- #install! ⇒ Object
- #install_sql ⇒ Object
- #uninstall! ⇒ Object
- #uninstall_sql ⇒ Object
Constructor Details
#initialize(config) ⇒ DDL
Returns a new instance of DDL.
51 52 53 |
# File 'lib/dag_me/ddl.rb', line 51 def initialize(config) @config = config end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
49 50 51 |
# File 'lib/dag_me/ddl.rb', line 49 def config @config end |
Class Method Details
.install!(model) ⇒ Object
Installs / removes the SQL objects for every dag the model declares.
40 41 42 |
# File 'lib/dag_me/ddl.rb', line 40 def install!(model) model.dag_configs.each_value { |config| new(config).install! } end |
.uninstall!(model) ⇒ Object
44 45 46 |
# File 'lib/dag_me/ddl.rb', line 44 def uninstall!(model) model.dag_configs.each_value { |config| new(config).uninstall! } end |
Instance Method Details
#backfill_self_rows_sql ⇒ Object
Graph#backfill_self_rows! reuses this to repair nodes inserted with triggers disabled (e.g. Rails fixture loading).
81 82 83 84 85 86 87 |
# File 'lib/dag_me/ddl.rb', line 81 def backfill_self_rows_sql <<~SQL INSERT INTO #{config.paths_table} (#{list(anc_cols)}, #{list(desc_cols)}, min_depth, path_count#{scope_column_list}) SELECT #{list(pk_cols)}, #{list(pk_cols)}, 0, 1#{scope_column_list} FROM #{config.node_table} ON CONFLICT DO NOTHING; SQL end |
#install! ⇒ Object
55 56 57 |
# File 'lib/dag_me/ddl.rb', line 55 def install! execute_all(install_sql) end |
#install_sql ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/dag_me/ddl.rb', line 63 def install_sql statements = [edges_table_sql] if config.closure? statements << paths_table_sql statements.concat(closure_function_sql) statements.concat(closure_trigger_sql) statements << backfill_self_rows_sql statements << rebuild_function_sql statements << validate_function_sql else statements.concat(cte_function_sql) statements.concat(cte_trigger_sql) end statements end |
#uninstall! ⇒ Object
59 60 61 |
# File 'lib/dag_me/ddl.rb', line 59 def uninstall! execute_all(uninstall_sql) end |
#uninstall_sql ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/dag_me/ddl.rb', line 89 def uninstall_sql [ "DROP TRIGGER IF EXISTS #{config.trigger_name('node_insert')} ON #{config.node_table};", "DROP TRIGGER IF EXISTS #{config.trigger_name('node_update')} ON #{config.node_table};", "DROP TRIGGER IF EXISTS #{config.trigger_name('node_delete')} ON #{config.node_table};", "DROP TABLE IF EXISTS #{config.paths_table};", "DROP TABLE IF EXISTS #{config.edge_table};", "DROP FUNCTION IF EXISTS #{config.function_ref('lock')}(text);", "DROP FUNCTION IF EXISTS #{config.function_ref('edge_insert_check')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('edge_insert_apply')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('edge_delete_apply')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('node_insert')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('node_update')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('node_delete')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('rebuild_paths')}();", "DROP FUNCTION IF EXISTS #{config.function_ref('validate_paths')}();" ] end |