File Coverage

blib/lib/DBIx/Schema/DSL.pm
Criterion Covered Total %
statement 187 197 94.9
branch 49 60 81.6
condition 20 38 52.6
subroutine 39 42 92.8
pod 23 23 100.0
total 318 360 88.3


line stmt bran cond sub pod time code
1             package DBIx::Schema::DSL;
2 8     8   408462 use 5.008_005;
  8         88  
3 8     8   36 use strict;
  8         12  
  8         146  
4 8     8   28 use warnings;
  8         15  
  8         267  
5              
6             our $VERSION = '1.0000';
7              
8 8     8   48 use Carp qw/croak/;
  8         23  
  8         323  
9 8     8   2741 use Array::Diff;
  8         98654  
  8         54  
10 8     8   2984 use DBIx::Schema::DSL::Context;
  8         22  
  8         262  
11 8     8   55 use SQL::Translator::Schema::Constants;
  8         13  
  8         514  
12 8     8   43 use SQL::Translator::Schema::Field;
  8         12  
  8         310  
13              
14             sub context {
15 105     105 1 2185 my $pkg = shift;
16 105 50       173 die 'something went wrong when calling context method.' if $pkg eq __PACKAGE__;
17 8     8   37 no strict 'refs';
  8         15  
  8         485  
18 105   66     114 ${"$pkg\::CONTEXT"} ||= DBIx::Schema::DSL::Context->new;
  105         579  
19             }
20              
21             # don't override CORE::int
22 8     8   4722 use Pod::Functions ();
  8         23588  
  8         947  
