added two tables, so up the number expected
[dbsrgits/SQL-Translator-2.0-ish.git] / t / 02mysql-parser.t
1 use strict;
2 use warnings;
3 use Test::More;
4 use SQL::Translator;
5 use SQL::Translator::Constants qw(:sqlt_types :sqlt_constants);
6
7 {
8     my $tr = SQL::Translator->new({ from => 'MySQL' });
9     my $data = q|create table "sessions" (
10         id char(32) not null default '0' primary key,
11         a_session text,
12         ssn varchar(12) unique key,
13         age int key,
14         fulltext key `session_fulltext` (a_session)
15     );|;
16
17     my $val = $tr->parse($data);
18     my $schema = $tr->schema;
19     is( $schema->is_valid, 1, 'Schema is valid' );
20     my @tables = $schema->get_tables;
21     is( scalar @tables, 1, 'Right number of tables (1)' );
22     my $table = shift @tables;
23     is( $table->name, 'sessions', 'Found "sessions" table' );
24
25     my @fields = $table->get_fields;
26     is( scalar @fields, 4, 'Right number of fields (4)' );
27
28     my $f1 = shift @fields;
29     my $f2 = shift @fields;
30     is( $f1->name, 'id', 'First field name is "id"' );
31     is( $f1->data_type, 'char', 'Type is "char"' );
32     is( $f1->size, 32, 'Size is "32"' );
33     is( $f1->is_nullable, 0, 'Field cannot be null' );
34     is( $f1->default_value, '0', 'Default value is "0"' );
35     is( $f1->is_primary_key, 1, 'Field is PK' );
36
37     is( $f2->name, 'a_session', 'Second field name is "a_session"' );
38     is( $f2->data_type, 'text', 'Type is "text"' );
39     is( $f2->size, 0, 'Size is "0"' );
40     is( $f2->is_nullable, 1, 'Field can be null' );
41     is( $f2->default_value, undef, 'Default value is undefined' );
42     is( $f2->is_primary_key, 0, 'Field is not PK' );
43
44     my @indices = $table->get_indices;
45     is( scalar @indices, 2, 'Right number of indices (2)' );
46
47     my $i = pop @indices;
48     is( $i->type, 'FULLTEXT', 'Found fulltext' );
49
50     my @constraints = $table->get_constraints;
51     is( scalar @constraints, 2, 'Right number of constraints (2)' );
52     my $c1 = shift @constraints;
53     is( $c1->type, PRIMARY_KEY, 'Constraint is a PK' );
54     is( join(',', $c1->fields), 'id', 'Constraint is on "id"' );
55     my $c2 = shift @constraints;
56     is( $c2->type, UNIQUE, 'Constraint is UNIQUE' );
57     is( join(',', $c2->fields), 'ssn', 'Constraint is on "ssn"' );
58 }
59
60 {
61     my $tr = SQL::Translator->new({ from => 'MySQL' });
62     my $val = $tr->parse(
63         q[
64             CREATE TABLE `check` (
65               check_id int(7) unsigned zerofill NOT NULL default '0000000' 
66                 auto_increment primary key,
67               successful date NOT NULL default '0000-00-00',
68               unsuccessful date default '0000-00-00',
69               i1 int(11) default '0' not null,
70               s1 set('a','b','c') default 'b',
71               e1 enum('a','b','c') default 'c',
72               name varchar(30) default NULL,
73               foo_type enum('vk','ck') NOT NULL default 'vk',
74               date timestamp,
75               time_stamp2 timestamp,
76               KEY (i1),
77               UNIQUE (date, i1),
78               KEY date_idx (date),
79               KEY name_idx (name)
80             ) TYPE=MyISAM PACK_KEYS=1;
81         ]
82     ); ## KEY name_idx (name(10))
83     
84     my $schema = $tr->schema;
85     is( $schema->is_valid, 1, 'Schema is valid' );
86     my @tables = $schema->get_tables;
87     is( scalar @tables, 1, 'Right number of tables (1)' );
88     my $table  = shift @tables;
89     is( $table->name, 'check', 'Found "check" table' );
90
91     my @fields = $table->get_fields;
92     is( scalar @fields, 10, 'Right number of fields (10)' );
93     my $f1 = shift @fields;
94     is( $f1->name, 'check_id', 'First field name is "check_id"' );
95     is( $f1->data_type, 'int', 'Type is "int"' );
96     is( $f1->size, 7, 'Size is "7"' );
97     is( $f1->is_nullable, 0, 'Field cannot be null' );
98     is( $f1->default_value, '0000000', 'Default value is "0000000"' );
99     is( $f1->is_primary_key, 1, 'Field is PK' );
100     is( $f1->is_auto_increment, 1, 'Field is auto inc' );
101     my %extra = $f1->extra;
102     ok( defined $extra{'unsigned'}, 'Field is unsigned' );
103     ok( defined $extra{'zerofill'}, 'Field is zerofill' );
104
105     my $f2 = shift @fields;
106     is( $f2->name, 'successful', 'Second field name is "successful"' );
107     is( $f2->data_type, 'date', 'Type is "date"' );
108     is( $f2->size, 0, 'Size is "0"' );
109     is( $f2->is_nullable, 0, 'Field cannot be null' );
110     is( $f2->default_value, '0000-00-00', 'Default value is "0000-00-00"' );
111     is( $f2->is_primary_key, 0, 'Field is not PK' );
112
113     my $f3 = shift @fields;
114     is( $f3->name, 'unsuccessful', 'Third field name is "unsuccessful"' );
115     is( $f3->data_type, 'date', 'Type is "date"' );
116     is( $f3->size, 0, 'Size is "0"' );
117     is( $f3->is_nullable, 1, 'Field can be null' );
118     is( $f3->default_value, '0000-00-00', 'Default value is "0000-00-00"' );
119     is( $f3->is_primary_key, 0, 'Field is not PK' );
120
121     my $f4 = shift @fields;
122     is( $f4->name, 'i1', 'Fourth field name is "i1"' );
123     is( $f4->data_type, 'int', 'Type is "int"' );
124     is( $f4->size, 11, 'Size is "11"' );
125     is( $f4->is_nullable, 0, 'Field cannot be null' );
126     is( $f4->default_value, '0', 'Default value is "0"' );
127     is( $f4->is_primary_key, 0, 'Field is not PK' );
128
129     my $f5 = shift @fields;
130     is( $f5->name, 's1', 'Fifth field name is "s1"' );
131     is( $f5->data_type, 'set', 'Type is "set"' );
132     is( $f5->size, 0, 'Size is "0"' );
133     is( $f5->is_nullable, 1, 'Field can be null' );
134     is( $f5->default_value, 'b', 'Default value is "b"' );
135     is( $f5->is_primary_key, 0, 'Field is not PK' );
136     my %f5extra = $f5->extra;
137     is( join(',', @{ $f5extra{'list'} || [] }), 'a,b,c', 'List is "a,b,c"' );
138
139     my $f6 = shift @fields;
140     is( $f6->name, 'e1', 'Sixth field name is "e1"' );
141     is( $f6->data_type, 'enum', 'Type is "enum"' );
142     is( $f6->size, 0, 'Size is "0"' );
143     is( $f6->is_nullable, 1, 'Field can be null' );
144     is( $f6->default_value, 'c', 'Default value is "c"' );
145     is( $f6->is_primary_key, 0, 'Field is not PK' );
146     my %f6extra = $f6->extra;
147     is( join(',', @{ $f6extra{'list'} || [] }), 'a,b,c', 'List is "a,b,c"' );
148
149     my $f7 = shift @fields;
150     is( $f7->name, 'name', 'Seventh field name is "name"' );
151     is( $f7->data_type, 'varchar', 'Type is "varchar"' );
152     is( $f7->size, 30, 'Size is "30"' );
153     is( $f7->is_nullable, 1, 'Field can be null' );
154     is( $f7->default_value, 'NULL', 'Default value is "NULL"' );
155     is( $f7->is_primary_key, 0, 'Field is not PK' );
156
157     my $f8 = shift @fields;
158     is( $f8->name, 'foo_type', 'Eighth field name is "foo_type"' );
159     is( $f8->data_type, 'enum', 'Type is "enum"' );
160     is( $f8->size, 0, 'Size is "0"' );
161     is( $f8->is_nullable, 0, 'Field cannot be null' );
162     is( $f8->default_value, 'vk', 'Default value is "vk"' );
163     is( $f8->is_primary_key, 0, 'Field is not PK' );
164     my %f8extra = $f8->extra;
165     is( join(',', @{ $f8extra{'list'} || [] }), 'vk,ck', 'List is "vk,ck"' );
166
167     my $f9 = shift @fields;
168     is( $f9->name, 'date', 'Ninth field name is "date"' );
169     is( $f9->data_type, 'timestamp', 'Type is "timestamp"' );
170     is( $f9->size, 0, 'Size is "0"' );
171     is( $f9->is_nullable, 1, 'Field can be null' );
172     is( $f9->default_value, undef, 'Default value is undefined' );
173     is( $f9->is_primary_key, 0, 'Field is not PK' );
174
175     my $f10 = shift @fields;
176     is( $f10->name, 'time_stamp2', 'Tenth field name is "time_stamp2"' );
177     is( $f10->data_type, 'timestamp', 'Type is "timestamp"' );
178     is( $f10->size, 0, 'Size is "0"' );
179     is( $f10->is_nullable, 1, 'Field can be null' );
180     is( $f10->default_value, undef, 'Default value is undefined' );
181     is( $f10->is_primary_key, 0, 'Field is not PK' );
182
183     my @indices = $table->get_indices;
184     is( scalar @indices, 3, 'Right number of indices (3)' );
185
186     my $i1 = shift @indices;
187     is( $i1->name, '', 'No name on index' );
188     is( $i1->type, NORMAL, 'Normal index' );
189     is( join(',', $i1->fields ), 'i1', 'Index is on field "i1"' );
190
191     my $i2 = shift @indices;
192     is( $i2->name, 'date_idx', 'Name is "date_idx"' );
193     is( $i2->type, NORMAL, 'Normal index' );
194     is( join(',', $i2->fields ), 'date', 'Index is on field "date"' );
195
196     my $i3 = shift @indices; 
197     is( $i3->name, 'name_idx', 'Name is "name_idx"' );
198     is( $i3->type, NORMAL, 'Normal index' );
199     is( join(',', $i3->fields ), 'name', 'Index is on field "name(10)"' ); ## FIX ME
200
201     my @constraints = $table->get_constraints;
202     is( scalar @constraints, 2, 'Right number of constraints (2)' );
203
204     my $c1 = shift @constraints;
205     is( $c1->type, PRIMARY_KEY, 'Constraint is a PK' );
206     is( join(',', $c1->fields), 'check_id', 'Constraint is on "check_id"' );
207
208     my $c2 = shift @constraints;
209     is( $c2->type, UNIQUE, 'Constraint is UNIQUE' );
210     is( join(',', $c2->fields), 'date,i1', 'Constraint is on "date, i1"' );
211 }
212
213 {
214     my $tr = SQL::Translator->new({ from => 'MySQL' });
215     my $val = $tr->parse(
216         q[
217             CREATE TABLE orders (
218               order_id                  integer NOT NULL auto_increment,
219               member_id                 varchar(255) comment 'fk to member',
220               billing_address_id        int,
221               shipping_address_id       int,
222               credit_card_id            int,
223               status                    smallint NOT NULL,
224               store_id                  varchar(255) NOT NULL REFERENCES store,
225               tax                       decimal(8,2),
226               shipping_charge           decimal(8,2),
227               price_paid                decimal(8,2),
228               PRIMARY KEY (order_id),
229               KEY (status),
230               KEY (billing_address_id),
231               KEY (shipping_address_id),
232               KEY (member_id, store_id),
233               FOREIGN KEY (status)              REFERENCES order_status(id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
234               FOREIGN KEY (billing_address_id)  REFERENCES address(address_id),
235               FOREIGN KEY (shipping_address_id) REFERENCES address(address_id)
236             ) TYPE=INNODB COMMENT = 'orders table comment';
237
238             CREATE TABLE address (
239               address_id                int NOT NULL auto_increment,
240               recipient                 varchar(255) NOT NULL,
241               address1                  varchar(255) NOT NULL,
242               address2                  varchar(255),
243               city                      varchar(255) NOT NULL,
244               state                     varchar(255) NOT NULL,
245               postal_code               varchar(255) NOT NULL,
246               phone                     varchar(255),
247               PRIMARY KEY (address_id)
248             ) TYPE=INNODB;
249
250             CREATE TABLE store (
251               id                        int NOT NULL auto_increment,
252               name                      varchar(255) NOT NULL
253             ) TYPE=INNODB;
254
255             CREATE TABLE order_status (
256               id                        int NOT NULL,
257               status                    varchar(255) NOT NULL
258             ) TYPE=INNODB;
259         ]
260     ) or die $tr->error;
261
262     my $schema = $tr->schema;
263     is( $schema->is_valid, 1, 'Schema is valid' );
264     my @tables = $schema->get_tables;
265     is( scalar @tables, 4, 'Right number of tables (4)' );
266
267     my $t1  = shift @tables;
268     is( $t1->name, 'orders', 'Found "orders" table' );
269     is( $t1->comments, 'orders table comment', 'Table comment OK' );
270
271     my @fields = $t1->get_fields;
272     is( scalar @fields, 10, 'Right number of fields (10)' );
273
274     my $f1 = shift @fields;
275     is( $f1->name, 'order_id', 'First field name is "order_id"' );
276     is( $f1->data_type, 'integer', 'Type is "integer"' );
277     is( $f1->size, 0, 'Size is "0"' );
278     is( $f1->is_nullable, 0, 'Field cannot be null' );
279     is( $f1->default_value, undef, 'Default value is undefined' );
280     is( $f1->is_primary_key, 1, 'Field is PK' );
281     is( $f1->is_auto_increment, 1, 'Field is auto inc' );
282
283     my $f2 = shift @fields;
284     is( $f2->name, 'member_id', 'Second field name is "member_id"' );
285     is( $f2->data_type, 'varchar', 'Type is "varchar"' );
286     is( $f2->size, 255, 'Size is "255"' );
287     is( $f2->is_nullable, 1, 'Field can be null' );
288     is( $f2->comments, 'fk to member', 'Field comment OK' );
289     is( $f2->default_value, undef, 'Default value is undefined' );
290
291     my $f3 = shift @fields;
292     is( $f3->name, 'billing_address_id', 
293         'Third field name is "billing_address_id"' );
294     is( $f3->data_type, 'int', 'Type is "int"' );
295     is( $f3->size, 0, 'Size is "0"' );
296
297     my $f4 = shift @fields;
298     is( $f4->name, 'shipping_address_id', 
299         'Fourth field name is "shipping_address_id"' );
300     is( $f4->data_type, 'int', 'Type is "int"' );
301     is( $f4->size, 0, 'Size is "0"' );
302
303     my $f5 = shift @fields;
304     is( $f5->name, 'credit_card_id', 'Fifth field name is "credit_card_id"' );
305     is( $f5->data_type, 'int', 'Type is "int"' );
306     is( $f5->size, 0, 'Size is "0"' );
307
308     my $f6 = shift @fields;
309     is( $f6->name, 'status', 'Sixth field name is "status"' );
310     is( $f6->data_type, 'smallint', 'Type is "smallint"' );
311     is( $f6->size, 0, 'Size is "0"' );
312     is( $f6->is_nullable, 0, 'Field cannot be null' );
313
314     my $f7 = shift @fields;
315     is( $f7->name, 'store_id', 'Seventh field name is "store_id"' );
316     is( $f7->data_type, 'varchar', 'Type is "varchar"' );
317     is( $f7->size, 255, 'Size is "255"' );
318     is( $f7->is_nullable, 0, 'Field cannot be null' );
319     is( $f7->is_foreign_key, 1, 'Field is a FK' );
320     my $fk_ref = $f7->foreign_key_reference;
321     isa_ok( $fk_ref, 'SQL::Translator::Object::Constraint', 'FK' );
322     is( $fk_ref->reference_table, 'store', 'FK is to "store" table' );
323
324     my $f8 = shift @fields;
325     is( $f8->name, 'tax', 'Eighth field name is "tax"' );
326     is( $f8->data_type, 'decimal', 'Type is "decimal"' );
327     is( $f8->size, '8,2', 'Size is "8,2"' );
328
329     my $f9 = shift @fields;
330     is( $f9->name, 'shipping_charge', 'Ninth field name is "shipping_charge"' );
331     is( $f9->data_type, 'decimal', 'Type is "decimal"' );
332     is( $f9->size, '8,2', 'Size is "8,2"' );
333
334     my $f10 = shift @fields;
335     is( $f10->name, 'price_paid', 'Tenth field name is "price_paid"' );
336     is( $f10->data_type, 'decimal', 'Type is "decimal"' );
337     is( $f10->size, '8,2', 'Size is "8,2"' );
338
339     my @indices = $t1->get_indices;
340     is( scalar @indices, 4, 'Right number of indices (4)' );
341
342     my $i1 = shift @indices;
343     is( $i1->type, NORMAL, 'First index is normal' );
344     is( join(',', $i1->fields), 'status', 'Index is on "status"' );
345
346     my $i2 = shift @indices;
347     is( $i2->type, NORMAL, 'Second index is normal' );
348     is( join(',', $i2->fields), 'billing_address_id', 
349         'Index is on "billing_address_id"' );
350
351     my $i3 = shift @indices;
352     is( $i3->type, NORMAL, 'Third index is normal' );
353     is( join(',', $i3->fields), 'shipping_address_id', 
354         'Index is on "shipping_address_id"' );
355
356     my $i4 = shift @indices;
357     is( $i4->type, NORMAL, 'Third index is normal' );
358     is( join(',', $i4->fields), 'member_id,store_id', 
359         'Index is on "member_id,store_id"' );
360
361     my @constraints = $t1->get_constraints;
362     is( scalar @constraints, 5, 'Right number of constraints (5)' );
363
364     my $c1 = shift @constraints;
365     is( $c1->type, PRIMARY_KEY, 'Constraint is a PK' );
366     is( join(',', $c1->fields), 'order_id', 'Constraint is on "order_id"' );
367
368     my $c2 = shift @constraints;
369     is( $c2->type, FOREIGN_KEY, 'Constraint is a FK' );
370     is( join(',', $c2->fields), 'status', 'Constraint is on "status"' );
371     is( $c2->reference_table, 'order_status', 'To table "order_status"' );
372     is( join(',', $c2->reference_fields), 'id', 'To field "id"' );
373
374     my $c3 = shift @constraints;
375     is( $c3->type, FOREIGN_KEY, 'Constraint is a FK' );
376     is( join(',', $c3->fields), 'billing_address_id', 
377         'Constraint is on "billing_address_id"' );
378     is( $c3->reference_table, 'address', 'To table "address"' );
379     is( join(',', $c3->reference_fields), 'address_id', 
380         'To field "address_id"' );
381
382     my $c4 = shift @constraints;
383     is( $c4->type, FOREIGN_KEY, 'Constraint is a FK' );
384     is( join(',', $c4->fields), 'shipping_address_id', 
385         'Constraint is on "shipping_address_id"' );
386     is( $c4->reference_table, 'address', 'To table "address"' );
387     is( join(',', $c4->reference_fields), 'address_id', 
388         'To field "address_id"' );
389
390     my $c5 = shift @constraints;
391     is( $c5->type, FOREIGN_KEY, 'Constraint is a FK' );
392     is( join(',', $c5->fields), 'store_id', 'Constraint is on "store_id"' );
393     is( $c5->reference_table, 'store', 'To table "store"' );
394     is( join(',', map { $_ || '' } $c5->reference_fields), '', 
395         'No reference fields defined' );
396
397     my $t2  = shift @tables;
398     is( $t2->name, 'address', 'Found "address" table' );
399
400     my @t2_fields = $t2->get_fields;
401     is( scalar @t2_fields, 8, 'Right number of fields (8)' );
402 }
403
404 # djh Tests for:
405 #    USE database ;
406 #    ALTER TABLE ADD FOREIGN KEY
407 #    trailing comma on last create definition
408 #    Ignoring INSERT statements
409 #
410 {
411     my $tr = SQL::Translator->new({ from => 'MySQL' });
412     my $data = $tr->parse(
413         q[
414             USE database_name;
415
416             CREATE TABLE one (
417               id                     integer NOT NULL auto_increment,
418               two_id                 integer NOT NULL auto_increment,
419               some_data              text,
420               PRIMARY KEY (id),
421               INDEX (two_id),
422             ) TYPE=INNODB;
423
424             CREATE TABLE two (
425               id                     int NOT NULL auto_increment,
426               one_id                 int NOT NULL auto_increment,
427               some_data              text,
428               PRIMARY KEY (id),
429               INDEX (one_id),
430               FOREIGN KEY (one_id) REFERENCES one (id),
431             ) TYPE=INNODB;
432
433             ALTER TABLE one ADD FOREIGN KEY (two_id) REFERENCES two (id);
434
435             INSERT absolutely *#! any old $£ ? rubbish, even "quoted; semi-what""sits";
436         ]
437     ) or die $tr->error;
438
439     my $schema = $tr->schema;
440     is( $schema->is_valid, 1, 'Schema is valid' );
441     my $db_name = $schema->name;
442     is( $db_name, 'database_name', 'Database name extracted from USE' );
443     my @tables = $schema->get_tables;
444     is( scalar @tables, 2, 'Right number of tables (2)' );
445     my $table1 = shift @tables;
446     is( $table1->name, 'one', 'Found "one" table' );
447     my $table2 = shift @tables;
448     is( $table2->name, 'two', 'Found "two" table' );
449
450     my @constraints = $table1->get_constraints;
451     is(scalar @constraints, 2, 'Right number of constraints (2) on table one');
452
453     my $t1c1 = shift @constraints;
454     is( $t1c1->type, PRIMARY_KEY, 'Constraint is a PK' );
455     is( join(',', $t1c1->fields), 'id', 'Constraint is on "id"' );
456
457     my $t1c2 = shift @constraints;
458     is( $t1c2->type, FOREIGN_KEY, 'Constraint is a FK' );
459     is( join(',', $t1c2->fields), 'two_id', 'Constraint is on "two_id"' );
460     is( $t1c2->reference_table, 'two', 'To table "two"' );
461     is( join(',', $t1c2->reference_fields), 'id', 'To field "id"' );
462
463     @constraints = $table2->get_constraints;
464     is(scalar @constraints, 2, 'Right number of constraints (2) on table two');
465
466     my $t2c1 = shift @constraints;
467     is( $t2c1->type, PRIMARY_KEY, 'Constraint is a PK' );
468     is( join(',', $t2c1->fields), 'id', 'Constraint is on "id"' );
469
470     my $t2c2 = shift @constraints;
471     is( $t2c2->type, FOREIGN_KEY, 'Constraint is a FK' );
472     is( join(',', $t2c2->fields), 'one_id', 'Constraint is on "one_id"' );
473     is( $t2c2->reference_table, 'one', 'To table "one"' );
474     is( join(',', $t2c2->reference_fields), 'id', 'To field "id"' );
475 }
476
477 # cch Tests for:
478 #    comments like: /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
479 #    char fields with character set and collate qualifiers
480 #    timestamp fields with on update qualifier
481 #    charset table option
482 #
483 {
484     my $tr = SQL::Translator->new({ from => 'MySQL', parser_args => { mysql_parser_version => 50003 } });
485     my $data = $tr->parse(
486             q[
487                 DELIMITER ;;
488             /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;;
489                         /*!50003 CREATE */ /*!50017 DEFINER=`cmdomain`@`localhost` */
490                         /*!50003 TRIGGER `acl_entry_insert` BEFORE INSERT ON `acl_entry`
491                                 FOR EACH ROW SET NEW.dateCreated = CONVERT_TZ(SYSDATE(),'SYSTEM','+0:00'),
492                                 NEW.dateModified = CONVERT_TZ(SYSDATE(),'SYSTEM','+0:00') */;;
493
494                         DELIMITER ;
495             CREATE TABLE one (
496               `op` varchar(255) character set latin1 collate latin1_bin default NULL,
497               `last_modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
498             ) TYPE=INNODB DEFAULT CHARSET=latin1;
499
500                         /*!50001 CREATE ALGORITHM=UNDEFINED */
501                         /*!50013 DEFINER=`cmdomain`@`localhost` SQL SECURITY DEFINER */
502                         /*! VIEW `vs_asset` AS 
503                                 select `a`.`asset_id` AS `asset_id`,`a`.`fq_name` AS `fq_name`,
504                                 `cfgmgmt_mig`.`ap_extract_folder`(`a`.`fq_name`) AS `folder_name`,
505                                 `cfgmgmt_mig`.`ap_extract_asset`(`a`.`fq_name`) AS `asset_name`,
506                                 `a`.`annotation` AS `annotation`,`a`.`asset_type` AS `asset_type`,
507                                 `a`.`foreign_asset_id` AS `foreign_asset_id`,
508                                 `a`.`foreign_asset_id2` AS `foreign_asset_id2`,`a`.`dateCreated` AS `date_created`,
509                                 `a`.`dateModified` AS `date_modified`,`a`.`container_id` AS `container_id`,
510                                 `a`.`creator_id` AS `creator_id`,`a`.`modifier_id` AS `modifier_id`,
511                                 `m`.`user_id` AS `user_access` 
512                                 from (`asset` `a` join `M_ACCESS_CONTROL` `m` on((`a`.`acl_id` = `m`.`acl_id`))) */;
513                         DELIMITER ;;
514                         /*!50001 CREATE */
515                         /*! VIEW `vs_asset2` AS 
516                                 select `a`.`asset_id` AS `asset_id`,`a`.`fq_name` AS `fq_name`,
517                                 `cfgmgmt_mig`.`ap_extract_folder`(`a`.`fq_name`) AS `folder_name`,
518                                 `cfgmgmt_mig`.`ap_extract_asset`(`a`.`fq_name`) AS `asset_name`,
519                                 `a`.`annotation` AS `annotation`,`a`.`asset_type` AS `asset_type`,
520                                 `a`.`foreign_asset_id` AS `foreign_asset_id`,
521                                 `a`.`foreign_asset_id2` AS `foreign_asset_id2`,`a`.`dateCreated` AS `date_created`,
522                                 `a`.`dateModified` AS `date_modified`,`a`.`container_id` AS `container_id`,
523                                 `a`.`creator_id` AS `creator_id`,`a`.`modifier_id` AS `modifier_id`,
524                                 `m`.`user_id` AS `user_access` 
525                                 from (`asset` `a` join `M_ACCESS_CONTROL` `m` on((`a`.`acl_id` = `m`.`acl_id`))) */;
526                         DELIMITER ;;
527                         /*!50001 CREATE OR REPLACE */
528                         /*! VIEW `vs_asset3` AS 
529                                 select `a`.`asset_id` AS `asset_id`,`a`.`fq_name` AS `fq_name`,
530                                 `cfgmgmt_mig`.`ap_extract_folder`(`a`.`fq_name`) AS `folder_name`,
531                                 `cfgmgmt_mig`.`ap_extract_asset`(`a`.`fq_name`) AS `asset_name`,
532                                 `a`.`annotation` AS `annotation`,`a`.`asset_type` AS `asset_type`,
533                                 `a`.`foreign_asset_id` AS `foreign_asset_id`,
534                                 `a`.`foreign_asset_id2` AS `foreign_asset_id2`,`a`.`dateCreated` AS `date_created`,
535                                 `a`.`dateModified` AS `date_modified`,`a`.`container_id` AS `container_id`,
536                                 `a`.`creator_id` AS `creator_id`,`a`.`modifier_id` AS `modifier_id`,
537                                 `m`.`user_id` AS `user_access` 
538                                 from (`asset` `a` join `M_ACCESS_CONTROL` `m` on((`a`.`acl_id` = `m`.`acl_id`))) */;
539                         DELIMITER ;;
540                         /*!50003 CREATE*/ /*!50020 DEFINER=`cmdomain`@`localhost`*/ /*!50003 FUNCTION `ap_from_millitime_nullable`( millis_since_1970 BIGINT ) RETURNS timestamp
541                         DETERMINISTIC
542                                 BEGIN
543                                 DECLARE rval TIMESTAMP;
544                                     IF ( millis_since_1970 = 0 )
545                                     THEN
546                                         SET rval = NULL;
547                                     ELSE
548                                         SET rval = FROM_UNIXTIME( millis_since_1970 / 1000 );
549                                     END IF;
550                                     RETURN rval;
551                                 END */;;
552                         /*!50003 CREATE*/ /*!50020 DEFINER=`cmdomain`@`localhost`*/ /*!50003 PROCEDURE `sp_update_security_acl`(IN t_acl_id INTEGER)
553                         BEGIN
554                                 DECLARE hasMoreRows BOOL DEFAULT TRUE;
555                                 DECLARE t_group_id INT;
556                                 DECLARE t_user_id INT ;
557                                 DECLARE t_user_name VARCHAR (512) ;
558                                 DECLARE t_message VARCHAR (512) ;
559
560                                 DROP TABLE IF EXISTS group_acl;
561                                 DROP TABLE IF EXISTS user_group;
562                                 DELETE FROM M_ACCESS_CONTROL WHERE acl_id = t_acl_id;
563
564                                 CREATE TEMPORARY TABLE group_acl SELECT DISTINCT p.id group_id, d.acl_id acl_id
565                                         FROM  asset d, acl_entry e, alterpoint_principal p
566                                         WHERE d.acl_id = e.acl
567                                         AND p.id = e.principal AND d.acl_id = t_acl_id;
568
569                                 CREATE TEMPORARY TABLE user_group  SELECT a.id user_id, a.name user_name, c.id group_id
570                                         FROM alterpoint_principal a, groups_for_user b, alterpoint_principal c
571                                         WHERE a.id = b.user_ref AND b.elt = c.id;
572
573                                 INSERT INTO M_ACCESS_CONTROL SELECT DISTINCT group_acl.group_id, group_acl.acl_id, user_group.user_id, user_group.user_name
574                                         FROM group_acl, user_group
575                                         WHERE group_acl.group_id = user_group.group_id ;
576                         END */;;
577         ]
578     ) or die $tr->error;
579
580     my $schema = $tr->schema;
581     is( $schema->is_valid, 1, 'Schema is valid' );
582     my @tables = $schema->get_tables;
583     is( scalar @tables, 1, 'Right number of tables (1)' );
584     my $table1 = shift @tables;
585     is( $table1->name, 'one', 'Found "one" table' );
586
587     my @fields = $table1->get_fields;
588     is(scalar @fields, 2, 'Right number of fields (2) on table one');
589     my $tableTypeFound = 0;
590     my $charsetFound = 0;
591         for my $t1_option_ref ( $table1->options ) {
592                 my($key, $value) = %{$t1_option_ref};
593                 if ( $key eq 'TYPE' ) {
594                         is($value, 'INNODB', 'Table has right table type option' );
595                         $tableTypeFound = 1;
596                 } elsif ( $key eq 'CHARACTER SET' ) {
597                         is($value, 'latin1', 'Table has right character set option' );
598                         $charsetFound = 1;
599                 }
600         }
601         fail('Table did not have a type option') unless $tableTypeFound;
602         fail('Table did not have a character set option') unless $charsetFound;
603
604     my $t1f1 = shift @fields;
605     is( $t1f1->data_type, 'varchar', 'Field is a varchar' );
606     is( $t1f1->size, 255, 'Field is right size' );
607     is( $t1f1->extra('character set'), 'latin1', 'Field has right character set qualifier' );
608     is( $t1f1->extra('collate'), 'latin1_bin', 'Field has right collate qualifier' );
609     is( $t1f1->default_value, 'NULL', 'Field has right default value' );
610
611     my $t1f2 = shift @fields;
612     is( $t1f2->data_type, 'timestamp', 'Field is a timestamp' );
613     ok( !$t1f2->is_nullable, 'Field is not nullable' );
614     is_deeply(
615       $t1f2->default_value,
616       \'CURRENT_TIMESTAMP',
617       'Field has right default value' 
618     );
619     is( $t1f2->extra('on update'), 'CURRENT_TIMESTAMP', 'Field has right on update qualifier' );
620     
621     my @views = $schema->get_views;
622     is( scalar @views, 3, 'Right number of views (3)' );
623     my ($view3, $view1, $view2) = @views;
624     is( $view1->name, 'vs_asset', 'Found "vs_asset" view' );
625     is( $view2->name, 'vs_asset2', 'Found "vs_asset2" view' );
626     is( $view3->name, 'vs_asset3', 'Found "vs_asset3" view' );
627         like($view1->sql, qr/ALGORITHM=UNDEFINED/, "Detected algorithm");
628         like($view1->sql, qr/vs_asset/, "Detected view vs_asset");
629         unlike($view1->sql, qr/cfgmgmt_mig/, "Did not detect cfgmgmt_mig");
630     
631     my @procs = $schema->get_procedures;
632     is( scalar @procs, 2, 'Right number of procedures (2)' );
633     my $proc1 = shift @procs;
634     is( $proc1->name, 'ap_from_millitime_nullable', 'Found "ap_from_millitime_nullable" procedure' );
635         like($proc1->sql, qr/CREATE FUNCTION ap_from_millitime_nullable/, "Detected procedure ap_from_millitime_nullable");
636     my $proc2 = shift @procs;
637     is( $proc2->name, 'sp_update_security_acl', 'Found "sp_update_security_acl" procedure' );
638         like($proc2->sql, qr/CREATE PROCEDURE sp_update_security_acl/, "Detected procedure sp_update_security_acl");
639 }
640
641 # Tests for collate table option
642 {
643     my $tr = SQL::Translator->new({ from => 'MySQL', parser_args => { mysql_parser_version => 50003 } });
644     my $data = $tr->parse(
645         q[
646           CREATE TABLE test ( id int ) DEFAULT CHARACTER SET latin1 COLLATE latin1_bin;
647          ] ); 
648
649     my $schema = $tr->schema;
650     is( $schema->is_valid, 1, 'Schema is valid' );
651     my @tables = $schema->get_tables;
652     is( scalar @tables, 1, 'Right number of tables (1)' );
653     my $table1 = shift @tables;
654     is( $table1->name, 'test', 'Found "test" table' );
655
656
657     my $collate = "Not found!";
658     my $charset = "Not found!";
659     for my $t1_option_ref ( $table1->options ) {
660       my($key, $value) = %{$t1_option_ref};
661       $collate = $value if $key eq 'COLLATE';
662       $charset = $value if $key eq 'CHARACTER SET';
663     }
664     is($collate, 'latin1_bin', "Collate found");
665     is($charset, 'latin1', "Character set found");
666 }
667
668 # Test the mysql version parser (probably needs to migrate to t/utils.t)
669 my $parse_as = {
670     perl => {
671         '3.23.2'    => 3.023002,
672         '4'         => 4.000000,
673         '50003'     => 5.000003,
674         '5.01.0'    => 5.001000,
675         '5.1'       => 5.001000,
676     },
677     mysql => {
678         '3.23.2'    => 32302,
679         '4'         => 40000,
680         '50003'     => 50003,
681         '5.01.0'    => 50100,
682         '5.1'       => 50100,
683     },
684 };
685
686 my $tr = SQL::Translator->new;
687 for my $target (keys %$parse_as) {
688     for my $str (keys %{$parse_as->{$target}}) {
689         cmp_ok (
690             $tr->engine_version($str, $target),
691             '==',
692             $parse_as->{$target}{$str},
693             "'$str' parsed as $target version '$parse_as->{$target}{$str}'",
694         );
695     }
696 }
697
698 eval { $tr->engine_version ('bogus5.1') };
699 ok ($@, 'Exception thrown on invalid version string');
700
701 {
702     my $tr = SQL::Translator->new({ from => 'MySQL' });
703     my $data = q|create table merge_example (
704        id int(11) NOT NULL auto_increment,
705        shape_field geometry NOT NULL,
706        PRIMARY KEY (id),
707        SPATIAL KEY shape_field (shape_field)
708     ) ENGINE=MRG_MyISAM UNION=(`sometable_0`,`sometable_1`,`sometable_2`);|;
709
710     my $val = $tr->parse($data);
711     my $schema = $tr->schema;
712     is( $schema->is_valid, 1, 'Schema is valid' );
713     my @tables = $schema->get_tables;
714     is( scalar @tables, 1, 'Right number of tables (1)' );
715     my $table  = shift @tables;
716     is( $table->name, 'merge_example', 'Found "merge_example" table' );
717
718     my $tableTypeFound = 0;
719     my $unionFound = 0;
720     for my $t_option_ref ( $table->options ) {
721       my($key, $value) = %{$t_option_ref};
722       if ( $key eq 'ENGINE' ) {
723         is($value, 'MRG_MyISAM', 'Table has right table engine option' );
724         $tableTypeFound = 1;
725       } elsif ( $key eq 'UNION' ) {
726         is_deeply($value, [ 'sometable_0','sometable_1','sometable_2' ],
727           "UNION option has correct set");
728         $unionFound = 1;
729       }
730     }
731
732     fail('Table did not have a type option') unless $tableTypeFound;
733     fail('Table did not have a union option') unless $unionFound;
734
735     my @fields = $table->get_fields;
736     is( scalar @fields, 2, 'Right number of fields (2)' );
737     my $f1 = shift @fields;
738     my $f2 = shift @fields;
739     is( $f1->name, 'id', 'First field name is "id"' );
740     is( $f1->data_type, 'int', 'Type is "int"' );
741     is( $f1->size, 11, 'Size is "11"' );
742     is( $f1->is_nullable, 0, 'Field cannot be null' );
743     is( $f1->is_primary_key, 1, 'Field is PK' );
744
745     is( $f2->name, 'shape_field', 'Second field name is "shape_field"' );
746     is( $f2->data_type, 'geometry', 'Type is "geometry"' );
747     is( $f2->is_nullable, 0, 'Field cannot be null' );
748     is( $f2->is_primary_key, 0, 'Field is not PK' );
749
750     my @indices = $table->get_indices;
751     is( scalar @indices, 1, 'Right number of indices (1)' );
752     my $i1 = shift @indices;
753     is( $i1->name, 'shape_field', 'No name on index' );
754     is( $i1->type, SPATIAL, 'Spatial index' );
755
756     my @constraints = $table->get_constraints;
757     is( scalar @constraints, 1, 'Right number of constraints (1)' );
758     my $c = shift @constraints;
759     is( $c->type, PRIMARY_KEY, 'Constraint is a PK' );
760     is( join(',', $c->fields), 'id', 'Constraint is on "id"' );
761 }
762
763 {
764     my @data = (
765         q|create table quote (
766             id int(11) NOT NULL auto_increment,
767             PRIMARY KEY (id)
768         ) ENGINE="innodb";|,
769         q|create table quote (
770             id int(11) NOT NULL auto_increment,
771             PRIMARY KEY (id)
772         ) ENGINE='innodb';|,
773         q|create table quote (
774             id int(11) NOT NULL auto_increment,
775             PRIMARY KEY (id)
776         ) ENGINE=innodb;|,
777     );
778     for my $data (@data) {
779         my $tr = SQL::Translator->new({ from => 'MySQL' });
780
781         my $val = $tr->parse($data);
782         my $schema = $tr->schema;
783         is( $schema->is_valid, 1, 'Schema is valid' );
784         my @tables = $schema->get_tables;
785         is( scalar @tables, 1, 'Right number of tables (1)' );
786         my $table  = shift @tables;
787         is( $table->name, 'quote', 'Found "quote" table' );
788
789         my $tableTypeFound = 0;
790         for my $t_option_ref ( $table->options ) {
791         my($key, $value) = %{$t_option_ref};
792         if ( $key eq 'ENGINE' ) {
793             is($value, 'innodb', 'Table has right table engine option' );
794             $tableTypeFound = 1;
795         }
796         }
797
798         fail('Table did not have a type option') unless $tableTypeFound;
799
800         my @fields = $table->get_fields;
801         my $f1 = shift @fields;
802         is( $f1->name, 'id', 'First field name is "id"' );
803         is( $f1->data_type, 'int', 'Type is "int"' );
804         is( $f1->size, 11, 'Size is "11"' );
805         is( $f1->is_nullable, 0, 'Field cannot be null' );
806         is( $f1->is_primary_key, 1, 'Field is PK' );
807     }
808 }
809
810 {
811     my $tr = SQL::Translator->new({ from => 'MySQL' });
812     my $data = q|create table "sessions" (
813         id char(32) not null default '0' primary key,
814         ssn varchar(12) NOT NULL default 'test single quotes like in you''re',
815         user varchar(20) NOT NULL default 'test single quotes escaped like you\'re',
816     );|;
817
818     my $val = $tr->parse($data);
819     my $schema = $tr->schema;
820     is( $schema->is_valid, 1, 'Schema is valid' );
821     my @tables = $schema->get_tables;
822     is( scalar @tables, 1, 'Right number of tables (1)' );
823     my $table  = shift @tables;
824     is( $table->name, 'sessions', 'Found "sessions" table' );
825
826     my @fields = $table->get_fields;
827     is( scalar @fields, 3, 'Right number of fields (3)' );
828     my $f1 = shift @fields;
829     my $f2 = shift @fields;
830     my $f3 = shift @fields;
831     is( $f1->name, 'id', 'First field name is "id"' );
832     is( $f1->data_type, 'char', 'Type is "char"' );
833     is( $f1->size, 32, 'Size is "32"' );
834     is( $f1->is_nullable, 0, 'Field cannot be null' );
835     is( $f1->default_value, '0', 'Default value is "0"' );
836     is( $f1->is_primary_key, 1, 'Field is PK' );
837
838     is( $f2->name, 'ssn', 'Second field name is "ssn"' );
839     is( $f2->data_type, 'varchar', 'Type is "varchar"' );
840     is( $f2->size, 12, 'Size is "12"' );
841     is( $f2->is_nullable, 0, 'Field can not be null' );
842     is( $f2->default_value, "test single quotes like in you''re", "Single quote in default value is escaped properly" );
843     is( $f2->is_primary_key, 0, 'Field is not PK' );
844
845     # this is more of a sanity test because the original sqlt regex for default looked for an escaped quote represented as \'
846     # however in mysql 5.x (and probably other previous versions) still actually outputs that as '' 
847     is( $f3->name, 'user', 'Second field name is "user"' );
848     is( $f3->data_type, 'varchar', 'Type is "varchar"' );
849     is( $f3->size, 20, 'Size is "20"' );
850     is( $f3->is_nullable, 0, 'Field can not be null' );
851     is( $f3->default_value, "test single quotes escaped like you\\'re", "Single quote in default value is escaped properly" );
852     is( $f3->is_primary_key, 0, 'Field is not PK' );
853 }
854
855 done_testing;