File Coverage

blib/lib/Workflow/Persister/DBI.pm
Criterion Covered Total %
statement 189 233 81.1
branch 29 52 55.7
condition 6 17 35.2
subroutine 28 32 87.5
pod 17 17 100.0
total 269 351 76.6


line stmt bran cond sub pod time code
1             package Workflow::Persister::DBI;
2              
3 18     18   593215 use warnings;
  18         43  
  18         1514  
4 18     18   116 use strict;
  18         40  
  18         480  
5 18     18   232 use v5.14.0;
  18         69  
6 18     18   104 use parent qw( Workflow::Persister );
  18         58  
  18         141  
7 18     18   5302 use DateTime;
  18         3117508  
  18         701  
8 18     18   9147 use DateTime::Format::Strptime;
  18         1064934  
  18         187  
9 18     18   29763 use DBI;
  18         466521  
  18         1900  
10 18     18   199 use Workflow::Exception qw( configuration_error persist_error );
  18         59  
  18         1312  
11 18     18   8429 use Workflow::Persister::RandomId;
  18         61  
  18         194  
12 18     18   10954 use Workflow::Persister::DBI::AutoGeneratedId;
  18         97  
  18         123  
13 18     18   11756 use Workflow::Persister::DBI::SequenceId;
  18         72  
  18         147  
14 18     18   1199 use Carp qw(croak);
  18         42  
  18         1223  
15 18     18   105 use Syntax::Keyword::Try;
  18         38  
  18         87  
16 18     18   15473 use Readonly;
  18         131178  
  18         104802  
