File Coverage

blib/lib/SQL/Translator/Generator/DDL/SQLServer.pm
Criterion Covered Total %
statement 64 68 94.1
branch 12 18 66.6
condition 12 28 42.8
subroutine 24 26 92.3
pod 0 20 0.0
total 112 160 70.0


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