View | Details | Raw Unified | Return to bug 19871
Collapse All | Expand All

(-)a/Koha/Object.pm (-15 / +5 lines)
Lines 173-178 sub store { Link Here
173
    try {
173
    try {
174
        return $self->_result()->update_or_insert() ? $self : undef;
174
        return $self->_result()->update_or_insert() ? $self : undef;
175
    } catch {
175
    } catch {
176
176
        # Use centralized exception translation
177
        # Use centralized exception translation
177
        warn $_->{msg} if ref($_) eq 'DBIx::Class::Exception';
178
        warn $_->{msg} if ref($_) eq 'DBIx::Class::Exception';
178
179
Lines 192-198 sub store { Link Here
192
        }
193
        }
193
194
194
        # Delegate to centralized exception translation
195
        # Delegate to centralized exception translation
195
        $self->_result->result_source->schema->translate_exception($_, $columns_info);
196
        $self->_result->result_source->schema->translate_exception( $_, $columns_info );
196
    }
197
    }
197
}
198
}
198
199
Lines 238-257 sub delete { Link Here
238
    try {
239
    try {
239
        $deleted = $self->_result()->delete;
240
        $deleted = $self->_result()->delete;
240
    } catch {
241
    } catch {
241
        if ( ref($_) eq 'DBIx::Class::Exception' ) {
242
242
            if ( $_->{msg} =~
243
        # Delegate to centralized exception translation
243
                /Cannot delete or update a parent row\: a foreign key constraint fails \(\`(?<database>.*?)\`\.\`(?<table>.*?)\`, CONSTRAINT \`(?<constraint>.*?)\` FOREIGN KEY \(\`(?<fk>.*?)\`\) REFERENCES \`.*\` \(\`(?<column>.*?)\`\)/
244
        $self->_result->result_source->schema->translate_exception($_);
244
                )
245
            {
246
                Koha::Exceptions::Object::FKConstraintDeletion->throw(
247
                    column     => $+{column},
248
                    constraint => $+{constraint},
249
                    fk         => $+{fk},
250
                    table      => $+{table},
251
                );
252
            }
253
            $_->rethrow();
254
        }
255
    };
245
    };
256
246
257
    if ( ref $deleted ) {
247
    if ( ref $deleted ) {
(-)a/Koha/Schema/Util/ExceptionTranslator.pm (-6 / +25 lines)
Lines 89-94 sub translate_exception { Link Here
89
89
90
    # Foreign key constraint failures
90
    # Foreign key constraint failures
91
    if ( $msg =~ /Cannot add or update a child row: a foreign key constraint fails/ ) {
91
    if ( $msg =~ /Cannot add or update a child row: a foreign key constraint fails/ ) {
92
92
        # FIXME: MySQL error, if we support more DB engines we should implement this for each
93
        # FIXME: MySQL error, if we support more DB engines we should implement this for each
93
        if ( $msg =~ /FOREIGN KEY \(`(?<column>.*?)`\)/ ) {
94
        if ( $msg =~ /FOREIGN KEY \(`(?<column>.*?)`\)/ ) {
94
            Koha::Exceptions::Object::FKConstraint->throw(
95
            Koha::Exceptions::Object::FKConstraint->throw(
Lines 97-102 sub translate_exception { Link Here
97
            );
98
            );
98
        }
99
        }
99
    }
100
    }
101
102
    # Foreign key constraint deletion failures (parent row deletion blocked)
103
    elsif ( $msg =~
104
        /Cannot delete or update a parent row\: a foreign key constraint fails \(\`(?<database>.*?)\`\.\`(?<table>.*?)\`, CONSTRAINT \`(?<constraint>.*?)\` FOREIGN KEY \(\`(?<fk>.*?)\`\) REFERENCES \`.*\` \(\`(?<column>.*?)\`\)/
105
        )
106
    {
107
        Koha::Exceptions::Object::FKConstraintDeletion->throw(
108
            column     => $+{column},
109
            constraint => $+{constraint},
110
            fk         => $+{fk},
111
            table      => $+{table},
112
        );
113
    }
114
100
    # Duplicate key violations
115
    # Duplicate key violations
101
    elsif ( $msg =~ /Duplicate entry '(.*?)' for key '(?<key>.*?)'/ ) {
116
    elsif ( $msg =~ /Duplicate entry '(.*?)' for key '(?<key>.*?)'/ ) {
102
        Koha::Exceptions::Object::DuplicateID->throw(
117
        Koha::Exceptions::Object::DuplicateID->throw(
Lines 104-111 sub translate_exception { Link Here
104
            duplicate_id => $+{key}
119
            duplicate_id => $+{key}
105
        );
120
        );
106
    }
121
    }
122
107
    # Invalid data type values
123
    # Invalid data type values
108
    elsif ( $msg =~ /Incorrect (?<type>\w+) value: '(?<value>.*)' for column \W?(?<property>\S+)/ ) {
124
    elsif ( $msg =~ /Incorrect (?<type>\w+) value: '(?<value>.*)' for column \W?(?<property>\S+)/ ) {
125
109
        # The optional \W in the regex might be a quote or backtick
126
        # The optional \W in the regex might be a quote or backtick
110
        my $type     = $+{type};
127
        my $type     = $+{type};
111
        my $value    = $+{value};
128
        my $value    = $+{value};
Lines 116-127 sub translate_exception { Link Here
116
            type     => $type,
133
            type     => $type,
117
            value    => $value,
134
            value    => $value,
118
            property => $property =~ /(\w+\.\w+)$/
135
            property => $property =~ /(\w+\.\w+)$/
119
                ? $1
136
            ? $1
120
                : $property,    # results in table.column without quotes or backticks
137
            : $property,    # results in table.column without quotes or backticks
121
        );
138
        );
122
    }
139
    }
140
123
    # Data truncation for enum columns
141
    # Data truncation for enum columns
124
    elsif ( $msg =~ /Data truncated for column \W?(?<property>\w+)/ ) {
142
    elsif ( $msg =~ /Data truncated for column \W?(?<property>\w+)/ ) {
143
125
        # The optional \W in the regex might be a quote or backtick
144
        # The optional \W in the regex might be a quote or backtick
126
        my $property = $+{property};
145
        my $property = $+{property};
127
146
Lines 132-140 sub translate_exception { Link Here
132
                Koha::Exceptions::Object::BadValue->throw(
151
                Koha::Exceptions::Object::BadValue->throw(
133
                    type     => 'enum',
152
                    type     => 'enum',
134
                    property => $property =~ /(\w+\.\w+)$/
153
                    property => $property =~ /(\w+\.\w+)$/
135
                        ? $1
154
                    ? $1
136
                        : $property,    # results in table.column without quotes or backticks
155
                    : $property,                      # results in table.column without quotes or backticks
137
                    value => 'Invalid enum value',  # We don't have access to the object here
156
                    value => 'Invalid enum value',    # We don't have access to the object here
138
                );
157
                );
139
            }
158
            }
140
        }
159
        }
Lines 164-167 Koha Development Team Link Here
164
183
165
=cut
184
=cut
166
185
167
1;
186
1;
(-)a/t/db_dependent/Koha/Schema/Util/ExceptionTranslator.t (-38 / +58 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
use Test::NoWarnings;
21
use Test::NoWarnings;
22
use Test::More tests => 7;
22
use Test::More tests => 8;
23
use Test::Exception;
23
use Test::Exception;
24
24
25
use Koha::Database;
25
use Koha::Database;
Lines 36-48 subtest 'foreign_key_constraint_translation' => sub { Link Here
36
    $schema->storage->txn_begin;
36
    $schema->storage->txn_begin;
37
37
38
    # Create a mock DBIx::Class::Exception for FK constraint
38
    # Create a mock DBIx::Class::Exception for FK constraint
39
    my $exception = bless {
39
    my $exception =
40
        msg => "Cannot add or update a child row: a foreign key constraint fails (`koha`.`items`, CONSTRAINT `items_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`))"
40
        bless { msg =>
41
    }, 'DBIx::Class::Exception';
41
            "Cannot add or update a child row: a foreign key constraint fails (`koha`.`items`, CONSTRAINT `items_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`))"
42
        }, 'DBIx::Class::Exception';
42
43
43
    throws_ok {
44
    throws_ok {
44
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
45
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
45
    } 'Koha::Exceptions::Object::FKConstraint', 'FK constraint exception is properly translated';
46
    }
47
    'Koha::Exceptions::Object::FKConstraint', 'FK constraint exception is properly translated';
46
48
47
    $schema->storage->txn_rollback;
49
    $schema->storage->txn_rollback;
48
};
50
};
Lines 53-65 subtest 'duplicate_key_translation' => sub { Link Here
53
    $schema->storage->txn_begin;
55
    $schema->storage->txn_begin;
54
56
55
    # Create a mock DBIx::Class::Exception for duplicate key
57
    # Create a mock DBIx::Class::Exception for duplicate key
56
    my $exception = bless {
58
    my $exception = bless { msg => "Duplicate entry 'test\@example.com' for key 'borrowers.email'" },
57
        msg => "Duplicate entry 'test\@example.com' for key 'borrowers.email'"
59
        'DBIx::Class::Exception';
58
    }, 'DBIx::Class::Exception';
59
60
60
    throws_ok {
61
    throws_ok {
61
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
62
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
62
    } 'Koha::Exceptions::Object::DuplicateID', 'Duplicate key exception is properly translated';
63
    }
64
    'Koha::Exceptions::Object::DuplicateID', 'Duplicate key exception is properly translated';
63
65
64
    $schema->storage->txn_rollback;
66
    $schema->storage->txn_rollback;
65
};
67
};
Lines 70-82 subtest 'bad_value_translation' => sub { Link Here
70
    $schema->storage->txn_begin;
72
    $schema->storage->txn_begin;
71
73
72
    # Create a mock DBIx::Class::Exception for bad value
74
    # Create a mock DBIx::Class::Exception for bad value
73
    my $exception = bless {
75
    my $exception = bless { msg => "Incorrect datetime value: '2025-13-45' for column 'date_due' at row 1" },
74
        msg => "Incorrect datetime value: '2025-13-45' for column 'date_due' at row 1"
76
        'DBIx::Class::Exception';
75
    }, 'DBIx::Class::Exception';
76
77
77
    throws_ok {
78
    throws_ok {
78
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
79
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
79
    } 'Koha::Exceptions::Object::BadValue', 'Bad value exception is properly translated';
80
    }
81
    'Koha::Exceptions::Object::BadValue', 'Bad value exception is properly translated';
80
82
81
    $schema->storage->txn_rollback;
83
    $schema->storage->txn_rollback;
82
};
84
};
Lines 87-103 subtest 'enum_truncation_translation' => sub { Link Here
87
    $schema->storage->txn_begin;
89
    $schema->storage->txn_begin;
88
90
89
    # Create a mock DBIx::Class::Exception for enum truncation
91
    # Create a mock DBIx::Class::Exception for enum truncation
90
    my $exception = bless {
92
    my $exception = bless { msg => "Data truncated for column 'status' at row 1" }, 'DBIx::Class::Exception';
91
        msg => "Data truncated for column 'status' at row 1"
92
    }, 'DBIx::Class::Exception';
93
93
94
    my $columns_info = {
94
    my $columns_info = { status => { data_type => 'enum' } };
95
        status => { data_type => 'enum' }
96
    };
97
95
98
    throws_ok {
96
    throws_ok {
99
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception, $columns_info);
97
        Koha::Schema::Util::ExceptionTranslator->translate_exception( $exception, $columns_info );
100
    } 'Koha::Exceptions::Object::BadValue', 'Enum truncation exception is properly translated';
98
    }
99
    'Koha::Exceptions::Object::BadValue', 'Enum truncation exception is properly translated';
101
100
102
    $schema->storage->txn_rollback;
101
    $schema->storage->txn_rollback;
103
};
102
};
Lines 108-127 subtest 'non_dbix_exception_passthrough' => sub { Link Here
108
    $schema->storage->txn_begin;
107
    $schema->storage->txn_begin;
109
108
110
    # Create a regular exception (not DBIx::Class::Exception)
109
    # Create a regular exception (not DBIx::Class::Exception)
111
    my $exception = bless {
110
    my $exception = bless { msg => "Some other error" }, 'Some::Other::Exception';
112
        msg => "Some other error"
113
    }, 'Some::Other::Exception';
114
111
115
    # Mock the rethrow method
112
    # Mock the rethrow method
116
    $exception->{rethrown} = 0;
113
    $exception->{rethrown} = 0;
117
    {
114
    {
115
118
        package Some::Other::Exception;
116
        package Some::Other::Exception;
119
        sub rethrow { $_[0]->{rethrown} = 1; die $_[0]; }
117
        sub rethrow { $_[0]->{rethrown} = 1; die $_[0]; }
120
    }
118
    }
121
119
122
    throws_ok {
120
    throws_ok {
123
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
121
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
124
    } qr/Some::Other::Exception/, 'Non-DBIx::Class exceptions are rethrown unchanged';
122
    }
123
    qr/Some::Other::Exception/, 'Non-DBIx::Class exceptions are rethrown unchanged';
124
125
    $schema->storage->txn_rollback;
126
};
127
128
subtest 'fk_constraint_deletion_translation' => sub {
129
    plan tests => 1;
130
131
    $schema->storage->txn_begin;
132
133
    # Create a mock DBIx::Class::Exception for FK constraint deletion
134
    my $exception =
135
        bless { msg =>
136
            "Cannot delete or update a parent row: a foreign key constraint fails (`koha`.`items`, CONSTRAINT `items_ibfk_1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`))"
137
        }, 'DBIx::Class::Exception';
138
139
    throws_ok {
140
        Koha::Schema::Util::ExceptionTranslator->translate_exception($exception);
141
    }
142
    'Koha::Exceptions::Object::FKConstraintDeletion', 'FK constraint deletion exception is properly translated';
125
143
126
    $schema->storage->txn_rollback;
144
    $schema->storage->txn_rollback;
127
};
145
};
Lines 132-154 subtest 'schema_safe_do_method' => sub { Link Here
132
    $schema->storage->txn_begin;
150
    $schema->storage->txn_begin;
133
151
134
    # Test successful operation
152
    # Test successful operation
135
    my $result = $schema->safe_do(sub {
153
    my $result = $schema->safe_do(
136
        return "success";
154
        sub {
137
    });
155
            return "success";
156
        }
157
    );
138
    is( $result, "success", 'safe_do returns result on success' );
158
    is( $result, "success", 'safe_do returns result on success' );
139
159
140
    # Test exception translation
160
    # Test exception translation
141
    throws_ok {
161
    throws_ok {
142
        $schema->safe_do(sub {
162
        $schema->safe_do(
143
            # Create a mock DBIx::Class::Exception
163
            sub {
144
            my $exception = bless {
164
                # Create a mock DBIx::Class::Exception
145
                msg => "Duplicate entry 'test' for key 'primary'"
165
                my $exception = bless { msg => "Duplicate entry 'test' for key 'primary'" }, 'DBIx::Class::Exception';
146
            }, 'DBIx::Class::Exception';
166
                die $exception;
147
            die $exception;
167
            }
148
        });
168
        );
149
    } 'Koha::Exceptions::Object::DuplicateID', 'safe_do translates exceptions properly';
169
    }
170
    'Koha::Exceptions::Object::DuplicateID', 'safe_do translates exceptions properly';
150
171
151
    $schema->storage->txn_rollback;
172
    $schema->storage->txn_rollback;
152
};
173
};
153
174
154
1;
175
1;
155
- 

Return to bug 19871