17              
18             Readonly::Scalar my $TRUE => 1;
19             Readonly::Scalar my $FALSE => 0;
20              
21             $Workflow::Persister::DBI::VERSION = '2.09';
22              
23             my @FIELDS = qw( _wf_fields _hist_fields handle dsn user password driver
24             workflow_table history_table date_format parser autocommit options);
25             __PACKAGE__->mk_accessors(@FIELDS);
26              
27              
28             sub init {
29 17     17 1 54 my ( $self, $params ) = @_;
30 17         186 $self->SUPER::init($params);
31              
32             # Default to old date format if not provided so we don't break old configurations.
33 17         3755 $self->date_format('%Y-%m-%d %H:%M');
34              
35             # Default to autocommit on for backward compatibility.
36 17         288 $self->autocommit($TRUE);
37              
38             # Load user-provided values from config.
39 17         190 for (qw( dsn user password date_format autocommit )) {
40 85 100       717 $self->$_( $params->{$_} ) if ( defined $params->{$_} );
41             }
42 17         49 my %options = ();
43 17 50       96 if ( defined $params->{options} ) {
44 0         0 for my $option ( @{ $params->{options} } ) {
  0         0  
45 0         0 $options{$option->{name}} = $option->{value};
46             }
47             }
48 17         84 $self->options( \%options );
49 17         219 $self->handle($self->create_handle);
50             my $driver
51 17 100 50     370 = $self->handle ? $self->handle->{Driver}->{Name} : ($params->{driver} || '');
52 17         1037 $self->log->debug( "Pulled driver '$driver' from DBI DSN" );
53 17         130 $self->driver($driver);
54 17         451 $self->assign_generators( $params, $driver );
55 17         274 $self->log->info(
56             "Assigned workflow generator '",
57             ref( $self->workflow_id_generator ),
58             "'; ",
59             "history generator '",
60             ref( $self->history_id_generator ),
61             "'"
62             );
63 17         483 $self->assign_tables($params);
64 17         380 $self->log->info(
65             "Assigned workflow table '",
66             $self->workflow_table, "'; ", "history table '",
67             $self->history_table, "'"
68             );
69              
70 17         495 my $parser
71             = DateTime::Format::Strptime->new( pattern => $self->date_format );
72 17         35660 $self->parser($parser);
73             }
74              
75             sub create_handle {
76 16     16 1 236 my ($self, $params) = @_;
77 16 50       282 unless ( $self->dsn ) {
78 0         0 configuration_error "DBI persister configuration must include ",
79             "key 'dsn' which maps to the first paramter ",
80             "in the DBI 'connect()' call.";
81             }
82 16         414 my $dbh;
83             try {
84             $dbh = DBI->connect(
85             $self->dsn,
86             $self->user,
87             $self->password,
88             $self->options
89             )
90             or croak "Cannot connect to database: $DBI::errstr";
91             }
92 16         51 catch ($error) {
93             persist_error $error;
94             }
95 16         415802 $dbh->{RaiseError} = $TRUE;
96 16         352 $dbh->{PrintError} = $FALSE;
97 16         331 $dbh->{ChopBlanks} = $TRUE;
98 16         347 $dbh->{AutoCommit} = $self->autocommit();
99 16         645 $self->log->debug( "Connected to database '",
100             $self->dsn, "' and ", "assigned to persister ok" );
101              
102 16         453 return $dbh;
103             }
104              
105             sub assign_generators {
106 17     17 1 73 my ( $self, $params, $driver ) = @_;
107 17         407 $self->SUPER::assign_generators($params);
108             return
109 17 50 33     160 if ($self->workflow_id_generator
110             and $self->history_id_generator );
111              
112 17         233 my ( $wf_gen, $history_gen );
113 17 100       189 if ( $driver eq 'Pg' ) {
    50          
    50          
    50          
114 1         4 $self->log->debug("Assigning ID generators for PostgreSQL");
115 1         9 ( $wf_gen, $history_gen ) = $self->init_postgres_generators($params);
116             } elsif ( $driver eq 'Oracle' ) {
117 0         0 $self->log->debug("Assigning ID generators for Oracle");
118 0         0 ( $wf_gen, $history_gen ) = $self->init_oracle_generators($params);
119             } elsif ( $driver eq 'mysql' ) {
120 0         0 $self->log->debug("Assigning ID generators for MySQL");
121 0         0 ( $wf_gen, $history_gen ) = $self->init_mysql_generators($params);
122             } elsif ( $driver eq 'SQLite' ) {
123 0         0 $self->log->debug("Assigning ID generators for SQLite");
124 0         0 ( $wf_gen, $history_gen ) = $self->init_sqlite_generators($params);
125             } else {
126 16         73 $self->log->debug("Assigning random ID generators");
127 16         193 ( $wf_gen, $history_gen ) = $self->init_random_generators($params);
128             }
129 17         82 $self->workflow_id_generator($wf_gen);
130 17         398 $self->history_id_generator($history_gen);
131             }
132              
133             sub init_postgres_generators {
134 1     1 1 4 my ( $self, $params ) = @_;
135 1         2 my $sequence_select = q{SELECT NEXTVAL( '%s' )};
136 1   50     9 $params->{workflow_sequence} ||= 'workflow_seq';
137 1   50     7 $params->{history_sequence} ||= 'workflow_history_seq';
138             return (
139             Workflow::Persister::DBI::SequenceId->new(
140             { sequence_name => $params->{workflow_sequence},
141             sequence_select => $sequence_select
142             }
143             ),
144             Workflow::Persister::DBI::SequenceId->new(
145             { sequence_name => $params->{history_sequence},
146 1         14 sequence_select => $sequence_select
147             }
148             )
149             );
150             }
151              
152             sub init_oracle_generators {
153 0     0 1 0 my ( $self, $params ) = @_;
154 0         0 my $sequence_select = q{SELECT %s.NEXTVAL from dual};
155 0   0     0 $params->{workflow_sequence} ||= 'workflow_seq';
156 0   0     0 $params->{history_sequence} ||= 'workflow_history_seq';
157             return (
158             Workflow::Persister::DBI::SequenceId->new(
159             { sequence_name => $params->{workflow_sequence},
160             sequence_select => $sequence_select
161             }
162             ),
163             Workflow::Persister::DBI::SequenceId->new(
164             { sequence_name => $params->{history_sequence},
165 0         0 sequence_select => $sequence_select
166             }
167             )
168             );
169             }
170              
171             sub init_mysql_generators {
172 0     0 1 0 my ( $self, $params ) = @_;
173 0         0 my $generator = Workflow::Persister::DBI::AutoGeneratedId->new(
174             { from_handle => 'database',
175             handle_property => 'mysql_insertid',
176             }
177             );
178 0         0 return ( $generator, $generator );
179             }
180              
181             sub init_sqlite_generators {
182 0     0 1 0 my ( $self, $params ) = @_;
183 0         0 my $generator = Workflow::Persister::DBI::AutoGeneratedId->new(
184             { func_property => 'last_insert_rowid' } );
185 0         0 return ( $generator, $generator );
186             }
187              
188             sub assign_tables {
189 17     17 1 405 my ( $self, $params ) = @_;
190 17   50     302 my $wf_table = $params->{workflow_table} || 'workflow';
191 17   50     240 my $hist_table = $params->{history_table} || 'workflow_history';
192 17         97 $self->workflow_table($wf_table);
193 17         250 $self->history_table($hist_table);
194             }
195              
196             ########################################
197             # PERSISTENCE IMPLEMENTATION
198              
199             sub create_workflow {
200 22     22 1 65 my ( $self, $wf ) = @_;
201              
202 22         121 $self->_init_fields();
203 22         911 my @wf_fields = @{ $self->_wf_fields };
  22         99  
204 22         321 my @fields = @wf_fields[ 1, 2, 3 ];
205 22         131 my @values = (
206             $wf->type,
207             $wf->state,
208             DateTime->now( time_zone => $wf->time_zone() )
209             ->strftime( $self->date_format() ),
210             );
211 22         68951 my $dbh = $self->handle;
212              
213 22         372 my $id = $self->workflow_id_generator->pre_fetch_id($dbh);
214 22 50       124 if ($id) {
215 22         70 push @fields, $wf_fields[0];
216 22         51 push @values, $id;
217 22         135 $self->log->debug("Got ID from pre_fetch_id: $id");
218             }
219 22         120 my $sql = 'INSERT INTO %s ( %s ) VALUES ( %s )';
220              
221             $sql = sprintf $sql,
222             $self->handle->quote_identifier( $self->workflow_table ),
223             join( ', ', @fields ),
224 22         113 join( ', ', map {'?'} @values );
  88         1654  
225              
226 22 50       117 if ( $self->log->is_debug ) {
227 0         0 $self->log->debug("Will use SQL: $sql");
228 0         0 $self->log->debug( "Will use parameters: ", join ', ', @values );
229             }
230              
231 22         375 my ($sth);
232             try {
233             $sth = $dbh->prepare($sql);
234             $sth->execute(@values);
235             }
236 22         76 catch ($error) {
237             persist_error "Failed to create workflow: $error";
238             }
239 22 50       5902 unless ($id) {
240 0         0 $id = $self->workflow_id_generator->post_fetch_id( $dbh, $sth );
241 0 0       0 unless ($id) {
242 0         0 persist_error "No ID found using generator '",
243             ref( $self->workflow_id_generator ), "'";
244             }
245             }
246 22         150 $sth->finish;
247              
248 22         727 $wf->id($id);
249 22         248 return $id;
250             }
251              
252             sub fetch_workflow {
253 7     7 1 24 my ( $self, $wf_id ) = @_;
254 7         32 $self->_init_fields();
255 7         87 my $sql = q{SELECT %s, %s FROM %s WHERE %s = ?};
256 7         15 my @wf_fields = @{ $self->_wf_fields };
  7         22  
257 7         114 $sql = sprintf $sql,
258             $wf_fields[2], $wf_fields[3],
259             $self->handle->quote_identifier( $self->workflow_table ),
260             $wf_fields[0];
261              
262 7 50       486 if ( $self->log->is_debug ) {
263 0         0 $self->log->debug("Will use SQL: $sql");
264 0         0 $self->log->debug("Will use parameters: $wf_id");
265             }
266              
267 7         118 my ($sth);
268             try {
269             $sth = $self->handle->prepare($sql);
270             $sth->execute($wf_id);
271             }
272 7         21 catch ($error) {
273             persist_error "Cannot fetch workflow: $error";
274             }
275 7         1200 my $row = $sth->fetchrow_arrayref;
276 7 100       791 return unless ($row);
277              
278             return {
279 6         36 state => $row->[0],
280             last_update => $self->parser->parse_datetime( $row->[1] ),
281             };
282             }
283              
284             sub update_workflow {
285 34     34 1 109 my ( $self, $wf ) = @_;
286 34         434 $self->_init_fields();
287 34         453 my $sql = q{UPDATE %s SET %s = ?, %s = ? WHERE %s = ?};
288 34         73 my @wf_fields = @{ $self->_wf_fields };
  34         143  
289 34         585 $sql = sprintf $sql,
290             $self->handle->quote_identifier( $self->workflow_table ),
291             $wf_fields[2], $wf_fields[3], $wf_fields[0];
292 34         2949 my $update_date = DateTime->now( time_zone => $wf->time_zone() )
293             ->strftime( $self->date_format() );
294              
295 34 50       29146 if ( $self->log->is_debug ) {
296 0         0 $self->log->debug("Will use SQL: $sql");
297 0         0 $self->log->debug( "Will use parameters: ",
298             join ', ', $wf->state, $update_date, $wf->id );
299             }
300              
301 34         535 my ($sth);
302             try {
303             $sth = $self->handle->prepare($sql);
304             $sth->execute( $wf->state, $update_date, $wf->id );
305             }
306 34         102 catch ($error) {
307             persist_error $error;
308             }
309 34         9751 $self->log->info( "Workflow ", $wf->id, " updated ok" );
310             }
311              
312             sub create_history {
313 56     56 1 200 my ( $self, $wf, @history ) = @_;
314 56         235 $self->_init_fields();
315              
316 56         766 my $dbh = $self->handle;
317 56         770 my $generator = $self->history_id_generator;
318 56         710 foreach my $h (@history) {
319 30 50       99 next if ( $h->is_saved );
320 30         157 my $id = $generator->pre_fetch_id($dbh);
321 30         78 my @hist_fields = @{ $self->_hist_fields };
  30         111  
322 30         458 my @fields = @hist_fields[ 1 .. 6 ];
323 30         115 my @values = (
324             $wf->id, $h->action, $h->description, $h->state, $h->user,
325             $h->date->strftime( $self->date_format() ),
326             );
327 30 50       4924 if ($id) {
328 30         166 push @fields, $hist_fields[0];
329 30         123 push @values, $id;
330             }
331 30         75 my $sql = 'INSERT INTO %s ( %s ) VALUES ( %s )';
332              
333             $sql = sprintf $sql, $dbh->quote_identifier( $self->history_table ),
334 30         175 join( ', ', @fields ), join( ', ', map {'?'} @values );
  210         2229  
335 30 50       150 if ( $self->log->is_debug ) {
336 0         0 $self->log->debug("Will use SQL: $sql");
337 0         0 $self->log->debug( "Will use parameters: ", join ', ', @values );
338             }
339              
340 30         395 my ($sth);
341             try {
342             $sth = $dbh->prepare($sql);
343             $sth->execute(@values);
344             }
345 30         93 catch ($error) {
346             persist_error $error;
347             }
348 30 50       6328 unless ($id) {
349 0         0 $id = $self->history_id_generator->post_fetch_id( $dbh, $sth );
350 0 0       0 unless ($id) {
351 0         0 persist_error "No ID found using generator '",
352             ref( $self->history_id_generator ), "'";
353             }
354             }
355 30         194 $h->id($id);
356 30         542 $h->set_saved();
357 30         138 $self->log->info( "Workflow history entry ", $id, " created ok" );
358             }
359 56         729 return @history;
360             }
361              
362             sub fetch_history {
363 4     4 1 15 my ( $self, $wf ) = @_;
364 4         21 $self->_init_fields();
365              
366 4         69 my $sql = qq{SELECT %s FROM %s WHERE %s = ? ORDER BY %s DESC};
367 4         11 my @hist_fields = @{ $self->_hist_fields };
  4         14  
368 4         73 my $history_fields = join ', ', @hist_fields;
369 4         19 $sql = sprintf $sql, $history_fields,
370             $self->handle->quote_identifier($self->history_table),
371             $hist_fields[1], $hist_fields[6];
372              
373 4 50       308 if ( $self->log->is_debug ) {
374 0         0 $self->log->debug("Will use SQL: $sql");
375 0         0 $self->log->debug( "Will use parameters: ", $wf->id );
376             }
377              
378 4         53 my ($sth);
379             try {
380             $sth = $self->handle->prepare($sql);
381             $sth->execute( $wf->id );
382             }
383 4         14 catch ($error) {
384             $self->log->error("Caught error fetching workflow history: $error");
385             persist_error $error;
386             }
387 4         816 $self->log->debug("Prepared and executed ok");
388              
389 4         23 my @history = ();
390 4         29 while ( my $row = $sth->fetchrow_arrayref ) {
391 2         321 $self->log->debug("Fetched history object '$row->[0]'");
392 2         283 push @history, {
393             id => $row->[0],
394             workflow_id => $row->[1],
395             action => $row->[2],
396             description => $row->[3],
397             state => $row->[4],
398             user => $row->[5],
399             date => $self->parser->parse_datetime( $row->[6] ),
400             };
401             }
402 4         2915 $sth->finish;
403 4         131 return @history;
404             }
405              
406             sub commit_transaction {
407 56     56 1 201 my ( $self, $wf ) = @_;
408 56 50       308 if ( not $self->autocommit() ) {
409             try {
410             $self->handle->commit();
411             }
412 0         0 catch ($error) {
413             $self->log->error("Caught error committing transaction: $error");
414             persist_error $error;
415             }
416 0         0 $self->log->debug('Committed transaction.');
417             }
418             }
419              
420             sub rollback_transaction {
421 0     0 1 0 my ( $self, $wf ) = @_;
422 0 0       0 if ( not $self->autocommit() ) {
423             try {
424             $self->handle->rollback();
425             }
426 0         0 catch ($error) {
427             $self->log->error("Caught error rolling back transaction: $error");
428             persist_error $error;
429             }
430 0         0 $self->log->debug('Rolled back transaction.');
431             }
432             else {
433 0         0 $self->log->warn(
434             'Transaction NOT rolled back due to "autocommit" being enabled.'
435             );
436             }
437             }
438              
439             ##########
440             # FIELDS
441              
442             # Allow subclasses to override the fieldnames
443              
444             sub _init_fields {
445 123     123   304 my ($self) = @_;
446 123 100       518 unless ( $self->_wf_fields ) {
447             $self->_wf_fields(
448             [
449             map {
450 14         291 $self->handle->quote_identifier($_)
  56         2572  
451             } $self->get_workflow_fields()
452             ]);
453             }
454 123 100       2392 unless ( $self->_hist_fields ) {
455             $self->_hist_fields(
456             [
457             map {
458 14         254 $self->handle->quote_identifier($_)
  98         2731  
459             } $self->get_history_fields()
460             ]);
461             }
462             }
463              
464             sub get_workflow_fields {
465 13     13 1 56 return qw( workflow_id type state last_update );
466             }
467              
468             sub get_history_fields {
469 13     13 1 61 return qw( workflow_hist_id workflow_id
470             action description state
471             workflow_user history_date );
472             }
473              
474             1;
475              
476             __END__
477              
478             =pod
479              
480             =head1 NAME
481              
482             Workflow::Persister::DBI - Persist workflow and history to DBI database
483              
484             =head1 VERSION
485              
486             This documentation describes version 2.09 of this package
487              
488             =head1 SYNOPSIS
489              
490             # persister.yaml
491             persister:
492             - name: MainDatabase
493             class: Workflow::Persister::DBI
494             dsn: DBI:mysql:database=workflows
495             user: wf
496             password: mypass
497             - name: BackupDatabase
498             class: Workflow::Persister::DBI
499             user: wf
500             password: mypass
501             date_format: '%Y-%m-%d %H:%M'
502             autocommit: 0
503             workflow_table: wf
504             workflow_sequence: wf_seq
505             history_table: wf_history
506             history_sequence: wf_history_seq
507             - name: OtherDatabase
508             class: My::Persester::DBHFromElsewhere
509             driver: mysql
510              
511              
512             =head1 DESCRIPTION
513              
514             Main persistence class for storing the workflow and workflow history
515             records to a DBI-accessible datasource.
516              
517             =head2 Subclassing: Getting handle from elsewhere
518              
519             A common need to create a subclass is to use a database handle created
520             with other means. For instance, L<LedgerSMB|https://ledgersmb.org/> has
521             a centrally managed database connection. So we can create a subclass to
522             provide the database handle from the central storage location
523             C<LedgerSMB::App_State>. A sample implementation is below.
524              
525             package Workflow::Persister::DBI::LedgerSMB_Handle;
526              
527             use strict;
528             use parent qw( Workflow::Persister::DBI );
529             use LedgerSMB::App_State;
530              
531             # override parent method, assuming that we set the 'datasource'
532             # parameter in the persister declaration
533              
534             sub init {
535             # expressly don't call the parent; we don't want to set
536             # connection parameters...
537             }
538              
539             # suppress the parent from trying to connect to the database
540             sub create_handle { return undef; }
541              
542             sub handle {
543             return LedgerSMB::App_State::DBH();
544             }
545              
546             =head2 Subclassing: Changing fieldnames
547              
548             Earlier versions of Workflow used the field 'user' to record in the
549             history the user making a state change or comment. Unfortunately
550             'user' is a reserved word in our favorite database,
551             PostgreSQL. (Oops.) So in addition to changing the field to an
552             assuredly-unreserved word (workflow_user), we made the fieldnames
553             customizable by subclasses.
554              
555             Just override either or both of the methods:
556              
557             =head3 get_workflow_fields()
558              
559             Return list of fields in this order:
560              
561             workflow_id, type, state, last_update
562              
563             =head3 get_history_fields()
564              
565             Return list of fields in this order:
566              
567             workflow_hist_id, workflow_id, action, description,
568             state, workflow_user, history_date
569              
570             Note that we may cache the results, so don't try and do anything weird
571             like change the fieldnames based on the workflow user or something...
572              
573             =head1 METHODS
574              
575             =head2 Public Methods
576              
577             All public methods are inherited from L<Workflow::Persister>.
578              
579             =head2 Private Methods
580              
581             =head3 init( \%params )
582              
583             Initializes the the instance by setting the connection parameters
584             and calling C<create_handle>. You are only required to provide 'dsn',
585             which is the full DBI DSN you normally use as the first argument
586             to C<connect()>.
587              
588             You can set these parameters in your persister configuration file and
589             they will be passed to init.
590              
591             You may also use:
592              
593             =over 4
594              
595             =item B<user>
596              
597             Name of user to login with.
598              
599             =item B<password>
600              
601             Password for C<user> to login with.
602              
603             =item B<date_format>
604              
605             Date format to use when working with the database. Accepts a format string
606             that can be processed by the DateTime module. See
607             L<DateTime's strftime Patterns|https://metacpan.org/pod/DateTime#strftime-Patterns>
608             for the format options.
609              
610             The default is '%Y-%m-%d %H:%M' for backward compatibility.
611              
612             =item B<autocommit>
613              
614             0 or 1 to turn autocommit off or on for the database handle.
615              
616             Setting autocommit to off will run Workflow with transactions. If there is
617             a failure somewhere and the persister supports it, Workflow will attempt
618             to roll back all database activity in the current transaction.
619              
620             If you turn autocommit off, you must still
621             commit transactions for L<Workflow::Persister::DBI::ExtraData> yourself. Also,
622             if you are sharing the database handle, you must be careful to not pass control
623             to the workflow engine with pending transactions as they will be committed if
624             the workflow actions are successful.
625              
626             The default autocommit value for the database handle is on.
627              
628             =item B<workflow_table>
629              
630             Table to use for persisting workflow. Default is 'workflow'.
631              
632             =item B<history_table>
633              
634             Table to use for persisting workflow history. Default is
635             'workflow_history'.
636              
637             =back
638              
639             You may also use parameters for the different types of ID
640             generators. See below under the C<init_*_generator> for the necessary
641             parameters for your database.
642              
643             In addition to creating a database handle we parse the C<dsn> to see
644             what driver we are using to determine how to generate IDs. We have the
645             ability to use automatically generated IDs for PostgreSQL, MySQL, and
646             SQLite. If your database is not included a randomly generated ID will
647             be used. (Default length of 8 characters, which you can modify with a
648             C<id_length> parameter.)
649              
650             You can also create your own adapter for a different type of
651             database. Just check out the existing
652             L<Workflow::Persister::DBI::AutoGeneratedId> and
653             L<Workflow::Persister::DBI::SequenceId> classes for examples.
654              
655             =head3 assign_generators( $driver, \%params )
656              
657             Given C<$driver> and the persister parameters in C<\%params>, assign
658             the appropriate ID generators for both the workflow and history
659             tables.
660              
661             Returns: nothing, but assigns the object properties
662             C<workflow_id_generator> and C<history_id_generator>.
663              
664             =head3 assign_tables( \%params )
665              
666             Assign the table names from C<\%params> (using 'workflow_table' and
667             'history_table') or use the defaults 'workflow' and 'workflow_history'.
668              
669             Returns: nothing, but assigns the object properties C<workflow_table>
670             and C<history_table>.
671              
672             =head3 init_postgres_generators( \%params )
673              
674             Create ID generators for the workflow and history tables using
675             PostgreSQL sequences. You can specify the sequences used for the
676             workflow and history tables:
677              
678             =over 4
679              
680             =item B<workflow_sequence>
681              
682             Sequence for the workflow table. Default: 'workflow_seq'
683              
684             =item B<history_sequence>
685              
686             Sequence for the workflow history table. Default:
687             'workflow_history_seq'
688              
689             =back
690              
691             =head3 init_mysql_generators( \%params )
692              
693             Create ID generators for the workflow and history tables using
694             the MySQL 'auto_increment' type. No parameters are necessary.
695              
696             =head3 init_sqlite_generators( \%params )
697              
698             Create ID generators for the workflow and history tables using
699             the SQLite implicit increment. No parameters are necessary.
700              
701             =head3 init_random_generators( \%params )
702              
703             Create ID generators for the workflow and history tables using
704             a random set of characters. You can specify:
705              
706             =over 4
707              
708             =item B<id_length>
709              
710             Length of character sequence to generate. Default: 8.
711              
712             =back
713              
714             =head3 init_oracle_generators
715              
716             Create ID generators for the workflow and history tables using
717             the Oracle sequences. No parameters are necessary.
718              
719             =head3 create_handle
720              
721             Creates a database connection using DBI's C<connect> method and returns
722             the resulting database handle. Override this method if you want to set
723             different options than the hard-coded ones, or when you want to use a
724             handle from elsewhere.
725              
726             The default implementation hard-codes these database handle settings:
727              
728             $dbh->{RaiseError} = 1;
729             $dbh->{PrintError} = 0;
730             $dbh->{ChopBlanks} = 1;
731              
732             =head3 create_workflow
733              
734             Serializes a workflow into the persistance entity configured by our workflow.
735              
736             Takes a single parameter: a workflow object
737              
738             Returns a single value, a id for unique identification of out serialized
739             workflow for possible deserialization.
740              
741             =head3 fetch_workflow
742              
743             Deserializes a workflow from the persistance entity configured by our workflow.
744              
745             Takes a single parameter: the unique id assigned to our workflow upon
746             serialization (see L</create_workflow>).
747              
748             Returns a hashref consisting of two keys:
749              
750             =over
751              
752             =item * state, the workflows current state
753              
754             =item * last_update, date indicating last update
755              
756             =back
757              
758             =head3 update_workflow
759              
760             Updates a serialized workflow in the persistance entity configured by our
761             workflow.
762              
763             Takes a single parameter: a workflow object
764              
765             Returns: Nothing
766              
767             =head3 create_history
768              
769             Serializes history records associated with a workflow object
770              
771             Takes two parameters: a workflow object and an array of workflow history objects
772              
773             Returns: provided array of workflow history objects upon success
774              
775             =head3 fetch_history
776              
777             Deserializes history records associated with a workflow object
778              
779             Takes a single parameter: a workflow object
780              
781             Returns an array of workflow history objects upon success
782              
783             =head3 commit_transaction ( $wf )
784              
785             Commit the transaction for a workflow if autocommit is not enabled.
786              
787             Returns nothing
788              
789             =head3 rollback_transaction
790              
791             Rollsback the transaction for a workflow if autocommit is not enabled.
792              
793             Returns nothing
794              
795              
796             =head1 SEE ALSO
797              
798             =over
799              
800             =item L<Workflow>
801              
802             =item L<Workflow::Persister>
803              
804             =item L<DBI>
805              
806             =back
807              
808             =head1 COPYRIGHT
809              
810             Copyright (c) 2003-2021 Chris Winters. All rights reserved.
811              
812             This library is free software; you can redistribute it and/or modify
813             it under the same terms as Perl itself.
814              
815             Please see the F<LICENSE>
816              
817             =head1 AUTHORS
818              
819             Please see L<Workflow>
820              
821             =cut