LedgerSMB Documentation

Description manuals and libraries
LedgerSMB Documentation > Perl Modules > LedgerSMB::Database::ChangeChecks
Source

NAME

LedgerSMB::Database::ChangeChecks - Data validation checks for schema changes

DESCRIPTION

This module provides the DSL necessary to build the checks being executed before schema change scripts are being run.

Additionally, it defines an API to be used to implement user interfaces. This API is further detailed in the "FORMATTERS" section at the end of this document.

Lastly, the module implements a few driver functions (described in the "FUNCTIONS" section of this document).

SYNOPSIS

  package SomePackage;

  use LedgerSMB::Database::ChangeChecks;

  check "The first check",
     query => qq|SELECT * FROM a_table|,
     description => qq|... extensive description for the user ... |,
     tables => {
        'table_a' => { prim_key => [ 'a', 'b' ] },
        ...
     },
     on_failure => sub {
         my ($dbh, $rows) = @_;

         grid $rows,
           name => 'grid',
           id => 'id',
           table => 'table_a',
           columns => [ 'column1', 'column2', ... ] # column subset
           edit_columns => [ ... one or more columns ..],
           dropdowns => {
             column1 => {
                 value1 => "Text 1",
                 ...,
             },
             column2 => dropdown_sql($dbh, "SELECT value, text FROM b_table"),
             column3 => sub {
                 my $row = shift;
                 # dynamically create option list for this row...
                 return [
                     {value => 1, text => 'Option 1'},
                     ...,
                 ];
             }
           };
     },
     on_submit => sub {
         my ($dbh, $inputs) = @_;

         save_grid $inputs,
           id => 'id',
           name => 'grid',
           table => 'a_table';
     };

  check "The second check",
     query => qq|SELECT * FROM a_table|,
     description => qq|... extensive description for the user ... |,
     on_failure => sub {
         my ($dbh, $rows) = @_;

         choice { remove => 'Remove', retain => 'Retain' };
     },
     on_submit => sub { ... };


  1;

METHODS

This module declares no methods.

FUNCTIONS

Modules designed to run checks and/or bind a user interface to perform user interaction for failing checks, may want to use these functions.

These functions need to be explicitly imported into a using module (as they are marked @EXPORT_OK, but not @EXPORT).

load_checks( $path )

Loads the check definitions from the file designated by $path, returning the checks as a list. $path will be either a filesystem path or a file handle reference.

Unless the input specifies its own package scope, the input will be imported into the main:: package. It's highly recommended to define a package scope in the input.

SECURITY WARNING: Please note that the file indicated by $path is being evaluated (executed). It's considered insecure to pass relative paths to this function.

run_with_formatters($block, $formatters)

Sets up a context of "FORMATTERS" given in $formatters and runs the $block in this context, returning the block's return value(s).

$formatters is a hash reference with the names of the "FORMATTERS" as the hash keys (confirm, describe, grid, provided). The values are coderefs of functions following the respective formatter protocols. When one of the functions isn't provided, it's bound to a failure-generating coderef.

run_checks( $dbh, checks => [ .. ] )

Runs checks previously loaded using load_checks contained in the array reference of the checks argument.

Checks are being run against the database identified by $dbh, which must be opened by database superuser or the database owner (i.e. a LedgerSMB database admin).

Returns true when checks have successfully completed, false if one of the checks has failed. For the failing check, the on_failure event has been called on return.

The caller is expected to repeat the run_checks call with a provided formatter bound to a function which provides replacement values to update the table content with, in case of an unsuccessful return.

DSL keywords for check definition

The keyword(s) in this section will be automatically imported into the active namespace when this module is used. It's therefore highly recommended to declare a specific namespace in each file using this module.

Checks defined in an input file are distinguished by their declared title. It's therefore not possible to declare multiple checks with the same title in a single file.

check( $title, ... )

Defines a query to be run as part of schema upgrades. Each check requires a title and a number of keyword arguments. The title is used to present the check to the user; it's meant as a short description.

