File Coverage

blib/lib/MongoX/Helper.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package MongoX::Helper;
2             # ABSTRACT: Helper to invoke MongoDB commands handy.
3 1     1   8385 use strict;
  1         2  
  1         37  
4 1     1   6 use warnings;
  1         1  
  1         31  
5              
6 1     1   6 use Carp 'croak';
  1         2  
  1         73  
7 1     1   881 use Tie::IxHash;
  1         19278  
  1         43  
8 1     1   15 use Digest::MD5 qw(md5_hex);
  1         3  
  1         106  
9 1     1   66 use MongoX::Context;
  0            
  0            
10             use boolean;
11             use Exporter qw( import );
12              
13             # admin only commands
14             my @TAG_ADMIN = qw(
15             admin_fsync_lock
16             admin_unlock
17             admin_server_status
18             admin_shutdown_server
19             admin_build_info
20             admin_get_cmd_line_opts
21             admin_log_rotate
22             admin_logout
23             admin_resync
24             admin_sharding_state
25             admin_unset_sharding
26             admin_diag_logging
27             );
28              
29             my @TAG_COMMON = qw(
30             db_list_commands
31             db_stats
32             db_is_master
33             db_eval
34             db_add_user
35             db_remove_user
36             db_auth
37             db_create_collection
38             db_convert_to_capped
39             db_ping
40             db_repair_database
41             db_run_command
42             db_current_op
43             db_re_index
44             db_filemd5
45             db_map_reduce
46             db_distinct
47             db_group
48             db_insert
49             db_count
50             db_remove
51             db_update
52             db_update_set
53             db_find_one
54              
55             db_find
56             db_find_all
57             db_find_and_modify
58             db_increment
59             db_ensure_index
60             db_drop_index
61             db_drop_indexes
62             db_get_indexes
63              
64             db_find_by_id
65             db_remove_by_id
66             );
67             # TODO: Replica Set commands
68             my @TAG_RS = qw(
69             rs_freeze
70             rs_get_status
71             rs_initiate
72             rs_reconfig
73             rs_step_down
74             );
75              
76             our %EXPORT_TAGS = (
77             admin => [@TAG_ADMIN],
78             all => [ @TAG_COMMON, @TAG_ADMIN ]
79             );
80             our @EXPORT_OK = (@TAG_ADMIN,@TAG_COMMON);
81             our @EXPORT = @TAG_COMMON;
82              
83              
84             sub AUTOLOAD {
85             shift;
86             our $AUTOLOAD;
87             my $cmd_name = $AUTOLOAD;
88             my $admin_only = 0;
89              
90             $cmd_name =~ s/.*:://;
91             if ($cmd_name =~ m/^db_/ ) {
92             $cmd_name =~ s/^db_//;
93             }
94             elsif ($cmd_name =~ m/^admin_/ ) {
95             $cmd_name =~ s/^admin_//;
96             $admin_only = 1;
97             }
98              
99             $cmd_name = lcfirst(join('', map{ ucfirst $_ } split(/(?<=[A-Za-z])_(?=[A-Za-z])|\b/, $cmd_name)));
100              
101             # print '$cmd_name:',$cmd_name,"\n";
102             {
103             no strict 'refs';
104             if ($admin_only) {
105             *$AUTOLOAD = sub {
106             my $c = Tie::IxHash->new($cmd_name => 1,@_);
107             return __PACKAGE__->_admin_db->run_command($c);
108             };
109             }
110             else {
111             *$AUTOLOAD = sub {
112             my $c = Tie::IxHash->new($cmd_name => 1,@_);
113             # $c->Push(@_);
114             return __PACKAGE__->_db->run_command($c);
115             };
116             }
117             }
118             goto &$AUTOLOAD;
119             }
120              
121              
122             # private
123             sub _db { MongoX::Context::context_db }
124              
125             sub _connection { MongoX::Context::context_connection }
126              
127             sub _collection { MongoX::Context::context_collection }
128              
129             sub _admin_db { MongoX::Context::context_connection->get_database('admin') }
130              
131             # ====================admin only commands section
132              
133              
134             sub admin_fsync_lock {
135             my $result = _admin_db->run_command(Tie::IxHash->new('fsync' => 1, 'lock' => 1));
136             return $result->{ok} ? 1 : 0;
137             }
138              
139              
140             sub admin_unlock {
141             my $result = _admin_db->get_collection('$cmd.sys.unlock')->find_one();
142             return $result->{ok} ? 1 : 0;
143             }
144              
145              
146              
147              
148              
149             sub admin_shutdown_server {
150             eval { _admin_db->run_command({ shutdown => 1 }) };
151             # hack, todo
152             return 1 if $@ =~ m/couldn't connect to server/;
153             }
154              
155              
156              
157             # ====================common commands section
158              
159              
160             sub db_stats { db_run_command({dbstats => 1 }) }
161              
162              
163             sub db_is_master {
164             my $result = db_run_command({isMaster => 1});
165             return unless ref $result;
166             return $result->{ismaster}?1:0;
167             }
168              
169              
170              
171             sub db_eval { _db->eval(@_) }
172              
173              
174             sub db_current_op {
175             _connection->get_database('local')->get_collection('$cmd.sys.inprog')->find_one();
176             }
177              
178              
179             sub db_filemd5 {
180             my $result = db_run_command({filemd5 => shift });
181             return unless $result;
182             return $result->{md5};
183             }
184              
185              
186              
187             sub db_re_index {
188             my ($col) = @_;
189             $col ||= _collection->name;
190             my $result = db_run_command({reIndex => $col});
191             return $result->{ok}?1:0;
192             }
193              
194              
195             sub db_distinct {
196             my ($key,$query) = @_;
197             my $result = db_run_command(Tie::IxHash->new(
198             distinct => _collection->name,
199             key => $key,
200             query => ref $query ? $query : {},
201             ));
202             return unless ref $result;
203             return $result->{values};
204             }
205              
206              
207             sub db_group {
208             my ($args) = @_;
209             my $group = { ns => _collection->name };
210             $group->{cond} = ref $args->{condition} ? $args->{condition} : {};
211             $group->{'$reduce'} = $args->{reduce};
212             $group->{key} = $args->{key} if $args->{key};
213             $group->{'$keyf'} = $args->{keyf} if $args->{keyf};
214             $group->{initial} = ref $args->{initial} ? $args->{initial} : {};
215             $group->{finalize} = $args->{finalize} if $args->{finalize};
216             my $result = db_run_command({ group => $group });
217             return unless ref $result;
218             return $result->{retval};
219             }
220              
221              
222             sub db_map_reduce {
223             my ($opts) = @_;
224             my $cmd = Tie::IxHash->new('mapreduce' => _collection->name,'map' => $opts->{map},'reduce' => $opts->{reduce});
225             $cmd->Push(query => $opts->{query}) if exists $opts->{query};
226             $cmd->Push(sort => $opts->{sort}) if exists $opts->{sort};
227             $cmd->Push(limit => $opts->{limit}) if exists $opts->{limit};
228             $cmd->Push(out => $opts->{out}) if exists $opts->{out};
229             $cmd->Push(keeptemp => $opts->{keeptemp}? true:false ) if exists $opts->{keeptemp};
230             $cmd->Push(finalize => $opts->{finalize}) if exists $opts->{finalize};
231             $cmd->Push(scope => $opts->{scope}?true:false) if exists $opts->{scope};
232             my $result = db_run_command($cmd);
233             return unless ref $result;
234             return _db->get_collection($result->{result}) if $result->{ok};
235             }
236              
237              
238              
239             sub db_run_command { _db->run_command(@_) }
240              
241              
242             sub db_list_commands {
243             my $result = db_run_command { listCommands => 1};
244             return unless ref $result;
245             return $result->{commands};
246             }
247              
248              
249             sub db_add_user {
250             my ($username,$password,$readonly) = @_;
251             my $col = _db->get_collection('system.users');
252             my $user = $col->find_one({user => $username});
253             $user ||= { user => $username};
254             $user->{readOnly} = $readonly?true:false;
255             $user->{pwd} = md5_hex($username.':mongo:'.$password);
256             $col->save($user);
257             }
258              
259              
260              
261             sub db_remove_user { _db->get_collection('system.users')->remove({ user => shift }) }
262              
263              
264              
265             sub db_find_and_modify {
266             my ($options) = @_;
267              
268             my $cmd = Tie::IxHash->new(findandmodify => _collection->name );
269              
270             $cmd->Push(query => $options->{query} || {});
271             $cmd->Push(new => $options->{new} ? true : false ) if exists $options->{new};
272             $cmd->Push(remove => $options->{remove} ? true : false) if exists $options->{remove};
273             $cmd->Push(update => $options->{update}) if exists $options->{update};
274             $cmd->Push(sort => $options->{sort}) if exists $options->{sort};
275              
276             my $result;
277              
278             eval {
279             $result = db_run_command($cmd);
280             };
281             if ($@) {
282             croak $@;
283             }
284             unless (ref $result) {
285             if ($result eq 'No matching object found') {
286             return;
287             }
288             croak $result;
289             }
290             return $result->{value};
291             }
292              
293              
294              
295             sub db_auth {
296             my ($username,$password,$is_digest) = @_;
297             my $result = _connection->authenticate(_db->name,$username,$password,$is_digest);
298             return unless ref $result;
299             return $result->{ok};
300             }
301              
302              
303             sub db_convert_to_capped { db_run_command(Tie::IxHash->new('convertToCapped' => shift, 'size' => shift)) }
304              
305              
306             sub db_create_collection {
307             my ($name,$options) = @_;
308             $options = {} unless ref $options;
309             my $cmd = Tie::IxHash->new('create' => $name);
310             $cmd->Push('capped' => $options->{capped}) if exists $options->{capped};
311             $cmd->Push('size' => $options->{size}) if exists $options->{size};
312             $cmd->Push('max' => $options->{max}) if exists $options->{max};
313             db_run_command($cmd);
314             }
315              
316              
317              
318             sub db_insert {
319             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
320             _collection->insert(@_);
321             }
322              
323              
324              
325             sub db_find {
326             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
327             _collection->find(@_);
328             }
329              
330              
331             sub db_find_all {
332             my $cursor = db_find(@_);
333             return unless $cursor;
334             $cursor->all;
335             }
336              
337              
338             sub db_count {
339             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
340             _collection->count(@_);
341             }
342              
343              
344              
345             sub db_increment {
346             my ($query,$field_deltas,$options) = @_;
347             db_update($query, { '$inc' => $field_deltas }, $options);
348             }
349              
350              
351             sub db_update_set {
352             my ($query,$set_obj,$options) = @_;
353             $options ||= {};
354             db_update($query,{ '$set' => $set_obj },$options);
355             }
356              
357              
358             sub db_update {
359             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
360             _collection->update(@_);
361             }
362              
363              
364             sub db_remove {
365             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
366             _collection->remove(@_);
367             }
368              
369              
370             sub db_find_one {
371             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
372             _collection->find_one(@_);
373             }
374              
375              
376             sub db_ensure_index {
377             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
378             return _collection->ensure_index(@_);
379             }
380              
381              
382             sub db_drop_index {
383             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
384             return _collection->drop_index(@_);
385             }
386              
387              
388             sub db_drop_indexes {
389             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
390             return _collection->drop_indexes(@_);
391             }
392              
393              
394             sub db_get_indexes {
395             croak 'context_collection not defined,forget(use_collection?)' unless _collection;
396             return _collection->get_indexes;
397             }
398              
399              
400             sub db_find_by_id {
401             my ($id) = @_;
402             $id = MongoDB::OID->new(value => "$id") unless ref $id eq 'MongoDB::OID';
403             db_find_one {_id => $id};
404             }
405              
406             sub db_remove_by_id {
407             my ($id) = @_;
408             $id = MongoDB::OID->new(value => "$id") unless ref $id eq 'MongoDB::OID';
409             db_remove {_id => $id};
410             }
411              
412             1;
413              
414              
415              
416             =pod
417              
418             =head1 NAME
419              
420             MongoX::Helper - Helper to invoke MongoDB commands handy.
421              
422             =head1 VERSION
423              
424             version 0.05
425              
426             =head1 SYNOPSIS
427              
428             # default import common db_* helpers
429             use MongoX::Helper;
430              
431             # or admin only command
432             use MongoX::Helper ':admin';
433              
434             # explicit some command
435             use MongoX::Helper qw(db_count,db_find,admin_unlock,admin_shutdown_server);
436              
437             # or all commands
438             use MongoX::Helper ':all';
439              
440             =head1 DESCRIPTION
441              
442             =head1 METHODS
443              
444             =head2 admin_fsync_lock
445              
446             my $ok = admin_fsync_lock;
447              
448             call fsync_lock on current server.
449              
450             =head2 admin_unlock
451              
452             my $ok = admin_unlock;
453              
454             call unlock on current server.
455              
456             =head2 admin_server_status
457              
458             my $result = admin_server_status;
459             print 'server uptime:',$result->{uptime};
460              
461             Return current mongoDB server status.
462              
463             =head2 admin_shutdown_server
464              
465             admin_shutdown_server;
466              
467             Shutdown current mongodb server.
468              
469             =head2 admin_sharding_state
470              
471             $result = admin_sharding_state;
472              
473             Get sharding state.
474              
475             =head2 admin_diag_logging
476              
477             $result = admin_diag_logging;
478             print 'logging level:',$result->{was},"\n";
479              
480             Get diag logging level.
481              
482             =head2 db_stats
483              
484             my $stats_info = db_stats;
485              
486             Return current database stats information;
487              
488             =head2 db_is_master
489              
490             $ok = db_is_master;
491              
492             Return if current server is master.
493              
494             =head2 db_eval($code,$args?)
495              
496             my $result = db_eval 'function(x) { return "hello, "+x; }', ["world"];
497              
498             Evaluate a JavaScript expression on the Mongo server.
499              
500             =head2 db_current_op
501              
502             my $op = db_current_op;
503              
504             Return current operation in the db.
505              
506             =head2 db_filemd5($file_id)
507              
508             $md5_hex = db_filemd5 $file_id;
509              
510             return md5 hex value of the file.
511              
512             =head2 db_re_index($collection_name?)
513              
514             $ok = db_re_index;
515              
516             rebuild the collection indexes (default collection is context_collection).
517              
518             =head2 db_distinct
519              
520             $result = db_distinct;
521              
522             Performance a distinct query.
523              
524             =head2 db_group
525              
526             $result = db_group {
527             reduce => 'function(doc,prev){ prev[doc.name]++; }',
528             key => { key1 => 1,key2 => 1 },
529             initial => { counter => 0.0 }
530             };
531            
532             # or
533             $result = db_group {
534             reduce => 'function(doc,prev){ prev[doc.name]++; }',
535             keyf => 'function(doc) { return {"x" : doc.x};',
536             initial => {counter => 0.0}
537             };
538              
539             Returns an array of grouped items of current context collection. options:
540              
541             =over
542              
543             =item reduce
544              
545             The reduce function aggregates (reduces) the objects iterated. Typical operations of a reduce function
546             include summing and counting. reduce takes two arguments:
547             the current document being iterated over and the aggregation counter object.
548             In the example above, these arguments are named obj and prev.
549              
550             =item key
551              
552             Fields to group by.
553              
554             =item keyf?
555              
556             An optional function returning a "key object" to be used as the grouping key. Use this instead of key to specify a key that is not an existing member of the object (or, to access embedded members). Set in lieu of key.
557              
558             =item initial?
559              
560             initial value of the aggregation counter object.
561              
562             initial => { counter => 0.0 }
563              
564             B<WARNING: As a known bug, in initial, if you assign a zero numberic value to some attribute, you must defined zero as float format,
565             meant must be 0.0 but not 0, cause 0 will passed as boolean value, then you will got 'nan' value in retval.>
566              
567             =item finalize?
568              
569             An optional function to be run on each item in the result set just before the item is returned.
570             Can either modify the item (e.g., add an average field given a count and a total)
571             or return a replacement object (returning a new object with just _id and average fields).
572              
573             =back
574              
575             more about group, see: L<http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group>.
576              
577             =head2 db_map_reduce
578              
579             $result = db_map_reduce { map => 'javascript function',reduce => 'javascript function' };
580              
581             map, reduce, and finalize functions are written in JavaScript.
582              
583             valid options are:
584              
585             =over
586              
587             =item map => mapfunction
588              
589             =item reduce => reducefunction
590              
591             =item query => query filter object
592              
593             =item sort => sort the query. useful for optimization
594              
595             =item limit => number of objects to return from collection
596              
597             =item out => output-collection name
598              
599             =item keeptemp => boolean
600              
601             =item finalize => finalizefunction
602              
603             =item scope => object where fields go into javascript global scope
604              
605             =item verbose => boolean
606              
607             =back
608              
609             more about map/reduce, see: L<http://www.mongodb.org/display/DOCS/MapReduce>.
610              
611             =head2 db_run_command
612              
613             my $result = db_run_command {dbstats:1};
614              
615             Run the command on current database. shortcut of L<MongoDB::Database/run_command>.
616              
617             =head2 db_list_commands
618              
619             my $command_list = db_list_commands;
620             foreach my $cmd (keys %{$command_list}) {
621             say 'Command name:',$cmd,"\n";
622             say 'adminOnly:' => $cmd->{adminOnly};
623             say 'help:' => $cmd->{help};
624             say 'lockType:' => $cmd->{lockType};
625             say 'slaveOk:' => $cmd->{slaveOk};
626             }
627              
628             Get a hash reference of all db commands.
629              
630             =head2 db_add_user($username,$password,$readonly?)
631              
632             $ok = db_add_user('foo','12345');
633              
634             Add user into current database.
635              
636             =head2 db_remove_user($username)
637              
638             $ok = db_remove_user $username;
639              
640             Remove given user from current database.
641              
642             =head2 db_find_and_modify($options)
643              
644             my $next_val = db_find_and_modify {
645             query => { _id => 'foo'},
646             update => { '$inc' => { value => 1 } }
647             }
648            
649             # simply remove the object to be returned
650             my $obj = db_find_and_modify({
651             query => { _id => 10 },
652             remove => 1
653             });
654              
655             MongoDB 1.3+ supports a "find, modify, and return" command.
656             This command can be used to atomically modify a document (at most one) and return it.
657             B<Note:that the document returned will not include the modifications made on the update>.
658             The options can include 'sort' option which is useful when storing queue-like data.
659              
660             =head3 option parameters
661              
662             At least one of the update or remove parameters is required; the other arguments are optional.
663              
664             =over
665              
666             =item C<query>
667              
668             A query selector document for matching the desired document. default is {}.
669              
670             =item C<sort>
671              
672             if multiple docs match, choose the first one in the specified sort order as the object to manipulate. default is {}.
673              
674             =item C<remove => boolean>
675              
676             set to a true to remove the object before returning. default is false.
677              
678             =item C<update>
679              
680             a modifier object. default is undef.
681              
682             =item C<new => boolean>
683              
684             set to true if you want to return the modified object rather than the original. Ignored for remove. default is false.
685              
686             =back
687              
688             =head2 db_repair_database
689              
690             my $result = db_repair_database;
691             print 'ok:'.$result->{ok};
692              
693             Repair current database.
694              
695             =head2 db_auth($username,$password,$is_digest)
696              
697             $ok = db_auth 'pp', 'plain-text';
698              
699             Attempts to authenticate for use of the current database with $username and $password.
700             Shortcut of L<MongoDB::Connection/authenticate>.
701              
702             =head2 db_convert_to_capped($collection_name,$size)
703              
704             db_convert_to_capped 'common_collection1',1024*1024*10;
705              
706             Convert a normal collection to capped collection.
707              
708             =head2 db_create_collection($name,$options?)
709              
710             $result = db_create_collection 'foo',{ capped => 1 };
711              
712             Explicit create a special (capped) collection.
713              
714             =head2 db_insert(\%obj)
715              
716             db_insert {name => 'ns',workhard => 'mongox' };
717              
718             Implicit call C<context_collection->insert>.
719              
720             =head2 db_find($query?,$options?)
721              
722             $cursor = db_find {name => 'foo'};
723              
724             Implicit call C<context_collection->find>.
725              
726             =head2 db_find_all($query?,$options?)
727              
728             @result = db_find_all {age => 30},{limit => 20};
729              
730             Short of C<db_find()->all>.
731              
732             =head2 db_count($query?)
733              
734             $total = db_count { name => 'foo' };
735              
736             Implicit call C<context_collection->count>.
737              
738             =head2 db_increment($query,$increment_values,$options)
739              
740             db_increment {name => 2},{ counter => 1, money => 10 },{upsert => 1};
741              
742             Shortcut of '$inc' command.
743              
744             =head2 db_update_set(\%criteria,\%set_object,\%options)
745              
746             db_update_set {name => 'foo'},{new_location => 'Beijing'},{ upsert => 0 };
747              
748             Shortcut for '$set' command.
749              
750             =head2 db_update(\%criteria,\%new_object,\%options?)
751              
752             db_update {_id => 5},{name => 'foo'};
753              
754             Shortcut of L<MongoDB::Collection/update>.
755              
756             =head2 db_remove(\%criteria?,\%options?)
757              
758             db_remove;
759              
760             Shortcut of L<MongoDB::Collection/remove>.
761              
762             =head2 db_find_one(\%query?,\%options?)
763              
764             $result = db_find_one {_id => 5};
765              
766             Shortcut of L<MongoDB::Collection/find_one>.
767              
768             =head2 db_ensure_index(\%keys,\%options?)
769              
770             $result = db_ensure_index { foo => 1, name => 1};
771              
772             Shortcut of L<MongoDB::Collection/ensure_index>.
773              
774             =head2 db_drop_index($index_name)
775              
776             db_drop_index { 'name_1' };
777              
778             Shortcut of L<MongoDB::Collection/drop_index>.
779              
780             =head2 db_drop_indexes
781              
782             db_drop_indexes;
783              
784             Shortcut of L<MongoDB::Collection/drop_indexes>.
785              
786             =head2 db_get_indexes
787              
788             db_get_indexes;
789              
790             Shortcut of L<MongoDB::Collection/get_indexes>.
791              
792             =head2 db_find_by_id
793              
794             my $row = db_find_by_id $oid_or_id_string
795              
796             Quick find_one by _id.
797              
798             =head2 db_remove_by_id
799              
800             db_remove_by_id $oid_or_id_string
801              
802             Quick remove by _id.
803              
804             =head1 SEE ALSO
805              
806             MongoDB Commands docs: L<http://www.mongodb.org/display/DOCS/Commands>
807              
808             =head1 AUTHOR
809              
810             Pan Fan(nightsailer) <nightsailer at gmail dot com>
811              
812             =head1 COPYRIGHT AND LICENSE
813              
814             This software is copyright (c) 2010 by Pan Fan(nightsailer).
815              
816             This is free software; you can redistribute it and/or modify it under
817             the same terms as the Perl 5 programming language system itself.
818              
819             =cut
820              
821              
822             __END__
823