root/branches/release-39/t/driver-tests.pl @ 2500

Revision 2500, 26.6 kB (checked in by fumiakiy, 18 months ago)

Modernized how sort argument is specified in group_by query. BugId:79977. The legacy way of specifying it is still allowed but discouraged.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#!/usr/bin/perl
2
3# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
4# This program is distributed under the terms of the
5# GNU General Public License, version 2.
6#
7# $Id$
8
9use strict;
10use warnings;
11use Data::Dumper;
12use English qw( -no_match_vars );
13
14$OUTPUT_AUTOFLUSH = 1;
15
16# Run this script as a symlink, in the form of 99-driver.t, ie:
17# ln -s driver-tests.pl 99-driver.t
18
19BEGIN {
20    # Set config to driver-test.cfg when run as /path/to/99-driver.t
21    $ENV{MT_CONFIG} = "$1-test.cfg"
22        if __FILE__ =~ m{ [\\/] \d+- ([^\\/]+) \.t \z }xms;
23}
24
25use Test::More;
26use Test::Deep;
27use lib 't/lib';
28use MT::Test qw(:testdb :time);
29
30BEGIN {
31    plan skip_all => "Configuration file $ENV{MT_CONFIG} not found"
32        if !-r $ENV{MT_CONFIG};
33}
34
35plan tests => 197;
36
37package Zot;
38use base 'MT::Object';
39__PACKAGE__->install_properties({
40    column_defs => {
41        'id' => 'integer not null auto_increment',
42        'x' => 'string(255)',
43    },
44    primary_key => 'id',
45    datasource => 'zot',
46});
47
48package main;
49
50my(@foo, @bar);
51my($tmp, @tmp);
52
53# Test for existing table
54ok(MT::Object->driver->dbd->ddl_class->column_defs('Foo'), "table mt_foo exists after upgrade");
55# Test for non-existent table
56ok(!MT::Object->driver->dbd->ddl_class->column_defs('Zot'), "table mt_zot does not exist after upgrade where undefined");
57
58## Test creating object with new
59##     test column access through column, then through AUTOLOAD
60$foo[0] = Foo->new;
61isa_ok($foo[0], 'Foo', 'New Foo');
62$foo[0]->column('name', 'foo');
63is($foo[0]->column('name'), 'foo', 'Setting name field with column() persists through access');
64$foo[0]->name('foo');
65is($foo[0]->name, 'foo', 'Setting name field with mutator method persists through access');
66$foo[0]->status(2);
67$foo[0]->text('bar');
68
69## Test saving created object
70ok($foo[0]->save, 'A Foo could be saved');
71is($foo[0]->id, 1, 'First Foo was given an id of 1, says accessor method');
72is($foo[0]->column('id'), $foo[0]->id, 'First Foo was given an id of 1, says column()');
73
74sub _is_object {
75    my ($got, $expected, $name) = @_;
76
77    if (!defined $got) {
78        fail($name);
79        diag('    got undef, not an object');
80        return;
81    }
82
83    if (!$got->isa(ref $expected)) {
84        fail($name);
85        diag('    got a ', ref($got), ' but expected a ', ref $expected);
86        return;
87    }
88
89    if ($got == $expected) {
90        fail($name);
91        diag('    got the exact same instance as expected, when really expected a different but equivalent object');
92        return;
93    }
94
95    # Ignore object columns that have undefined values.
96    my (%got_values, %expected_values);
97    while (my ($field, $value) = each %{ $got->{column_values} }) {
98        $got_values{$field} = $value if defined $value;
99    }
100    while (my ($field, $value) = each %{ $expected->{column_values} }) {
101        $expected_values{$field} = $value if defined $value;
102    }
103
104    if (!eq_deeply(\%got_values, \%expected_values)) {
105        # 'Test' again so the helpful failure diagnostics are output.
106        is_deeply(\%got_values, \%expected_values, $name);
107        return;
108    }
109
110    return 1;
111}
112
113sub is_object {
114    my ($got, $expected, $name) = @_;
115    pass($name) if _is_object(@_);
116}
117
118sub are_objects {
119    my ($got, $expected, $name) = @_;
120
121    my $count = scalar @$expected;
122    if ($count != scalar @$got) {
123        fail($name);
124        diag('    got ', scalar(@$got), ' objects but expected ', $count);
125        return;
126    }
127
128    for my $i (0..$count-1) {
129        return if !_is_object($$got[$i], $$expected[$i], "$name (#$i)");
130    }
131    pass($name);
132}
133
134is_object(scalar Foo->load(1), $foo[0], 'Foo #1 by id is Foo #1');
135is_object(scalar Foo->load({ id => 1 }), $foo[0], 'Foo #1 by id hash is Foo #1');
136is_object(scalar Foo->load({ id => 1, name => 'foo' }), $foo[0], 'Foo #1 by id-name hash is Foo #1');
137is_object(scalar Foo->load({ name => 'foo' }), $foo[0], 'Foo #1 by name hash is Foo #1');
138is_object(scalar Foo->load({ created_on => $foo[0]->created_on }), $foo[0], 'Foo #1 by created_on hash is Foo #1');
139is_object(scalar Foo->load({ status => 2 }), $foo[0], 'Foo #1 by status hash is Foo #1');
140
141##     Change column value, save, try to load using old value (fail?),
142##     then load again using new value
143$foo[0]->status(0);
144ok($foo[0]->save, 'Foo #1 saved with new status (0)');
145$tmp = Foo->load({ status => 2 });
146ok(!$tmp, 'Foo #1 no longer loads with old status (2)');
147$tmp = Foo->load({ status => 0 });
148is_object($tmp, $foo[0], 'Foo #1 by new status (0) is Foo #1');
149
150## Create a new object so we can do range and last/first lookups.
151## Sleep first so that they get different created_on timestamps.
152sleep(3);
153
154## Create new object for iterator testing
155$foo[1] = Foo->new;
156$foo[1]->name('baz');
157$foo[1]->text('quux');
158$foo[1]->status(1);
159$foo[1]->save;
160
161## TEST LOADING IN VARIOUS WAYS
162
163## Load all objects via iterator
164my $iter = Foo->load_iter(undef, { sort => 'created_on', direction => 'ascend' });
165isa_ok($iter, 'CODE', "Iterator for all Foos");
166ok($tmp = $iter->(), 'Iterator for our two Foos had one object');
167is_object($tmp, $foo[0], "All Foo iterator's first Foo is Foo #1");
168ok($tmp = $iter->(), 'Iterator for our two Foos had two objects');
169is_object($tmp, $foo[1], "All Foo iterator's second Foo is Foo #2");
170ok(!$iter->(), 'Iterator for our two Foos did not have a third object');
171
172## Load all objects with status == 1 via iterator
173$iter = Foo->load_iter({ status => 1 });
174isa_ok($iter, 'CODE', "Iterator for status=1 Foos");
175ok($tmp = $iter->(), 'Iterator for our status=1 Foos had one object');
176is_object($tmp, $foo[1], "Status=1 Foo iterator's first Foo is Foo #2");
177ok(!$iter->(), "Iterator for our status=1 Foos did not have a second object");
178
179## Load using non-existent ID (should fail)
180$tmp = Foo->load(3);
181ok(!$tmp, 'There is no Foo #3');
182
183## Load using descending sort (newest)
184$tmp = Foo->load(undef, {
185    sort => 'created_on',
186    direction => 'descend',
187    limit => 1 });
188is_object($tmp, $foo[1], 'Newest Foo is Foo #2');
189
190## Load using ascending sort (oldest)
191$tmp = Foo->load(undef, {
192    sort => 'created_on',
193    direction => 'ascend',
194    limit => 1 });
195is_object($tmp, $foo[0], 'Oldest Foo is Foo #1');
196
197## Load using descending sort with limit = 2
198@tmp = Foo->load(undef, {
199    sort => 'created_on',
200    direction => 'descend',
201    limit => 2 });
202are_objects(\@tmp, [ reverse @foo ], 'Two Foos newest-first load() finds Foos #2 and #1');
203
204## Load using descending sort by created_on, no limit
205@tmp = Foo->load(undef, {
206    sort => 'created_on',
207    direction => 'descend' });
208are_objects(\@tmp, [ reverse @foo ], 'All Foos newest-first load() finds Foos #2 and #1');
209
210## Load using ascending sort by status, no limit
211@tmp = Foo->load(undef, { sort => 'status', });
212are_objects(\@tmp, \@foo, 'All Foos lowest-status-first load() finds Foos #1 and #2');
213
214## Load using 'last' where status == 0
215$tmp = Foo->load({ status => 0 }, {
216    sort => 'created_on',
217    direction => 'descend',
218    limit => 1 });
219is_object($tmp, $foo[0], 'Newest status=0 Foo is Foo #1');
220
221## Load using range search, one less than foo[1]->created_on and newer
222$tmp = Foo->load(
223    { created_on => [ $foo[1]->column('created_on')-1 ] },
224    { range => { created_on => 1 } });
225is_object($tmp, $foo[1], 'Foo from open-ended date range before Foo #2 is Foo #2');
226
227## Load using EXCLUSIVE range search, up through the momment $foo[1] created
228$tmp = Foo->load(
229    { created_on => [ $foo[1]->column('created_on')-1, 
230                      $foo[1]->column('created_on') ] },
231    { range => { created_on => 1 } });
232ok(!$tmp, "Exclusive date range load() ending at Foo #1's date found no Foos");
233
234$tmp = Foo->load(
235    { created_on => [ $foo[1]->column('created_on'), 
236                      $foo[1]->column('created_on')+1 ] },
237    { range => { created_on => 1 } });
238ok(!$tmp, "Exclusive date range load() starting at Foo #1's date found no Foos");
239
240## Load using INCLUSIVE range search, up through the momment $foo[1] created
241$tmp = Foo->load(
242    { created_on => [ $foo[1]->column('created_on')-1, 
243                      $foo[1]->column('created_on') ] },
244    { range_incl => { created_on => 1 } });
245ok($tmp, 'Loaded an object based on range_incl (ts-1 to ts)');
246is_object($tmp, $foo[1], "Foo from inclusive date-range load() ending at Foo #1's date is Foo #2");
247
248$tmp = Foo->load(
249    { created_on => [ $foo[1]->column('created_on'), 
250                      $foo[1]->column('created_on')+1 ] },
251    { range_incl => { created_on => 1 } });
252ok($tmp, 'Loaded an object based on range_incl (ts to ts+1)');
253is_object($tmp, $foo[1], "Foo from inclusive date-range load() starting at Foo #1's date is Foo #2");
254
255## Check that range searches return nothing when nothing is in the range.
256$tmp = Foo->load( { created_on => [ undef, '19690101000000' ] },
257                  { range => { created_on => 1 } });
258ok(!$tmp, 'Prehistoric date range load() found no Foos');
259
260## Range search, all items with created_on less than foo[1]->created_on
261$tmp = Foo->load(
262    { created_on => [ undef, $foo[1]->column('created_on')-1 ] },
263    { range => { created_on => 1 } });
264is_object($tmp, $foo[0], "Foo from exclusive open-started date-range load() ending before Foo #1 is Foo #1");
265
266## Get count of objects
267is(Foo->count(), 2, 'Count of all Foos finds both');
268is(Foo->count({ status => 0 }), 1, 'Count of all status=0 Foos finds all one');
269my $ranged_count = Foo->count(
270    { created_on => [ $foo[1]->column('created_on')-1 ] },
271    { range => { created_on => 1 } }
272);
273is($ranged_count, 1, 'Count of all Foos in open-ended date range starting before Foo #1 finds all one');
274
275## Update status for later tests.
276$foo[0]->status(2);
277$foo[0]->save;
278
279## Test start_val loads.
280## Given the first Foo object, should load the "next" one
281## (the one with a larger created_on time)
282$tmp = Foo->load(undef, {
283    limit => 1,
284    sort => 'created_on',
285    direction => 'ascend',
286    start_val => $foo[0]->created_on });
287is_object($tmp, $foo[1], 'Next newer Foo after Foo #1 is Foo #2');
288
289## Given the first Foo object, try to load the "previous" one
290## (the one with a smaller created_on time). This should fail.
291$tmp = Foo->load(undef, {
292    limit => 1,
293    sort => 'created_on',
294    direction => 'descend',
295    start_val => $foo[0]->created_on });
296ok(!$tmp, 'Search for next older Foo before Foo #1 found none');
297
298## Given the second Foo object, try to load the "previous" one
299## (the one with a smaller created_on time). This should work.
300$tmp = Foo->load(undef, {
301    limit => 1,
302    sort => 'created_on',
303    direction => 'descend',
304    start_val => $foo[1]->created_on });
305is_object($tmp, $foo[0], 'Next older Foo before Foo #2 is Foo #1');
306
307## Given the second Foo object, try to load the "next" one
308## (the one with a larger created_on time). This should fail.
309$tmp = Foo->load(undef, {
310    limit => 1,
311    sort => 'created_on',
312    direction => 'ascend',
313    start_val => $foo[1]->created_on });
314ok(!$tmp, 'Search for next newer Foo after Foo #2 found none');
315
316## Now, given the second Foo object's created_on - 1, try to
317## load the "previous" one. This should work.
318$tmp = Foo->load(undef, {
319    limit => 1,
320    sort => 'created_on',
321    direction => 'descend',
322    start_val => $foo[1]->created_on-1 });
323is_object($tmp, $foo[0], 'Next older Foo before just before Foo #2 is Foo #1');
324
325## Now, given the second Foo object's created_on - 1, try to
326## load the "next" one. This should work.
327$tmp = Foo->load(undef, {
328    limit => 1,
329    sort => 'created_on',
330    direction => 'ascend',
331    start_val => $foo[1]->created_on-1 });
332is_object($tmp, $foo[1], 'Next newer Foo after just before Foo #2 is Foo #2');
333
334## Override created_on timestamp, make sure it works
335my $ts = substr($foo[1]->created_on, 0, -4) . '0000';
336$foo[1]->created_on($ts);
337$foo[1]->save;
338
339@tmp = Foo->load(undef, {
340    sort => 'created_on',
341    direction => 'descend',
342    limit => 2 });
343are_objects(\@tmp, \@foo, 'Time-traveled Foos newest-first are Foos #1 and #2');
344
345## Test limit of 2 with direction descend, but without
346## a sort option. This should sort by the most recently-added
347## records, ie. sorted by ID, basically.
348@tmp = Foo->load(undef, {
349    direction => 'descend',
350    limit => 2 });
351are_objects(\@tmp, [ reverse @foo ], 'Foos highest-id-first are Foos #2 and #1');
352
353## Test loading using offset.
354## Should load the second Foo object.
355$tmp = Foo->load(undef, {
356    direction => 'descend',
357    sort => 'created_on',
358    limit => 1,
359    offset => 1 });
360is_object($tmp, $foo[1], 'Second newest Foo is Foo #2');
361
362## We only have 2 Foo objects, so this should load
363## only the second Foo object (because offset is 1).
364@tmp = Foo->load(undef, {
365    direction => 'descend',
366    sort => 'created_on',
367    limit => 2,
368    offset => 1 });
369are_objects(\@tmp, [ $foo[1] ], 'Second and third newest Foos is just Foo #2');
370
371## Should load the first Foo object (ascend with offset of 1).
372$tmp = Foo->load(undef, {
373    direction => 'ascend',
374    sort => 'created_on',
375    limit => 1,
376    offset => 1 });
377is_object($tmp, $foo[0], 'Second oldest Foo is Foo #1');
378
379## Now test join loads.
380## First we need to create a couple of Bar objects.
381$bar[0] = Bar->new;
382$bar[0]->foo_id($foo[1]->id);
383$bar[0]->name('bar0');
384$bar[0]->status(0);
385ok($bar[0]->save, 'saved');
386sleep(2);  ## Sleep to ensure created_on timestamps are unique
387
388$bar[1] = Bar->new;
389$bar[1]->foo_id($foo[1]->id);
390$bar[1]->name('bar1');
391$bar[1]->status(1);
392ok($bar[1]->save, 'saved');
393sleep(2);  ## Sleep to ensure created_on timestamps are unique
394
395$bar[2] = Bar->new;
396$bar[2]->foo_id($foo[0]->id);
397$bar[2]->name('bar2');
398$bar[2]->status(0);
399ok($bar[2]->save, 'saved');
400sleep(2);  ## Sleep to ensure created_on timestamps are unique
401
402# legacy way of specifying sort direction
403my $cgb_iter = Bar->count_group_by({
404        status => '0',
405    }, {
406        group => [ 'foo_id' ],
407        sort => 'foo_id desc',
408    });
409my ($count, $bfid, $month);
410isa_ok($cgb_iter, 'CODE');
411ok(($count, $bfid) = $cgb_iter->(), 'set');
412is($bfid, $bar[1]->id, 'id');
413is($count, 1, 'count4');
414ok(($count, $bfid) = $cgb_iter->(), 'set');
415is($bfid, $bar[0]->id, 'id');
416is($count, 1, 'count5');
417ok(!$cgb_iter->(), 'no $iter');
418
419# new way of specifying sort direction
420my $cgb_iter2 = Bar->count_group_by({
421        status => '0',
422    }, {
423        group => [ 'foo_id' ],
424        sort => 'foo_id',
425        direction => 'descend'
426    });
427
428isa_ok($cgb_iter2, 'CODE');
429ok(($count, $bfid) = $cgb_iter2->(), 'set');
430is($bfid, $bar[1]->id, 'id');
431is($count, 1, 'count4');
432ok(($count, $bfid) = $cgb_iter2->(), 'set');
433is($bfid, $bar[0]->id, 'id');
434is($count, 1, 'count5');
435ok(!$cgb_iter2->(), 'no $iter');
436
437# legacy way of specifying sort direction
438my $cgb_iter3 = Bar->count_group_by(undef, {
439        group => [ 'extract(month from created_on)' ],
440        sort => 'extract(month from created_on) desc',
441    });
442isa_ok($cgb_iter3, 'CODE');
443ok(($count, $month) = $cgb_iter3->(), 'set');
444use POSIX qw(strftime);
445is(int($month), int(strftime("%m", localtime)), 'month');
446is($count, 3, 'count6');
447ok(!$cgb_iter3->(), 'no $iter');
448
449# new way of specifying sort direction
450my $cgb_iter4 = Bar->count_group_by(undef, {
451        group => [ 'extract(month from created_on)' ],
452        sort => [{ column => 'extract(month from created_on)',
453            desc => 'desc' }]
454    });
455isa_ok($cgb_iter4, 'CODE');
456ok(($count, $month) = $cgb_iter4->(), 'set');
457is(int($month), int(strftime("%m", localtime)), 'month');
458is($count, 3, 'count6');
459ok(!$cgb_iter4->(), 'no $iter');
460
461## Get a count of all Foo objects in order of most recently
462## created Bar object. No uniqueness requirement. This tests
463## the on_load_complete temporary table stuff with count.
464
465is(Foo->count(undef,
466    { join => [ 'Bar', 'foo_id',
467                undef,
468                { unique => 1,
469                  sort => 'created_on',
470                  direction => 'descend', } ] }), 2, 'count7');
471
472## Now load all Foo objects in order of most recently
473## created Bar object. Make sure they are unique.
474@tmp = Foo->load(undef,
475    { join => [ 'Bar', 'foo_id',
476                undef,
477                { sort => 'created_on',
478                  direction => 'descend',
479                  unique => 1 } ] });
480is(@tmp, 2, 'array length 2');
481is($tmp[0]->id, $foo[0]->id, 'id');
482is($tmp[1]->id, $foo[1]->id, 'id');
483
484## Load all Foo objects in order of most recently
485## created Bar object. No uniqueness requirement.
486@tmp = Foo->load(undef,
487    { join => [ 'Bar', 'foo_id',
488                undef,
489                { sort => 'created_on',
490                  direction => 'descend', } ] });
491is(@tmp, 3, 'array length 3');
492is($tmp[0]->id, $foo[0]->id, 'id');
493is($tmp[1]->id, $foo[1]->id, 'id');
494is($tmp[2]->id, $foo[1]->id, 'id');
495
496## Load last 1 Foo object in order of most recently
497## created Bar object.
498@tmp = Foo->load(undef,
499    { join => [ 'Bar', 'foo_id',
500                undef,
501                { sort => 'created_on',
502                  direction => 'descend',
503                  unique => 1,
504                  limit => 1, } ] });
505is(@tmp, 1, 'array length 1 with join');
506is($tmp[0]->id, $foo[0]->id, 'id');
507
508## Load all Foo objects where Bar.name = 'bar0'
509@tmp = Foo->load(undef,
510    { join => [ 'Bar', 'foo_id',
511                { name => 'bar0' },
512                { sort => 'created_on',
513                  direction => 'descend',
514                  unique => 1, } ] });
515is(@tmp, 1, 'array length 1 with join, limit by name');
516is($tmp[0]->id, $foo[1]->id, 'id');
517
518## foo[1] is older than foo[0] because we overrode the timestamp,
519## so this should load foo[0]
520@tmp = Foo->load(undef,
521    { sort => 'created_on', direction => 'descend', limit => 1,
522    join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
523is(@tmp, 1, 'array length 1');
524is($tmp[0]->id, $foo[0]->id, 'id');
525
526## This is the same join as the last one, but without the limit--so
527## we should get both Foo objects this time, in descending order.
528@tmp = Foo->load(undef,
529    { sort => 'created_on', direction => 'descend',
530      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
531is(@tmp, 2, 'array length 2');
532is($tmp[0]->id, $foo[0]->id, 'id');
533is($tmp[1]->id, $foo[1]->id, 'id');
534
535## Filter join results by providing a value for 'status'; only Foo[0]
536## has a 'status' == 2, so only that record should be returned.
537@tmp = Foo->load({ status => 2 },
538    { sort => 'created_on', direction => 'descend',
539      join => [ 'Bar', 'foo_id', { status => 0 }, { unique => 1 } ] });
540is(@tmp, 1, 'array length 1');
541is($tmp[0]->id, $foo[0]->id, 'id');
542
543# Join across a column.
544@tmp = Foo->load({},
545    { sort => 'created_on', direction => 'descend',
546      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
547are_objects(\@tmp, \@foo, 'Foos loaded by explicit join across columns');
548
549@tmp = Foo->load({ status => 2 },
550    { sort => 'created_on', direction => 'descend',
551      join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 0 }, { unique => 1 } ] });
552are_objects(\@tmp, [ $foo[0] ], 'Foos of status=2 loaded by explicit join across columns');
553
554## TEST EXISTS METHOD
555ok($foo[0]->exists, 'exists');
556$tmp = Foo->new;
557ok(!$tmp->exists, 'does not exist');
558$tmp->id(5);
559ok(!$tmp->exists, 'does not exist');
560
561## Change foo[1]->status so that its value is unique (for index)
562$foo[1]->status(5);
563ok($foo[1]->save, 'saved');
564ok(Foo->load({ status => 5 }), 'loaded' );
565
566## Test remove
567ok($foo[1]->remove, 'removed');
568ok(! Foo->load(2), 'not loaded');
569ok(! Foo->load({ status => 5 }), 'not loaded');
570ok(! Foo->load({ name => $foo[1]->name }), 'not loaded');
571ok(! Foo->load({ created_on => $foo[1]->created_on }), 'not loaded');
572
573## Test methods:
574##     * properties
575my $props1 = Foo->properties;
576is($props1->{audit}, 1, 'audit');
577is(scalar keys %{ $props1->{indexes} }, 3, 'indexes');
578is($props1->{primary_key}, 'id', 'id');
579is(scalar @{ $props1->{columns} }, 9, 'columns');
580my $props2 = $foo[0]->properties;
581is($props1, $props2, "$props1 is $props2");  ## Same address, because same hashref
582
583##     * column_names
584my $cols = $foo[0]->column_names;
585isa_ok($cols, 'ARRAY');
586my %cols = map { $_ => 1 } @$cols;
587for (qw(id name status text data created_on created_by modified_on modified_by)) {
588    ok($cols{$_}, 'cols');
589}
590
591##     * column_values
592my $vals = $foo[0]->column_values;
593isa_ok($vals, 'HASH');
594is($vals->{id}, $foo[0]->id, 'id');
595is($vals->{name}, $foo[0]->name, 'name');
596is($vals->{status}, $foo[0]->status, 'status');
597is($vals->{text}, $foo[0]->text, 'text');
598is($vals->{created_on}, $foo[0]->created_on, 'created_on');
599is($vals->{created_by}, $foo[0]->created_by, 'created_by');
600is($vals->{modified_on}, $foo[0]->modified_on, 'modified_on');
601is($vals->{modified_by}, $foo[0]->modified_by, 'modified_by');
602
603##     * set_values
604$vals = {
605    id => 5,
606    name => 'baz',
607    status => 7,
608    text => 'quux',
609    created_on => 13209,
610    created_by => 'bar',
611    #modified_on => 39023, modified_by auto-set modified_on in our new code.
612    modified_by => 'foo',
613};
614$foo[0]->set_values($vals);
615for my $col (keys %$vals) {
616    is($vals->{$col}, $foo[0]->column($col), $col);
617}
618
619##     * binary data
620
621my $binmonster = Foo->new;
622
623$vals = {
624    funky => "yes",
625    monkey => "no",
626};
627
628require MT::Serialize;
629my $srlzr = MT::Serialize->new('MT');
630$binmonster->data($srlzr->serialize(\$vals));
631my $x = $binmonster->save();
632warn 'Failed binary data test: ' . $binmonster->errstr() unless $x;
633ok($x, 'saved');
634ok($binmonster->id, 'id');
635Foo->driver->clear_cache if Foo->driver->can('clear_cache');
636my $chk = Foo->load($binmonster->id);
637if ($chk) {
638    my $chk_data = $chk->data;
639    my $chk_vals = $srlzr->unserialize($chk_data);
640    foreach (keys %$vals) {
641        is($$chk_vals->{$_}, $vals->{$_}, $_);
642    }
643} else {
644    foreach (keys %$vals) {
645        ok(0, $_);
646    }
647}
648
649##     * datasource
650is($foo[0]->table_name, 'mt_' . $foo[0]->datasource, 'datasource');
651
652##     * clone
653my $clone = $foo[0]->clone_all;
654for my $col (@$cols) {
655    is($clone->column($col), $foo[0]->column($col), $col);
656}
657
658## Sleep first so that they get different created_on timestamps.
659sleep(3);
660
661Foo->set_by_key({name => "this"});
662my $obj = Foo->load({name => "this"});
663isa_ok($obj, 'Foo');
664
665Foo->set_by_key({name => "this"}, {status => 42});
666$obj = Foo->load({name => "this"});
667is($obj && $obj->status, 42, 'status');
668
669Foo->set_by_key({name => "this"}, {status => 47});
670$obj = Foo->load({name => "this"});
671is($obj && $obj->status, 47, 'status');
672
673Foo->set_by_key({name => "this", status => 47}, {text => "spiffy"});
674$obj = Foo->load({name => "this", status => 47});
675is($obj && $obj->text, 'spiffy', 'text');
676
677sleep(3);
678
679Foo->set_by_key({name => "that"}, {text => "Once"});
680$obj = Foo->load({name => "that"});
681is($obj && $obj->text, 'Once', 'text');
682
683Foo->driver->clear_cache if Foo->driver->can('clear_cache');
684## Load use direct set of values for non-PK column
685@tmp = Foo->load({ name => [qw(foo this)] });
686@tmp = sort {$a->name cmp $b->name} @tmp;
687is(@tmp, 2, 'array length 2');
688
689is(Foo->count(), 4, 'check number of Foos');
690
691## check offsets without limits
692## Should load the third and fourth Foo objects.
693my $foo4 = Foo->load({name => "this"});
694my $foo5 = Foo->load({name => "that"});
695my $foo1 = Foo->load(undef, { 'sort' => 'created_on', 'direction' => 'ascend' });
696my @offs = Foo->load(undef, {
697    direction => 'ascend',
698    sort => 'created_on',
699    offset => 2 });
700is(@offs, 2, 'array length 2');
701isa_ok($offs[0], 'Foo');
702is($offs[0]->id, $foo4->id, 'id');
703isa_ok($offs[1], 'Foo');
704is($offs[1]->id, $foo5->id, 'id');
705
706## Should load the third and fourth Foo objects.
707@offs = Foo->load(undef, {
708    direction => 'descend',
709    sort => 'created_on',
710    offset => 1 });
711is(@offs, 3, 'array length 3');
712isa_ok($offs[0], 'Foo');
713is($offs[0]->id, $foo4->id, 'id');
714isa_ok($offs[1], 'Foo');
715is($offs[1]->id, $binmonster->id, 'id');
716isa_ok($offs[2], 'Foo');
717is($offs[2]->id, $foo1->id, 'id');
718
719#sum_group_by
720my $cnt = 0;
721my @data = Foo->load(undef, {direction => 'ascend'});
722my @foos;
723foreach my $f (@data) {
724    $f->status($cnt + 1);
725    $f->save;
726    unshift @foos, $f;
727    $cnt++;
728}
729
730my $sgb = Foo->sum_group_by(undef, { sum => 'status', group => ['id'], direction => 'ascend' });
731while (my ($status, $id2) = $sgb->()) {
732    my $f = pop @foos;
733    is($f->id, $id2, 'id sgb');
734    is($f->status, $status, 'status sgb');
735}
736
737SKIP: {
738    skip(1, '$tmp[0] undefined') unless $tmp[0];
739    ok($tmp[0] && ($tmp[0]->name eq 'foo'), 'name')
740}
741SKIP: {
742    skip(1, '$tmp[1] undefined') unless $tmp[1];
743    ok($tmp[1] && ($tmp[1]->name eq 'this'), 'name');
744}
745
746# -or
747my $newdata = Foo->new;
748$newdata->status(11);
749$newdata->name('Apple');
750$newdata->text('MacBook');
751$newdata->save;
752$newdata = Foo->new;
753$newdata->status(12);
754$newdata->name('Linux');
755$newdata->text('Ubuntu');
756$newdata->save;
757$newdata = Foo->new;
758$newdata->status(13);
759$newdata->name('Microsoft');
760$newdata->text('Vista');
761$newdata->save;
762$newdata = Foo->new;
763$newdata->status(10);
764$newdata->name('Microsoft');
765$newdata->text('XP');
766$newdata->save;
767$newdata = Foo->new;
768$newdata->status(10);
769$newdata->name('Apple');
770$newdata->text('iBook');
771$newdata->save;
772
773$count = Foo->count( [{status => 10}, -or => {name => 'Apple'}] );
774# ==> select count(*) from mt_foo where foo_status = 10 or foo_name = 'Apple'
775is($count, 3, '-or count');
776
777$count = Foo->count( [ { status => { '<=' => 20 }, name => 'Apple' }, -and_not => { status => 11 } ] );
778# ==> select count(*) from mt_foo where (foo_status <= 20 and foo_name = 'Apple') and not (foo_status = 11)
779is($count, 1, '-and_not count');
780
781$count = Foo->count( [
782    { status => 10 },
783    -or => { name => 'Apple' },
784    -or => { name => { like => '%nux' } },
785] );
786# ==> select count(*) from mt_foo where (foo_status = 10) or (foo_name = 'Apple') or (foo_name like '%nux')
787# (selects Apple+MacBook, Apple+iBook, Microsoft+XP, Linux+Ubuntu)
788is($count, 4, '-or count, 3 clauses');
789
790# alias support
791my $vista = Foo->load({name=>'Microsoft', status=>13});
792my $newbar = Bar->new;
793$newbar->foo_id($vista->id);
794$newbar->name('Silverlight');
795$newbar->status(2);
796$newbar->save;
797sleep(3);
798$newbar = Bar->new;
799$newbar->foo_id($vista->id);
800$newbar->name('IronPython');
801$newbar->status(3);
802$newbar->save;
803sleep(3);
804my $mb = Foo->load({name=>'Apple', status=>11});
805$newbar = Bar->new;
806$newbar->foo_id($mb->id);
807$newbar->name('IronRuby');
808$newbar->status(0);
809$newbar->save;
810
811# select * from foo, bar bar1, bar bar2
812# where bar1.bar_foo_id = foo_id
813# and bar2.bar_foo_id = bar1.bar_foo_id
814# and bar1.status = 2
815# and bar2.status = 3
816my @a_foos = Foo->load(
817    undef,
818    { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
819        { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 3 },
820            { alias => 'bar2' } ],
821          alias => 'bar1'
822        }
823      ],
824      sort => 'created_on', direction => 'descend',
825    }
826);
827is(scalar(@a_foos), 1, 'join the same table using alias 1');
828is($a_foos[0]->id, $vista->id, 'join the same table using alias 2');
829
830@a_foos = Foo->load(
831    undef,
832    { join => [ 'Bar', undef, { foo_id => \'= foo_id', status => 2 },
833        { join => [ 'Bar', undef, { foo_id => \'= bar1.bar_foo_id', status => 0 },
834            { alias => 'bar2' } ],
835          alias => 'bar1'
836        }
837      ],
838      sort => 'created_on', direction => 'descend',
839    }
840);
841is(scalar(@a_foos), 0, 'join the same table using alias 3');
842 
843
8441;
Note: See TracBrowser for help on using the browser.