Further keyword arguments are:

description

Required. Contains a longer description of what the check means to achieve and explains which options the user is being presented with and what the user is supposed to do to resolve the situation.

The string is interpreted as Markdown.

tables

Required when a check involves either the grid or save_grid DSL keywords.

Contains a hash reference with table names as the keys and hashes of table attributes as the values. These attributes prevent duplication of arguments across the grid and save_grid keywords.

   tables => {
       'some-table' => {
          prim_key   =>  [ 'a', 'b', 'c' ],
       },
       'some-other-table' => {
          prim_key   =>  [ 'd', 'e', 'f' ],
       }
   }
query

Required. Specifies the SQL query to be run to identify data non-compliant with the intended change to be applied. This query returns those rows failing the compliance check. When this query returns any rows, the check is considered to have "failed", causing the on_failure event to be triggered.

Note that the query may be executed multiple times during the upgrade process. The query may therefore not modify the database in any way.

on_failure

Required. A coderef pointing to a function of 3 argument.

   sub {
      my ($check, $dbh, $rows) = @_;

      describe;
      grid $rows,
        table => 'some-table'
        name => 'the-grid';
      confirm left => 'Left', right => 'Right';
   }
$check

A hashref holding the check's configuration as defined in the source.

$dbh

The database handle against which the check query was run.

$rows

An arrayref holding the rows which failed the check -- i.e. those returned by the query.

The on_failure coderef makes use of the user interface defining elements of the pre-check DSL: grid, confirm, dropdown_sql.

The number of times the on_failure function is executed is undefined and the function is likely to be run multiple times, possibly even within a single invocation of run_tests.

on_submit

Required. A coderef pointing to a function of 2 arguments.

   sub {
      my ($dbh, $rows) = @_;

      save_grid $dbh, $rows,
        name => 'the-grid',
        table => 'some-table';
   }
$dbh

The database handle against which the check query was run.

$rows

The failing rows, retrieved from the database. These rows can be used to validate input provided through the UI for validity. This process has been implemented in save_grid, which will only accept modified values for internally identified failing rows -- as a measure for security.

The 'on_submit' coderef makes use of the data-modifying elements of the pre-check DSL: save_grid. Alternatively, the code can use the $dbh provided to directly modify the database contents.

DSL keywords for 'on_failure' event

This event will be triggered when a query returns any rows, indicating the schema contains data not compliant with the intended change.

The general purpose for this event is to define the UI to be presented as required to make the data pass the compliance check.

Please note that the code in this event should not modify the database or any context in general: the code may be run more than once and the code may even be run with different formatters bound than expected.

describe [ $msg ]

Used to explain the test that has been performed and the repair options shown as well as what the user is expected to do in order to resolve the problem detected.

Without $msg, presents the content of the description as provided through the check definition statement.

confirm [ value1 => 'Description1', value2 => ..., ... ]

Used to render confirmation options for the user to confirm the data entered. The intended way to render a confirmation is to render a button.

grid $rows, [ name => $string, table => $string, ... ]

Used to render a grid with the rows as indicated in the $rows arrayref.

The following keys are available:

name

Required. Names the grid in order to be able to extract the (changed) values from the returned data through the provided dsl keyword.

table

Optional. Names one of the tables specified through the tables keyword of the check definition.

When the name of the grid equals the name of one of the tables in the check as provided through the tables keyword, there's no need to specify this keyword.

columns

Required. Names the columns to be rendered (visibly) on the UI.

edit_columns

Required. Names the columns which should be editable on the UI. This should be a subset of columns.

Optional. Contains a hashref with the keys being a subset of the columns for which a dropdown should be rendered and the values being one of:

1) A hashref mapping the values of the field to description for each option.

2) A callback function to dynamically generate the list of options for each row. This should return an arrayref containing a hashref for each row defining value and text for each option. The function is called with a hashref argument containing key/value pairs for each field in the current row.