23             my @column_methods =
24             grep {!$Pod::Functions::Type{$_}}
25             grep { /^[a-zA-Z_][0-9a-zA-Z_]*$/ }
26             keys(%SQL::Translator::Schema::Field::type_mapping), qw/string number enum set/;
27             my @column_sugars = qw/unique auto_increment unsigned null/;
28             my @rev_column_sugars = qw/not_null signed/;
29             my @export_dsls = qw/
30             create_database database create_table column primary_key set_primary_key add_index add_unique_index
31             foreign_key has_many has_one belongs_to add_table_options default_unsigned columns pk fk
32             default_not_null
33             /;
34             my @class_methods = qw/context output no_fk_output translate_to translator/;
35             sub import {
36 13     13   112 my $caller = caller;
37              
38 8     8   51 no strict 'refs';
  8         59  
  8         8781  
39 13         29 for my $func (@export_dsls, @column_methods, @column_sugars, @class_methods, @rev_column_sugars) {
40 702         925 *{"$caller\::$func"} = \&$func;
  702         13726  
41             }
42             }
43              
44 3     3 1 38 sub create_database($) { caller->context->name(shift) }
45 3     3 1 141 sub database($) { caller->context->db(shift) }
46              
47             sub add_table_options {
48 3     3 1 18 my $c = caller->context;
49 3         16 my %opt = @_;
50              
51             $c->set_table_extra({
52 3         23 %{$c->table_extra},
  3         54  
53             %opt,
54             });
55              
56 3 50 33     20 if ($opt{mysql_charset} && $opt{mysql_charset} eq 'utf8mb4') {
57 3         12 $c->default_varchar_size(191);
58             }
59             }
60              
61             sub default_unsigned() {
62 2     2 1 11 caller->context->default_unsigned(1);
63             }
64              
65             sub default_not_null() {
66 1     1 1 5 caller->context->default_not_null(1);
67             }
68              
69             sub create_table($$) {
70 17     17 1 43 my ($table_name, $code) = @_;
71              
72 17         29 my $kls = caller;
73 17         67 my $c = $kls->context;
74              
75 17         148 $c->_creating_table({
76             table_name => $table_name,
77             columns => [],
78             indices => [],
79             constraints => [],
80             primary_key => undef,
81             });
82              
83 17         48 $code->();
84              
85 15         28 my $data = $c->_creating_table;
86             my $table = $c->schema->add_table(
87             name => $table_name,
88 15         274 extra => {%{$c->table_extra}},
  15         653  
89             );
90 15         8005 for my $column (@{ $data->{columns} }) {
  15         51  
91 52         32829 $table->add_field(%{ $column } );
  52         156  
92             }
93              
94 15         14968 my @columns = map {$_->{name}} @{$data->{columns}};
  52         88  
  15         61  
95 15         23 for my $index (@{ $data->{indices} }) {
  15         47  
96 3 100       13 if (my @undefined_columns = _detect_undefined_columns(\@columns, $index->{fields})) {
97 1         13 croak "Index error: Key column [@{[join ', ', @undefined_columns]}] doesn't exist in table]";
  1         165  
98             }
99 2         17 $table->add_index(%{ $index } );
  2         10  
100             }
101 14         878 for my $constraint (@{ $data->{constraints} }) {
  14         30  
102 8         15 my $cols = $constraint->{fields};
103 8 100       25 $cols = [$cols] unless ref $cols;
104 8 50       32 if (my @undefined_columns = _detect_undefined_columns(\@columns, $cols)) {
105 0         0 croak "Constraint error: Key column [@{[join ', ', @undefined_columns]}] doesn't exist in table]";
  0         0  
106             }
107 8         69 $table->add_constraint(%{ $constraint } );
  8         36  
108             }
109              
110 14 100       5101 if (my $pk = $data->{primary_key}) {
111 12 100       36 $pk = [$pk] unless ref $pk;
112 12 100       44 if (my @undefined_columns = _detect_undefined_columns(\@columns, $pk)) {
113 1         14 croak "Primary key error: Key column [@{[join ', ', @undefined_columns]}] doesn't exist in table]";
  1         83  
114             }
115 11         93 $table->primary_key($data->{primary_key});
116             }
117              
118 13         17776 $c->_clear_creating_table;
119 13         134 1;
120             }
121 17     17 1 2448 sub columns(&) {shift}
122              
123             sub _detect_undefined_columns {
124 23     23   37 my ($set, $subset) = @_;
125              
126 23         144 my $diff = Array::Diff->diff([sort @$set], [sort @$subset]);
127 23         7658 @{$diff->added};
  23         396  
128             }
129              
130             sub column($$;%) {
131 56     56 1 108 my ($column_name, $data_type, @opt) = @_;
132 56 100       233 croak '`column` function called in non void context' if defined wantarray;
133              
134 55 100       93 if (ref $opt[0] eq 'ARRAY') {
135             # enum or set
136 1         1 unshift @opt, 'list';
137             }
138              
139 55 100       103 if (@opt % 2) {
140 1         2 croak "odd number elements are assined to options. arguments: [@{[join ', ', @_]}]";
  1         76  
141             }
142 54         92 my %opt = @opt;
143 54 50       86 $data_type = 'varchar' if $data_type eq 'string';
144              
145 54         122 my $c = caller->context;
146              
147 54 50       131 my $creating_data = $c->_creating_table
148             or croak q{can't call `column` method outside `create_table` method};
149              
150 54         135 my %args = (
151             name => $column_name,
152             data_type => uc $data_type,
153             );
154              
155 54         176 my %map = (
156             null => 'is_nullable',
157             limit => 'size',
158             default => 'default_value',
159             unique => 'is_unique',
160             primary_key => 'is_primary_key',
161             auto_increment => 'is_auto_increment',
162             );
163 54         125 for my $key (keys %map) {
164 324 100       450 $args{$map{$key}} = delete $opt{$key} if exists $opt{$key};
165             }
166             %args = (
167 54         153 %args,
168             %opt
169             );
170              
171 54 100 100     211 if (exists $args{unsigned}) {
    100          
172 3         6 $args{extra}{unsigned} = delete $args{unsigned};
173             }
174             elsif ($c->default_unsigned && $data_type =~ /int(?:eger)?$/) {
175 9         21 $args{extra}{unsigned} = 1;
176             }
177              
178 54 100       88 if (exists $args{on_update}) {
179 1         2 $args{extra}{'on update'} = delete $args{on_update};
180             }
181              
182 54 100       82 if (exists $args{list}) {
183 1         2 $args{extra}{list} = delete $args{list};
184             }
185              
186              
187 54 100 100     150 if ( !exists $args{is_nullable} && $c->default_not_null ) {
188 2         4 $args{is_nullable} = 0;
189             }
190              
191 54 100 66     122 if ($args{data_type} eq 'VARCHAR' && !$args{size}) {
192 15         41 $args{size} = $c->default_varchar_size;
193             }
194              
195 54 100       82 if ($args{precision}) {
196 2         4 my $precision = delete $args{precision};
197 2   50     6 my $scale = delete $args{scale} || 0;
198 2         4 $args{size} = [$precision, $scale];
199             }
200              
201 54 100       92 if ($args{is_primary_key}) {
    100          
202 12         23 $creating_data->{primary_key} = $column_name;
203             }
204             elsif ($args{is_unique}) {
205 5         7 push @{$creating_data->{constraints}}, {
  5         31  
206             name => "${column_name}_uniq",
207             fields => [$column_name],
208             type => UNIQUE,
209             };
210             }
211              
212             # explicitly add `DEFAULT NULL` if is_nullable and not specified default_value
213 54 50 66     158 if ($args{is_nullable} && !exists $args{default_value} && $args{data_type} !~ /^(?:TINY|MEDIUM|LONG)?(?:TEXT|BLOB)$/ ) {
      66        
214 10         15 $args{default_value} = \'NULL';
215             }
216              
217 54         55 push @{$creating_data->{columns}}, \%args;
  54         177  
218             }
219              
220             sub primary_key {
221 9 100   9 1 36 if (defined wantarray) {
222 7         25 (primary_key => 1);
223             }
224             else { # void context
225 2         4 my $column_name = shift;
226              
227 2         9 @_ = ($column_name, 'integer', primary_key(), auto_increment(), @_);
228 2         8 goto \&column;
229             }
230             }
231             *pk = \&primary_key;
232              
233             for my $method (@column_methods) {
234 8     8   53 no strict 'refs';
  8         18  
  8         342  
235             *{__PACKAGE__."::$method"} = sub {
236 8     8   44 use strict 'refs';
  8         9  
  8         530  
237 51     51   107 my $column_name = shift;
238              
239 51         110 @_ = ($column_name, $method, @_);
240 51         118 goto \&column;
241             };
242             }
243              
244             for my $method (@column_sugars) {
245 8     8   42 no strict 'refs';
  8         10  
  8         330  
246             *{__PACKAGE__."::$method"} = sub() {
247 8     8   38 use strict 'refs';
  8         11  
  8         5117  
248 20     20   79 ($method => 1);
249             };
250             }
251 0     0 1 0 sub not_null() { (null => 0) }
252 0     0 1 0 sub signed() { (unsigned => 0) }
253              
254             sub set_primary_key(@) {
255 3     3 1 9 my @keys = @_;
256              
257 3         8 my $c = caller->context;
258              
259 3 50       32 my $creating_data = $c->_creating_table
260             or die q{can't call `set_primary_key` method outside `create_table` method};
261              
262 3         10 $creating_data->{primary_key} = \@keys;
263             }
264              
265             sub add_index {
266 2     2 1 6 my $c = caller->context;
267              
268 2 50       8 my $creating_data = $c->_creating_table
269             or die q{can't call `add_index` method outside `create_table` method};
270              
271 2         6 my ($idx_name, $fields, $type) = @_;
272              
273 2 50       4 push @{$creating_data->{indices}}, {
  2         10  
274             name => $idx_name,
275             fields => $fields,
276             ($type ? (type => $type) : ()),
277             };
278             }
279              
280             sub add_unique_index {
281 1     1 1 3 my $c = caller->context;
282              
283 1 50       3 my $creating_data = $c->_creating_table
284             or die q{can't call `add_unique_index` method outside `create_table` method};
285              
286 1         3 my ($idx_name, $fields) = @_;
287              
288 1         1 push @{$creating_data->{indices}}, {
  1         5  
289             name => $idx_name,
290             fields => $fields,
291             type => UNIQUE,
292             };
293             }
294              
295             sub foreign_key {
296 4     4 1 11 my $c = caller->context;
297              
298 4 50       19 my $creating_data = $c->_creating_table
299             or die q{can't call `foreign` method outside `create_table` method};
300              
301 4         11 my ($columns, $table, $foreign_columns, %opt) = @_;
302              
303 4         7 push @{$creating_data->{constraints}}, {
  4         21  
304             type => FOREIGN_KEY,
305             fields => $columns,
306             reference_table => $table,
307             reference_fields => $foreign_columns,
308             %opt,
309             };
310             }
311             *fk = \&foreign_key;
312              
313             sub has_many {
314 2     2 1 13 my $c = caller->context;
315              
316 2         6 my ($table, %opt) = @_;
317              
318 2   50     9 my $columns = delete $opt{column} || 'id';
319 2   33     15 my $foreign_columns = delete $opt{foreign_column} || $c->_creating_table_name .'_id';
320              
321 2         6 @_ = ($columns, $table, $foreign_columns, %opt);
322 2         6 goto \&foreign_key;
323             }
324              
325             sub has_one {
326 0     0 1 0 my $c = caller->context;
327              
328 0         0 my ($table, %opt) = @_;
329              
330 0   0     0 my $columns = delete $opt{column} || 'id';
331 0   0     0 my $foreign_columns = delete $opt{foreign_column} || $c->_creating_table_name .'_id';
332              
333 0         0 @_ = ($columns, $table, $foreign_columns, %opt);
334 0         0 goto \&foreign_key;
335             }
336              
337             sub belongs_to {
338 2     2 1 5 my ($table, %opt) = @_;
339              
340 2   33     14 my $columns = delete $opt{column} || "${table}_id";
341 2   50     8 my $foreign_columns = delete $opt{foreign_column} || 'id';
342              
343 2         7 @_ = ($columns, $table, $foreign_columns, %opt);
344 2         8 goto \&foreign_key;
345             }
346              
347             sub output {
348 5     5 1 13972 shift->context->translate;
349             }
350              
351             sub no_fk_output {
352 1     1 1 739 shift->context->no_fk_translate;
353             }
354              
355             sub translator {
356 1     1 1 4 shift->context->translator;
357             }
358              
359             sub translate_to {
360 1     1 1 4 my ($kls, $db_type) = @_;
361              
362 1         3 $kls->translator->translate(to => $db_type);
363             }
364              
365             1;
366             __END__