-
Notifications
You must be signed in to change notification settings - Fork 0
New feature - Callbacks #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
70b72bf
a5fb335
541b6c3
24e5bae
8b9bb71
34e1ef8
783d21c
369591c
a42728e
5a8e8f1
8fc77b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| class CreateIrontrailChangeCallbacks < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] | ||
| def up | ||
| create_table :irontrail_change_callbacks, id: :bigserial do |t| | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd call the feature Iron Trail Extensions instead of callbacks. |
||
| t.column :rec_table, :text, null: false | ||
| t.column :function_name, :text, null: false | ||
| t.column :enabled, :boolean, default: true, null: false | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for enable. Record can be created/deleted as needed to serve the same purpose. |
||
| t.column :created_at, :timestamp | ||
| t.column :updated_at, :timestamp | ||
| end | ||
|
|
||
| add_index :irontrail_change_callbacks, :rec_table | ||
| add_index :irontrail_change_callbacks, [:rec_table, :enabled] | ||
|
|
||
| IronTrail::DbFunctions.new(connection).tap do |db_fun| | ||
| db_fun.install_function('irontrail_log_row_function_with_callbacks') | ||
| end | ||
| end | ||
|
|
||
| def down | ||
| drop_table :irontrail_change_callbacks | ||
|
|
||
| IronTrail::DbFunctions.new(connection).tap do |db_fun| | ||
| db_fun.install_function('irontrail_log_row_function') | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,10 @@ module IronTrail | |
| irontrail_changes | ||
| ].freeze | ||
|
|
||
| OWN_TRACKABLE_TABLES = %w[ | ||
| irontrail_change_callbacks | ||
| ].freeze | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this constant and declare table on |
||
|
|
||
| module SchemaDumper | ||
| def trailer(stream) | ||
| if IronTrail.enabled? | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,12 @@ def initialize(connection) | |
| @connection = connection | ||
| end | ||
|
|
||
| def install_function(function_name) | ||
| path = File.expand_path("#{function_name}.sql", __dir__) | ||
| sql = File.read(path) | ||
| connection.execute(sql) | ||
| end | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this new method and a new |
||
|
|
||
| # Creates the SQL functions in the DB. It will not run the function or create | ||
| # any triggers. | ||
| def install_functions | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason to create this file instead of using the existing one? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| CREATE OR REPLACE FUNCTION irontrail_log_row() | ||
| RETURNS TRIGGER AS $$ | ||
| DECLARE | ||
| u_changes JSONB; | ||
| key TEXT; | ||
| it_meta TEXT; | ||
| it_meta_obj JSONB; | ||
| value_a JSONB; | ||
| value_b JSONB; | ||
| old_obj JSONB; | ||
| new_obj JSONB; | ||
| actor_type TEXT; | ||
| actor_id TEXT; | ||
| created_at TIMESTAMP; | ||
| change_id BIGINT; | ||
| ext_func_name TEXT; | ||
| operation_char TEXT; | ||
|
|
||
| err_text TEXT; err_detail TEXT; err_hint TEXT; err_ctx TEXT; | ||
| BEGIN | ||
| SELECT split_part(split_part(current_query(), '/*IronTrail ', 2), ' IronTrail*/', 1) INTO it_meta; | ||
|
|
||
| IF (it_meta <> '') THEN | ||
| it_meta_obj = it_meta::JSONB; | ||
|
|
||
| IF (it_meta_obj ? '_actor_type') THEN | ||
| actor_type = it_meta_obj->>'_actor_type'; | ||
| it_meta_obj = it_meta_obj - '_actor_type'; | ||
| END IF; | ||
| IF (it_meta_obj ? '_actor_id') THEN | ||
| actor_id = it_meta_obj->>'_actor_id'; | ||
| it_meta_obj = it_meta_obj - '_actor_id'; | ||
| END IF; | ||
| END IF; | ||
|
|
||
| old_obj = row_to_json(OLD); | ||
| new_obj = row_to_json(NEW); | ||
|
|
||
| IF (TG_OP = 'INSERT' AND new_obj ? 'created_at') THEN | ||
| created_at = NEW.created_at; | ||
| ELSIF (TG_OP = 'UPDATE' AND new_obj ? 'updated_at') THEN | ||
| IF (NEW.updated_at <> OLD.updated_at) THEN | ||
| created_at = NEW.updated_at; | ||
| END IF; | ||
| END IF; | ||
|
|
||
| IF (created_at IS NULL) THEN | ||
| created_at = STATEMENT_TIMESTAMP(); | ||
| ELSE | ||
| it_meta_obj = jsonb_set(COALESCE(it_meta_obj, '{}'::jsonb), array['_db_created_at'], TO_JSONB(STATEMENT_TIMESTAMP())); | ||
| END IF; | ||
|
|
||
| IF (TG_OP = 'INSERT') THEN | ||
| INSERT INTO "irontrail_changes" ("actor_id", "actor_type", | ||
| "rec_table", "operation", "rec_id", "rec_new", "metadata", "created_at") | ||
| VALUES (actor_id, actor_type, | ||
| TG_TABLE_NAME, 'i', NEW.id, new_obj, it_meta_obj, created_at) | ||
| RETURNING id INTO change_id; | ||
|
|
||
| operation_char = 'i'; | ||
|
|
||
| ELSIF (TG_OP = 'UPDATE') THEN | ||
| IF (OLD <> NEW) THEN | ||
| u_changes = jsonb_build_object(); | ||
|
|
||
| FOR key IN (SELECT jsonb_object_keys(old_obj) UNION SELECT jsonb_object_keys(new_obj)) | ||
| LOOP | ||
| value_a := old_obj->key; | ||
| value_b := new_obj->key; | ||
| IF value_a IS DISTINCT FROM value_b THEN | ||
| u_changes := u_changes || jsonb_build_object(key, jsonb_build_array(value_a, value_b)); | ||
| END IF; | ||
| END LOOP; | ||
|
|
||
| INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "rec_table", "operation", | ||
| "rec_id", "rec_old", "rec_new", "rec_delta", "metadata", "created_at") | ||
| VALUES (actor_id, actor_type, TG_TABLE_NAME, 'u', NEW.id, old_obj, new_obj, u_changes, it_meta_obj, created_at) | ||
| RETURNING id INTO change_id; | ||
|
|
||
| operation_char = 'u'; | ||
|
|
||
| END IF; | ||
| ELSIF (TG_OP = 'DELETE') THEN | ||
| INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "rec_table", "operation", | ||
| "rec_id", "rec_old", "metadata", "created_at") | ||
| VALUES (actor_id, actor_type, TG_TABLE_NAME, 'd', OLD.id, old_obj, it_meta_obj, created_at) | ||
| RETURNING id INTO change_id; | ||
|
|
||
| operation_char = 'd'; | ||
| END IF; | ||
|
|
||
| IF (change_id IS NOT NULL) THEN | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was this if necessary in your tests? it seems to be checking something that should always be satisfied |
||
| FOR ext_func_name IN | ||
| SELECT function_name | ||
| FROM irontrail_change_callbacks | ||
| WHERE rec_table = TG_TABLE_NAME::TEXT AND enabled = true | ||
| LOOP | ||
| BEGIN | ||
| EXECUTE format('SELECT %I($1, $2, $3)', ext_func_name) | ||
| USING change_id, TG_TABLE_NAME::TEXT, operation_char; | ||
| EXCEPTION | ||
| WHEN OTHERS THEN | ||
| GET STACKED DIAGNOSTICS | ||
| err_text = MESSAGE_TEXT, | ||
| err_detail = PG_EXCEPTION_DETAIL, | ||
| err_hint = PG_EXCEPTION_HINT, | ||
| err_ctx = PG_EXCEPTION_CONTEXT; | ||
|
|
||
| INSERT INTO "irontrail_trigger_errors" ("pg_errcode", "pg_message", | ||
| "err_text", "ex_detail", "ex_hint", "ex_ctx", "op", "table_name", | ||
| "old_data", "new_data", "query", "created_at") | ||
| VALUES (SQLSTATE, SQLERRM, err_text, err_detail, err_hint, | ||
| 'Extension: ' || ext_func_name || E'\n' || err_ctx, | ||
| TG_OP, TG_TABLE_NAME, row_to_json(OLD), row_to_json(NEW), | ||
| 'Extension function: ' || ext_func_name || ' for change_id: ' || change_id, | ||
| STATEMENT_TIMESTAMP()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should add some sort of indication that this error is from an extension (and which one in a structured way) and not the irontrail core. |
||
| END; | ||
| END LOOP; | ||
| END IF; | ||
| RETURN NULL; | ||
| EXCEPTION | ||
| WHEN OTHERS THEN | ||
| GET STACKED DIAGNOSTICS | ||
| err_text = MESSAGE_TEXT, | ||
| err_detail = PG_EXCEPTION_DETAIL, | ||
| err_hint = PG_EXCEPTION_HINT, | ||
| err_ctx = PG_EXCEPTION_CONTEXT; | ||
|
|
||
| INSERT INTO "irontrail_trigger_errors" ("pg_errcode", "pg_message", | ||
| "err_text", "ex_detail", "ex_hint", "ex_ctx", "op", "table_name", | ||
| "old_data", "new_data", "query", "created_at") | ||
| VALUES (SQLSTATE, SQLERRM, err_text, err_detail, err_hint, err_ctx, | ||
| TG_OP, TG_TABLE_NAME, row_to_json(OLD), row_to_json(NEW), current_query(), STATEMENT_TIMESTAMP()); | ||
| RETURN NULL; | ||
| END; | ||
| $$ LANGUAGE plpgsql; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| # frozen_literal_string: true | ||
|
|
||
| module IronTrail | ||
| VERSION = '0.1.8' | ||
| VERSION = '0.2.0' | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't think these
unlesss are necessary.