A column doesn't need to be editable in order for a dropdown to be applied; the UI is supposed to show a read-only dropdown element when the column is marked as dropdown but not as editable.

For an example see the "SYNOPSIS" section above.

Expects a query with a two-column result; the first column being the values expected in the column to which the dropdown is applied. The second being the descriptions to be shown instead of the actual values in the column.

This function can be used in the "value" position of the key/value pairs as meant in the dropdowns keyword as shown in the "SYNOPSIS" section above.

DSL keywords for 'on_submit' event

The 'on_submit' event is triggered when run_tests detects a check failure and provided indicates there's corrective data available to be applied for the check at hand.

Code in this event handler can make use of the database handle passed and modify the database content directly. Alternatively, DSL keywords are available to do some of the heavy-lifting and/or UI interaction.

provided [ $name [, key => value, ... ]

Used to access UI responses from elements named in the on_failure phase.

See the documentation in the FORMATTERS section.

save_grid $dbh, $failed_rows [, name => $name, table => $table, ... ]

Iterates over $failed_rows, finding input for those rows as provided in the UI and applying the fixed data to the database using $dbh. Parameters are retrieved from the grid declaration with the same name in the on_failure event. Arguments to this function can be used to override the values in the grid declaration. The columns to be stored by this command are taken to be the edit_columns from that table.

The following keys are supported:

name

Required. The name of the grid to be saved; used as argument to provided to query the replacement data.

table

Optional. The name of the table to save the data to. If not provided, defaults to the value provided in the name argument.

edit_columns

Optional. Overrides the value of the columns to be saved as would have been taken from the associated grid declaration.

column_transforms

Optional. A hash with as its keys names of columns to be included when saving the data from the grid. The values of the hash elements may be a code reference which will be executed for each saved row, or something else, in which case that value is taken to be constant for all rows.

The code reference receives as its first argument the value of the input parameter by the same name received from the grid, if such a parameter exists.

Note that column_transforms can be declared for columns in the set of edit_columns as well as any other existing column in the table to be updated.

FORMATTERS

Formatters implement the UI of the checks. This way, the UI can be anything from a web request/response based implementation to a terminal/ncurses solution.

The formatters have the same arguments as their API equivalents, except that each formatter has a reference to the $check in progress prepended to the argument list. E.g. grid $check, $rows, ....

Formatters implement callbacks that will be called from the checks while processing any of the events. During the on_failure event, this usually will mean output generation, while the on_submit event will want to query the UI for input provided.

The following output hooks have to be provided, all quite high level, leaving the implementation with lots of room to render the output.

confirm

Offers the user a way to indicate (s)he is done providing input for the given event.

describe

Shows the check's title and long description, informing the user about the intent of the check and the implications of the various resolutions offered.

The long description must be interpreted as Markdown and should be formatted appropriately for the target UI.

grid

Renders a grid with the columns indicated in the arguments. For each row, there's one magic column that needs to be reproduced in the on_submit event which isn't listed in any of the columns: the __pk column.

Next to the output formatters, these input-requesting routines are to be supplied:

provided $check [, $name ]

Called to retrieve input provided to the UI.

When called without parameters, returns a boolean value indicating whether any inputs are available for processing at all for the given check. In other words, during the on_failure phase, this callback is supposed to return a falsy value, while in the on_submit phase, a true-ish value must be returned.

When called with a $name argument, the value(s) of a specific element rendered in the on_failure phase for the given $check must be returned. These are the expected return value types per named rendered output:

The name of the confirm UI elements is "confirm".

grid

grid inputs are returned using an arrayref of hashrefs holding at least the magical __pk column value and the values of the columns named in edit_columns.

Note that the composition of the values in the __pk column is explicitly declared internal (and thus can't be depended upon).

confirm

Returns the value associated with the selected/pressed/clicked description.

LICENSE AND COPYRIGHT

Copyright (C) 2018 The LedgerSMB Core Team

This file is licensed under the GNU General Public License version 2, or at your option any later version. A copy of the license should have been included with your software.