File Coverage

blib/lib/SQL/Translator/Generator/DDL/SQLServer.pm
Criterion Covered Total %
statement 65 68 95.5
branch 13 18 72.2
condition 12 28 42.8
subroutine 24 26 92.3
pod 0 20 0.0
total 114 160 71.2


line stmt bran cond sub pod time code
1             package SQL::Translator::Generator::DDL::SQLServer;
2              
3             =head1 NAME
4              
5             SQL::Translator::Generator::DDL::SQLServer - A Moo based MS SQL Server DDL
6             generation engine.
7              
8             =head1 DESCRIPTION
9              
10             I
11              
12             =cut
13              
14 3     3   135416 use Moo;
  3         18647  
  3         22  
15 3     3   4828 use SQL::Translator::Schema::Constants;
  3         8  
  3         6637  
16              
17             with 'SQL::Translator::Generator::Role::Quote';
18             with 'SQL::Translator::Generator::Role::DDL';
19              
20 120     120 0 509 sub quote_chars { [qw([ ])] }
21 116     116 0 404 sub name_sep {q(.)}
22              
23             sub _build_numeric_types {
24 2     2   41 +{ int => 1, };
25             }
26              
27             sub _build_unquoted_defaults {
28 0     0   0 +{ NULL => 1, };
29             }
30              
31             sub _build_type_map {
32             +{
33 4     4   126 date => 'datetime',
34             'time' => 'datetime',
35             };
36             }
37              
38             sub _build_sizeless_types {
39 3     3   173 +{ map { $_ => 1 } qw( tinyint smallint int integer bigint text bit image datetime ) };
  27         177  
40             }
41              
42             sub field {
43 35     35 0 166 my ($self, $field) = @_;
44              
45 35   50     131 return join ' ', $self->field_name($field),
46             ($self->field_type($field) || die 'type is required'),
47             $self->field_autoinc($field),
48             $self->field_nullable($field),
49             $self->field_default($field),;
50             }
51              
52 35 100   35 0 1416 sub field_autoinc { ($_[1]->is_auto_increment ? 'IDENTITY' : ()) }
53              
54             sub primary_key_constraint {
55 7   66 7 0 359 'CONSTRAINT '
56             . $_[0]->quote($_[1]->name || $_[1]->table->name . '_pk')
57             . ' PRIMARY KEY ('
58             . join(', ', map $_[0]->quote($_), $_[1]->fields) . ')';
59             }
60              
61             sub index {
62 4   33 4 0 129 'CREATE INDEX '
63             . $_[0]->quote($_[1]->name || $_[1]->table->name . '_idx') . ' ON '
64             . $_[0]->quote($_[1]->table->name) . ' ('
65             . join(', ', map $_[0]->quote($_), $_[1]->fields) . ');';
66             }
67              
68             sub unique_constraint_single {
69 0     0 0 0 my ($self, $constraint) = @_;
70              
71 0         0 'CONSTRAINT '
72             . $self->unique_constraint_name($constraint)
73             . ' UNIQUE ('
74             . join(', ', map $self->quote($_), $constraint->fields) . ')';
75             }
76              
77             sub unique_constraint_name {
78 2     2 0 8 my ($self, $constraint) = @_;
79 2   33     63 $self->quote($constraint->name || $constraint->table->name . '_uc');
80             }
81              
82             sub unique_constraint_multiple {
83 2     2 0 73 my ($self, $constraint) = @_;
84              
85             'CREATE UNIQUE NONCLUSTERED INDEX '
86             . $self->unique_constraint_name($constraint) . ' ON '
87             . $self->quote($constraint->table->name) . ' ('
88             . join(', ', map $self->quote($_), $constraint->fields) . ')'
89             . ' WHERE '
90 2         12 . join(' AND ', map $self->quote($_->name) . ' IS NOT NULL', grep { $_->is_nullable } $constraint->fields) . ';';
  2         48  
91             }
92              
93             sub foreign_key_constraint {
94 4     4 0 82 my ($self, $constraint) = @_;
95              
96 4   50     162 my $on_delete = uc($constraint->on_delete || '');
97 4   50     103 my $on_update = uc($constraint->on_update || '');
98              
99             # The default implicit constraint action in MSSQL is RESTRICT
100             # but you can not specify it explicitly. Go figure :)
101 4   50     44 for (map uc $_ || '', $on_delete, $on_update) {
102 8 50       31 undef $_ if $_ eq 'RESTRICT';
103             }
104              
105             'ALTER TABLE '
106 4 50 33     170 . $self->quote($constraint->table->name)
    50 33        
      33        
107             . ' ADD CONSTRAINT '
108             . $self->quote($constraint->name || $constraint->table->name . '_fk')
109             . ' FOREIGN KEY' . ' ('
110             . join(', ', map $self->quote($_), $constraint->fields)
111             . ') REFERENCES '
112             . $self->quote($constraint->reference_table) . ' ('
113             . join(', ', map $self->quote($_), $constraint->reference_fields) . ')'
114             . (
115             $on_delete && $on_delete ne "NO ACTION" ? ' ON DELETE ' . $on_delete
116             : ''
117             )
118             . (
119             $on_update && $on_update ne "NO ACTION" ? ' ON UPDATE ' . $on_update
120             : ''
121             ) . ';';
122             }
123              
124             sub enum_constraint_name {
125 1     1 0 4 my ($self, $field_name) = @_;
126 1         7 $self->quote($field_name . '_chk');
127             }
128              
129             sub enum_constraint {
130 1     1 0 4 my ($self, $field_name, $vals) = @_;
131              
132 1         4 return ('CONSTRAINT '
133             . $self->enum_constraint_name($field_name)
134             . ' CHECK ('
135             . $self->quote($field_name) . ' IN ('
136             . join(',', map $self->quote_string($_), @$vals)
137             . '))');
138             }
139              
140             sub constraints {
141 10     10 0 30 my ($self, $table) = @_;
142              
143             (
144             map $self->enum_constraint($_->name, { $_->extra }->{list} || []),
145 33         197 grep { 'enum' eq lc $_->data_type } $table->get_fields
146             ),
147              
148 13         522 (map $self->primary_key_constraint($_), grep { $_->type eq PRIMARY_KEY } $table->get_constraints),
149              
150             (
151             map $self->unique_constraint_single($_),
152             grep {
153 10   50     72 $_->type eq UNIQUE
154 13 100       493 && !grep { $_->is_nullable }
  2         45  
155             $_->fields
156             } $table->get_constraints
157             ),
158             ;
159             }
160              
161             sub table {
162 10     10 0 131 my ($self, $table) = @_;
163             join("\n", $self->table_comments($table), '')
164             . join("\n\n",
165             'CREATE TABLE '
166             . $self->quote($table->name) . " (\n"
167 10         52 . join(",\n", map {" $_"} $self->fields($table), $self->constraints($table),) . "\n);",
  41         366  
168             $self->unique_constraints_multiple($table), $self->indices($table),);
169             }
170              
171             sub unique_constraints_multiple {
172 10     10 0 58 my ($self, $table) = @_;
173             (
174             map $self->unique_constraint_multiple($_),
175             grep {
176 10         53 $_->type eq UNIQUE
177 13 100       497 && grep { $_->is_nullable }
  2         47  
178             $_->fields
179             } $table->get_constraints
180             );
181             }
182              
183             sub drop_table {
184 8     8 0 29 my ($self, $table) = @_;
185 8         274 my $name = $table->name;
186 8         264 my $q_name = $self->quote($name);
187 8         134 "IF EXISTS (SELECT name FROM sysobjects WHERE name = '$name' AND type = 'U')" . " DROP TABLE $q_name;";
188             }
189              
190             sub remove_table_constraints {
191 8     8 0 26 my ($self, $table) = @_;
192 8         293 my $name = $table->name;
193 8         319 my $q_name = $self->quote($name);
194 8         89 "IF EXISTS (SELECT name FROM sysobjects WHERE name = '$name' AND type = 'U')"
195             . " ALTER TABLE $q_name NOCHECK CONSTRAINT all;";
196             }
197              
198             sub drop_tables {
199 3     3 0 11 my ($self, $schema) = @_;
200              
201 3 100       23 if ($self->add_drop_table) {
202 2         16 my @tables = sort { $b->order <=> $a->order } $schema->get_tables;
  8         200  
203 2 50       29 return join "\n",
    50          
204             (
205             (
206             $self->add_comments
207             ? ('--', '-- Turn off constraints', '--', '',)
208             : ()
209             ),
210             (map $self->remove_table_constraints($_), @tables),
211             ($self->add_comments ? ('--', '-- Drop tables', '--', '',) : ()),
212             (map $self->drop_table($_), @tables),
213             );
214             }
215 1         10 return '';
216             }
217              
218             sub foreign_key_constraints {
219 3     3 0 11 my ($self, $schema) = @_;
220             (
221 3         28 map $self->foreign_key_constraint($_), grep { $_->type eq FOREIGN_KEY }
  13         481  
222             map $_->get_constraints, $schema->get_tables
223             );
224             }
225              
226             sub schema {
227 3     3 0 53 my ($self, $schema) = @_;
228              
229             $self->header_comments
230             . $self->drop_tables($schema)
231 3         26 . join("\n\n", map $self->table($_), grep { $_->name } $schema->get_tables) . "\n"
  9         433  
232             . join "\n", $self->foreign_key_constraints($schema);
233             }
234              
235             1;
236              
237             =head1 AUTHORS
238              
239             See the included AUTHORS file:
240             L
241              
242             =head1 COPYRIGHT
243              
244             Copyright (c) 2012 the SQL::Translator L as listed above.
245              
246             =head1 LICENSE
247              
248             This code is free software and may be distributed under the same terms as Perl
249             itself.
250              
251             =cut