package DBIx::MyParse::Query; use strict; use warnings; use Carp; our $VERSION = '0.88'; # # If you change those constants, do not forget to change # the corresponding C #defines in my_parse.h # use constant MYPARSE_COMMAND => 0; use constant MYPARSE_ORIG_COMMAND => 1; use constant MYPARSE_QUERY_OPTIONS => 2; use constant MYPARSE_SELECT_ITEMS => 3; use constant MYPARSE_INSERT_FIELDS => 4; use constant MYPARSE_UPDATE_FIELDS => 5; use constant MYPARSE_INSERT_VALUES => 6; use constant MYPARSE_UPDATE_VALUES => 7; use constant MYPARSE_TABLES => 8; use constant MYPARSE_ORDER => 9; use constant MYPARSE_GROUP => 10; use constant MYPARSE_WHERE => 11; use constant MYPARSE_HAVING => 12; use constant MYPARSE_LIMIT => 13; use constant MYPARSE_ERROR => 14; use constant MYPARSE_ERRNO => 15; use constant MYPARSE_ERRSTR => 16; use constant MYPARSE_SQLSTATE => 17; use constant MYPARSE_DELETE_TABLES => 18; use constant MYPARSE_SAVEPOINT => 20; use constant MYPARSE_SCHEMA_SELECT => 21; use constant MYPARSE_WILD => 22; 1; sub getCommand { return $_[0]->[MYPARSE_COMMAND]; } sub setCommand { $_[0]->[MYPARSE_COMMAND] = $_[1]; } sub getOrigCommand { return $_[0]->[MYPARSE_ORIG_COMMAND]; } sub setOrigCommand { return $_[0]->[MYPARSE_ORIG_COMMAND] = $_[1]; } sub getOptions { return $_[0]->[MYPARSE_QUERY_OPTIONS]; } sub setOptions { return $_[0]->[MYPARSE_QUERY_OPTIONS] = $_[1]; } sub getOption { my ($query, $option) = @_; my $options = $query->getOptions(); return undef if not defined $options; foreach my $o (@{$options}) { return 1 if $option eq $o; } return 0; } sub setOption { my $options = $_[0]->getOptions(); $options = [] if not defined $options; push @{$options}, $_[1]; $_[0]->setOptions($options); } sub getSelectItems { return $_[0]->[MYPARSE_SELECT_ITEMS]; } sub setSelectItems { $_[0]->[MYPARSE_SELECT_ITEMS] = $_[1]; } sub getInsertFields { return $_[0]->[MYPARSE_INSERT_FIELDS]; } sub setInsertFields { $_[0]->[MYPARSE_INSERT_FIELDS] = $_[1]; } sub getInsertValues { return $_[0]->[MYPARSE_INSERT_VALUES]; } sub setInsertValues { $_[0]->[MYPARSE_INSERT_VALUES] = $_[1]; } sub getUpdateFields { return $_[0]->[MYPARSE_UPDATE_FIELDS]; } sub setUpdateFields { $_[0]->[MYPARSE_UPDATE_FIELDS] = $_[1]; } sub getUpdateValues { return $_[0]->[MYPARSE_UPDATE_VALUES]; } sub setUpdateValues { $_[0]->[MYPARSE_UPDATE_VALUES] = $_[1]; } sub getTables { return $_[0]->[MYPARSE_TABLES]; } sub setTables { $_[0]->[MYPARSE_TABLES] = $_[1]; } sub getDeleteTables { return $_[0]->[MYPARSE_DELETE_TABLES]; } sub setDeleteTables { $_[0]->[MYPARSE_DELETE_TABLES] = $_[1]; } sub getOrder { return $_[0]->[MYPARSE_ORDER]; } sub setOrder { $_[0]->[MYPARSE_ORDER] = $_[1]; } sub getOrderBy { return $_[0]->[MYPARSE_ORDER]; } sub setOrderBy { $_[0]->[MYPARSE_ORDER] = $_[1]; } sub getGroup { return $_[0]->[MYPARSE_GROUP]; } sub setGroup { return $_[0]->[MYPARSE_GROUP] = $_[1]; } sub getGroupBy { return $_[0]->[MYPARSE_GROUP]; } sub setGroupBy { $_[0]->[MYPARSE_GROUP] = $_[1]; } sub getWhere { return $_[0]->[MYPARSE_WHERE]; } sub setWhere { $_[0]->[MYPARSE_WHERE] = $_[1]; } sub getHaving { return $_[0]->[MYPARSE_HAVING]; } sub setHaving { $_[0]->[MYPARSE_HAVING] = $_[1]; } sub getLimit { return $_[0]->[MYPARSE_LIMIT]; }; sub setLimit { $_[0]->[MYPARSE_LIMIT] = $_[1]; } sub getError { return $_[0]->[MYPARSE_ERROR]; } sub setError { $_[0]->[MYPARSE_ERROR] = $_[1]; } sub getErrno { return $_[0]->[MYPARSE_ERRNO]; } sub setErrno { $_[0]->[MYPARSE_ERRNO] = $_[1]; } sub getErrstr { return $_[0]->[MYPARSE_ERRSTR]; } sub setErrStr { $_[0]->[MYPARSE_ERRSTR] = $_[1]; } sub getSQLState { return $_[0]->[MYPARSE_SQLSTATE]; } sub setSQLState { $_[0]->[MYPARSE_SQLSTATE] = $_[1]; } sub getSavepoint { if ( ($_[0]->[MYPARSE_COMMAND] eq 'SQLCOM_SAVEPOINT') || ($_[0]->[MYPARSE_COMMAND] eq 'SQLCOM_ROLLBACK_TO_SAVEPOINT') || ($_[0]->[MYPARSE_COMMAND] eq 'SQLCOM_RELEASE_SAVEPOINT') ) { return $_[0]->[MYPARSE_SAVEPOINT]; } else { carp("getSavepoint() called, but getCommand() == ".$_[0]->[MYPARSE_COMMAND]); return undef; } } sub setSavepoint { $_[0]->[MYPARSE_SAVEPOINT] = $_[1]; } sub getSchemaSelect { my $query = shift; my $command = $query->getCommand(); my $orig_command = $query->getOrigCommand(); if ( ($orig_command eq 'SQLCOM_SHOW_FIELDS') || ($orig_command eq 'SQLCOM_SHOW_TABLES') || ($orig_command eq 'SQLCOM_SHOW_TABLE_STATUS') || ($command eq 'SQLCOM_CHANGE_DB') || ($command eq 'SQLCOM_DROP_DB') || ($command eq 'SQLCOM_CREATE_DB') ) { return $query->[MYPARSE_SCHEMA_SELECT]; } else { warn("getSchemaSelect() called, but getOrigCommand() == ".$query->getOrigCommand()); return undef; } } sub setSchemaSelect { $_[0]->[MYPARSE_SCHEMA_SELECT] = $_[1]; } sub getWild { return $_[0]->[MYPARSE_WILD]; } sub setWild { $_[0]->[MYPARSE_WILD] = $_[1]; } sub isPrintable { my $query = shift; if ( ($query->getCommand() eq 'SQLCOM_SELECT') && ($query->getOrigCommand() ne 'SQLCOM_END') ) { return 0; # We can not print SHOW TABLES and the like for the time being } elsif ($query->getOrigCommand() =~ m{SELECT|INSERT|UPDATE|DELETE|REPLACE|DROP_DB|DROP_TABLE|CREATE_DB|RENAME_TABLE|TRUNCATE|BEGIN|COMMIT|ROLLBACK|SAVEPOINT}io) { return 1; } else { return 0; } } sub print { my $query = shift; my $command = $query->getCommand(); if ($command eq 'SQLCOM_SELECT') { return $query->_printSelect(); } elsif ( ($command eq 'SQLCOM_UPDATE') || ($command eq 'SQLCOM_UPDATE_MULTI') ) { return $query->_printUpdate(); } elsif ( ($command eq 'SQLCOM_DELETE') || ($command eq 'SQLCOM_DELETE_MULTI') ) { return $query->_printDelete(); } elsif ( ($command eq 'SQLCOM_INSERT') || ($command eq 'SQLCOM_REPLACE') || ($command eq 'SQLCOM_INSERT_SELECT') || ($command eq 'SQLCOM_REPLACE_SELECT') ) { return $query->_printInsertReplace(); } elsif ( ($command eq 'SQLCOM_DROP_DB') || ($command eq 'SQLCOM_DROP_TABLE') ) { return $query->_printDrop(); } elsif ($command eq 'SQLCOM_CREATE_DB') { return $query->_printCreate(); } elsif ($command eq 'SQLCOM_RENAME_TABLE') { return $query->_printRename(); } elsif ($command eq 'SQLCOM_TRUNCATE') { return "TRUNCATE TABLE ".$query->getTables()->[0]->_printTable(0); } elsif ($command eq 'SQLCOM_BEGIN') { if ($command->getOption("WITH_CONSISTENT_SNAPSHOT")) { return "START TRANSACTION WITH CONSISTENT SNAPSHOT"; } else { return "START TRANSACTION"; } } elsif ( ($command eq 'SQLCOM_COMMIT') || ($command eq 'SQLCOM_ROLLBACK') ) { my $chain = $query->getOption("CHAIN") ? "AND CHAIN " : ""; my $no_chain = $query->getOption("NO_CHAIN") ? "AND NO CHAIN " : ""; my $release = $query->getOption("RELEASE") ? "RELEASE " : ""; my $no_release = $query->getOption("NO_RELEASE") ? "NO RELEASE " : ""; if ($command eq 'SQLCOM_ROLLBACK') { return "ROLLBACK ".$chain.$no_chain.$release.$no_release; } else { return "COMMIT ".$chain.$no_chain.$release.$no_release; } } elsif ($command eq 'SQLCOM_SAVEPOINT') { return "SAVEPOINT ".$query->getSavepoint(); } elsif ($command eq 'SQLCOM_ROLLBACK_TO_SAVEPOINT') { return "ROLLBACK TO SAVEPOINT ".$query->getSavepoint(); } elsif ($command eq 'SQLCOM_RELEASE_SAVEPOINT') { return "RELEASE SAVEPOINT ".$query->getSavepoint(); } else { warn("DBIx::MyParse::Query::print() called, but command eq '$command'"); return undef; } } sub _printRename { my $query = shift; my $command = $query->getCommand(); if ($command eq 'SQLCOM_RENAME_TABLE') { my @tables = @{$query->getTables()}; my @tables_printed; while (my ($table1, $table2) = splice(@tables,0,2)) { push @tables_printed, $table1->_printTable(0)." TO ".$table2->_printTable(0); } return "RENAME TABLE ".join(', ', @tables_printed); } } sub _printDrop { my $query = shift; my $command = $query->getCommand(); if ($command eq 'SQLCOM_DROP_DB') { my $drop_if_exists = $query->getOption("DROP_IF_EXISTS") ? "IF EXISTS " : ""; return "DROP DATABASE ".$drop_if_exists.$query->getSchemaSelect()->print(); } elsif ($command eq 'SQLCOM_DROP_TABLE') { my $drop_if_exists = $query->getOption("DROP_IF_EXISTS") ? "IF EXISTS " : ""; my $drop_temporary = $query->getOption("DROP_TEMPORARY") ? "TEMPORARY " : ""; my $drop_restrict = $query->getOption("DROP_RESTRICT") ? " RESTRICT" : ""; my $drop_cascade = $query->getOption("DROP_CASCADE") ? " CASCADE" : ""; return "DROP ".$drop_temporary."TABLE ".$drop_if_exists.join(', ', map { $_->_printTable(0) } @{$query->getTables()}).$drop_restrict.$drop_cascade; } } sub _printCreate { my $query = shift; my $command = $query->getCommand(); if ($command eq 'SQLCOM_CREATE_DB') { my $create_if_not_exists = $query->getOption("CREATE_IF_NOT_EXISTS") ? "IF NOT EXISTS " : ""; return "CREATE DATABASE ".$create_if_not_exists.$query->getSchemaSelect()->print(); } } sub _printSelect { my $query = shift; my $describe = ""; $describe = 'EXPLAIN ' if $query->getOption('DESCRIBE_NORMAL'); $describe = 'EXPLAIN EXTENDED ' if $query->getOption('DESCRIBE_EXTENDED'); my $distinct = $query->getOption('SELECT_DISTINCT') ? "DISTINCT " : ""; my $high_priority = $query->getOption('TL_READ_HIGH_PRIORITY') ? "HIGH_PRIORITY " : ""; my $straight_join = $query->getOption('SELECT_STRAIGHT_JOIN') ? "STRAIGHT_JOIN " : ""; my $small_result = $query->getOption('SELECT_SMALL_RESULT') ? "SQL_SMALL_RESULT " : ""; my $big_result = $query->getOption('SELECT_BIG_RESULT') ? "SQL_BIG_RESULT " : ""; my $buffer_result = $query->getOption('OPTION_BUFFER_RESULT') ? "SQL_BUFFER_RESULT " : ""; my $query_cache = ""; $query_cache = 'SQL_NO_CACHE ' if $query->getOption('SQL_NO_CACHE'); $query_cache = 'SQL_CACHE ' if $query->getOption('OPTION_TO_QUERY_CACHE'); my $found_rows = $query->getOption("OPTION_FOUND_ROWS") ? "SQL_CALC_FOUND_ROWS ": ""; my $for_update = $query->getOption("TL_WRITE") ? " FOR UPDATE" : ""; my $share_mode = $query->getOption("TL_READ_WITH_SHARED_LOCKS") ? " LOCK IN SHARE MODE": ""; my $with_cube = $query->getOption("WITH_CUBE") ? " WITH CUBE " : ""; my $with_rollup = $query->getOption("WITH_ROLLUP") ? " WITH ROLLUP ": ""; my $select_items_str; my $select_items = $query->getSelectItems(); if (defined $select_items) { $select_items_str = join(', ', map { $_->print(1) } @{$select_items} ); } return $describe.'SELECT '.$distinct.$high_priority.$straight_join.$small_result.$big_result.$buffer_result. $query_cache.$found_rows.$select_items_str." ". (defined $query->getTables() ? "FROM ".$query->_printFrom() : ""). $query->_printWhere(). $query->_printGroupBy().$with_rollup.$with_cube. $query->_printHaving(). $query->_printOrderBy(). $query->_printLimit(). $for_update.$share_mode ; } sub _printInsertReplace { my $query = shift; my $command = $query->getCommand(); my $verb; if ( ($command eq 'SQLCOM_INSERT') || ($command eq 'SQLCOM_INSERT_SELECT') ) { $verb = 'INSERT '; } elsif ( ($command eq 'SQLCOM_REPLACE') || ($command eq 'SQLCOM_REPLACE_SELECT') ) { $verb = 'REPLACE '; } my $low_priority = ($query->getOption("TL_WRITE_LOW_PRIORITY") ? "LOW_PRIORITY " : ""); my $high_priority = ($query->getOption("TL_WRITE") && $command =~ m{INSERT}o ? "HIGH_PRIORITY " : ""); my $delayed = ($query->getOption("TL_WRITE_DELAYED") ? "DELAYED " : ""); my $ignore = ($query->getOption("IGNORE") ? "IGNORE " : ""); my $on_duplicate_key = ""; my @all_tables = @{$query->getTables()}; my $insert_table = shift @all_tables; # We do not use simply $insert_table->print() because INSERT does not accept table aliases my $table_printed = $insert_table->_printTable(0); my $insert_fields = $query->getInsertFields(); my $fields_printed = scalar(@{$insert_fields}) > 0 ? "(".join(', ', map { $_->print() } @{$insert_fields}).") " : ""; my $update_fields = $query->getUpdateFields(); if (defined $update_fields) { my $update_count = scalar(@{$update_fields}); my $update_values = $query->getUpdateValues(); my @updates; foreach my $i (0..($update_count - 1)) { push @updates, $update_fields->[$i]->print()." = ".$update_values->[$i]->print(); } $on_duplicate_key = "ON DUPLICATE KEY UPDATE ".join(', ', @updates); } if ( ($command eq 'SQLCOM_INSERT_SELECT') || ($command eq 'SQLCOM_REPLACE_SELECT') ) { my $select_printed = $query->_printSelect(); return $verb.$low_priority.$delayed.$high_priority.$ignore.'INTO '.$table_printed.' '.$fields_printed.' '.$select_printed.' '.$on_duplicate_key; } else { my $insert_values = $query->getInsertValues(); my @values_printed; foreach my $row (@{$insert_values}) { push @values_printed, join(', ', map { $_->print() } @{$row}); } my $values_printed = join(', ', map { "(".$_.")"} @values_printed)." "; return $verb.$low_priority.$delayed.$high_priority.$ignore.'INTO '.$table_printed.' '.$fields_printed."VALUES ".$values_printed.$on_duplicate_key; } } sub _printUpdate { my $query = shift; my $update_fields = $query->getUpdateFields(); my $update_values = $query->getUpdateValues(); my $field_count = scalar(@{$update_fields}); my @updates; foreach my $i (0..($field_count - 1)) { push @updates, $update_fields->[$i]->print()." = ".$update_values->[$i]->print(); } my $low_priority = ($query->getOption("TL_WRITE_LOW_PRIORITY") ? "LOW_PRIORITY " : ""); my $ignore = ($query->getOption("IGNORE") ? "IGNORE " : ""); return "UPDATE ".$low_priority.$ignore.$query->_printFrom(). " SET ".join(', ', @updates). $query->_printWhere(). $query->_printOrderBy(). $query->_printLimit(); } sub _printDelete { my $query = shift; my $low_priority = ($query->getOption("TL_WRITE_LOW_PRIORITY") ? "LOW_PRIORITY " : ""); my $ignore = ($query->getOption("IGNORE") ? "IGNORE " : ""); my $quick = ($query->getOption("OPTION_QUICK") ? "QUICK " : ""); if ($query->getCommand() eq 'SQLCOM_DELETE_MULTI') { my $delete_tables = join(', ', map { $_->print() } @{$query->getDeleteTables()}); return "DELETE ".$low_priority.$ignore.$quick.$delete_tables." FROM ". $query->_printFrom(). $query->_printWhere(); } else { my $delete_table = $query->getTables()->[0]; my $table_printed = $delete_table->_printTable(0); return "DELETE ".$low_priority.$ignore.$quick."FROM ".$table_printed. $query->_printWhere(). $query->_printOrderBy(). $query->_printLimit(); } } sub _printFrom { my $query = shift; my $from = $query->getTables(); my $command = $query->getCommand(); if (not defined $from) { return "DUAL"; } elsif (ref($from) eq 'ARRAY') { my @tables = @{$from}; if ( ($command eq 'SQLCOM_INSERT_SELECT') || ($command eq 'SQLCOM_REPLACE_SELECT') ) { shift @tables; } if (scalar(@tables) > 0) { return join(', ', map { $_->print(1) } @tables); } else { return "DUAL"; } } else { return $from->print(1); } } sub _printWhere { my $query = shift; my $where = $query->getWhere(); if (defined $where) { return " WHERE ".$where->print(); } else { return ""; } } sub _printGroupBy { my $query = shift; if (defined $query->getGroupBy()) { return " GROUP BY ".join(', ', map {$_->print()} @{$query->getGroupBy()}); } else { return ""; } } sub _printOrderBy { my $query = shift; if (defined $query->getOrderBy()) { return " ORDER BY ".join(', ', map {$_->print()." ".$_->getDir()} @{$query->getOrderBy()}); } else { return ""; } } sub _printHaving { my $query = shift; my $having = $query->getHaving(); if (defined $having) { return " HAVING ".$having->print(); } else { return ""; } } sub _printLimit { my $query = shift; my $limit = $query->getLimit(); return "" if not defined $limit; my $row_count = $limit->[0]; my $offset = $limit->[1]; my $limit_str = " LIMIT ".$row_count->print(); $limit_str .= " OFFSET ".$offset->print() if defined $offset; return $limit_str; } 1; __END__ =head1 NAME DBIx::MyParse::Query - Access the parse tree produced by DBIx::MyParse =head1 SYNOPSIS use DBIx::MyParse; my $parser = DBIx::MyParse->new(); my $query = $parser->parse("INSERT INTO table VALUES (1)"); print $query->getCommand(); $query->setCommand("SQLCOM_REPLACE"); # Replace INSERT with SELECT $query->print(); # Print modified query as SQL =head1 DESCRIPTION This module attempts to provide structured access to the parse tree that is produced by MySQL's SQL parser. Since the parser itself is not exactly perfectly structured, please make sure you read this entire document before attempting to make sense of C objects. =head1 METHODS =over =item C Returns, as string, the name of SQL command that was parsed. All possible values can be found in enum enum_sql_command in F from the MySQL source. The commands that are currently supported (that is, a parse tree is created for them) are as follows: "SQLCOM_SELECT", "SQLCOM_DO" "SQLCOM_INSERT", "SQLCOM_INSERT_SELECT" "SQLCOM_REPLACE", "SQLCOM_REPLACE_SELECT" "SQLCOM_UPDATE", "SQLCOM_UPDATE_MULTI" "SQLCOM_DELETE", "SQLCOM_DELETE_MULTI" "SQLCOM_BEGIN", "SQLCOM_COMMIT", "SQLCOM_ROLLBACK", "SQLCOM_SAVEPOINT", "SQLCOM_ROLLBACK_TO_SAVEPOINT", "SQLCOM_RELEASE_SAVEPOINT" "SQLCOM_DROP_DB", "SQLCOM_CREATE_DB", "SQLCOM_DROP_TABLE", "SQLCOM_RENAME_TABLE" Please note that the returned value is a string, and not an integer. Please read the section COMMANDS below for notes on individual commands =item C For C, C, C, C and C, the MySQL parser will rewrite the original query into a C query will return, each being a L object. Valid options are "SELECT_DISTINCT", "TL_READ_HIGH_PRIORITY", "SELECT_STRAIGHT_JOIN" "SELECT_SMALL_RESULT", "SELECT_BIG_RESULT", "OPTION_BUFFER_RESULT" "OPTION_FOUND_ROWS", "OPTION_TO_QUERY_CACHE" (force caching), "SQL_NO_CACHE", "TL_WRITE" (FOR UPDATE), "TL_READ_WITH_SHARED_LOCKS" (LOCK IN SHARE MODE), "WITH_ROLLUP", "WITH_CUBE", "SELECT_DESCRIBE", "DESCRIBE_NORMAL", "DESCRIBE_EXTENDED" =item C Rreturns a reference to the array of tables specified in the query. Each table is also an L object for which C which contains information on the Join type, join conditions, indexes, etc. See L for information on how to extract the individual properties. =item C Returns an L object that is the root of the tree containing all the WHERE conditions. =item C Operates the same way as C but for the HAVING clause. =item C Returns a reference to an array containing one L object for each GROUP BY condition. =item C Returns a reference to an array containing the individual L from the ORDER BY clause. =item C< Returns a reference to a two-item array containing the two parts of the LIMIT clause as L objects. =back =head2 C<"SQLCOM_DO"> C will return the expressions being executed. =head2 C<"SQLCOM_UPDATE"> and C<"SQLCOM_UPDATE_MULTI"> =over =item C<< my $array_ref = $query->getUpdateFields() >> Returns a reference to an array containing the fields that the query would update. =item C<< my $array_ref = $query->getUpdateValues() >> Returns a reference to an array containing the values that will be assigned to the fields being updated. =back C, C, C and C can also be used for update queries. For C<"SQLCOM_UPDATE">, C will return a reference to a one-item array containg a L object describing the table being updated. For C<"SQLCOM_UPDATE_MULTI">, the array can include several tables or Cs. =head2 C<"SQLCOM_DELETE"> and C<"SQLCOM_DELETE_MULTI"> For a multiple-table delete, C =over =item C Will return a reference to an array contwaining the table(s) we are deleting records from. =item C For a multiple-table delete, C will return the tables listed in the FROM clause, which are used to provide referential integrity. Those may include Cs. =back C, C and C can also be used. =head2 C<"SQLCOM_INSERT">, C<"SQLCOM_INSERT_SELECT">, C<"SQLCOM_REPLACE"> and C<"SQLCOM_REPLACE_SELECT"> =over =item C Returns a list of the fields you are inserting to. =item C For C<"SQLCOM_INSERT"> and C<"SQLCOM_REPLACE">, C will return a reference to an array, containing one sub-array for each row being inserted or replaced (even if there is only one row). =back For C<"SQLCOM_INSERT_SELECT"> and C<"SQLCOM_REPLACE_SELECT">, C, C, C and the other SELECT-related properties will describe the C, C, C, C and C statements are supported. Please note that the returned string may be very different from the orginal query due to internal transformations that MySQL applies during parsing. Also, the C-ed query may have extra C clauses and an abundance of nested brackets. C can be used to test whether calling C would be meaningful. =head1 Modifying the parse tree For every C method, there is a corresponding C method that updates the parse tree, e.g. $query->setCommand("SQLCOM_UPDATE"); Also, any arrayrefs returned from C methods can be modified and (since references are used) the results will be reflected in the original object. If you do not want this to happen, you do need to dereference the arrayref and assign it to a new array, e.g.: my $items = $query->getItems(); my @items_copy = @{$items}; =cut