SQL::Abstract::More(3pm) | User Contributed Perl Documentation | SQL::Abstract::More(3pm) |
SQL::Abstract::More - extension of SQL::Abstract with more constructs and more flexible API
This module generates SQL from Perl data structures. It is a subclass of SQL::Abstract::Classic or SQL::Abstract, fully compatible with the parent class, but with some improvements :
This module was designed for the specific needs of DBIx::DataModel, but is published as a standalone distribution, because it may possibly be useful for other needs.
Unfortunately, this module cannot be used with DBIx::Class, because "DBIx::Class" creates its own instance of "SQL::Abstract" and has no API to let the client instantiate from any other class.
use SQL::Abstract::More; # will inherit from SQL::Abstract::Classic; #or use SQL::Abstract::More -extends => 'SQL::Abstract'; # will inherit from SQL::Abstract; my $sqla = SQL::Abstract::More->new(); my ($sql, @bind); # ex1: named parameters, select DISTINCT, ORDER BY, LIMIT/OFFSET ($sql, @bind) = $sqla->select( -columns => [-distinct => qw/col1 col2/], -from => 'Foo', -where => {bar => {">" => 123}}, -order_by => [qw/col1 -col2 +col3/], # BY col1, col2 DESC, col3 ASC -limit => 100, -offset => 300, ); # ex2: column aliasing, join ($sql, @bind) = $sqla->select( -columns => [ qw/Foo.col_A|a Bar.col_B|b /], -from => [-join => qw/Foo fk=pk Bar /], ); # ex3: INTERSECT (or similar syntax for UNION) ($sql, @bind) = $sqla->select( -columns => [qw/col1 col2/], -from => 'Foo', -where => {col1 => 123}, -intersect => [ -columns => [qw/col3 col4/], -from => 'Bar', -where => {col3 => 456}, ], ); # ex4: passing datatype specifications ($sql, @bind) = $sqla->select( -from => 'Foo', -where => {bar => [{dbd_attrs => {ora_type => ORA_XMLTYPE}}, $xml]}, ); my $sth = $dbh->prepare($sql); $sqla->bind_params($sth, @bind); $sth->execute; # ex5: multicolumns-in $sqla = SQL::Abstract::More->new( multicols_sep => '/', has_multicols_in_SQL => 1, ); ($sql, @bind) = $sqla->select( -from => 'Foo', -where => {"foo/bar/buz" => {-in => ['1/a/X', '2/b/Y', '3/c/Z']}}, ); # ex6: merging several criteria my $merged = $sqla->merge_conditions($cond_A, $cond_B, ...); ($sql, @bind) = $sqla->select(..., -where => $merged, ..); # ex7: insert / update / delete ($sql, @bind) = $sqla->insert( -add_sql => 'OR IGNORE', # SQLite syntax -into => $table, -values => {col => $val, ...}, ); ($sql, @bind) = $sqla->insert( -into => $table, -columns => [qw/a b/], -select => {-from => 'Bar', -columns => [qw/x y/], -where => ...}, ); ($sql, @bind) = $sqla->update( -table => $table, -set => {col => $val, ...}, -where => \%conditions, ); ($sql, @bind) = $sqla->delete ( -from => $table -where => \%conditions, ); # ex8 : initial WITH clause -- example borrowed from https://sqlite.org/lang_with.html ($sql, @bind) = $sqla->with_recursive( [ -table => 'parent_of', -columns => [qw/name parent/], -as_select => {-columns => [qw/name mom/], -from => 'family', -union => [-columns => [qw/name dad/], -from => 'family']}, ], [ -table => 'ancestor_of_alice', -columns => [qw/name/], -as_select => {-columns => [qw/parent/], -from => 'parent_of', -where => {name => 'Alice'}, -union_all => [-columns => [qw/parent/], -from => [qw/-join parent_of {name} ancestor_of_alice/]], }, ], )->select( -columns => 'family.name', -from => [qw/-join ancestor_of_alice {name} family/], -where => {died => undef}, -order_by => 'born', );
The "import()" method is called automatically when a client writes "use SQL::Abstract::More".
At this point there is a choice to make about the class to inherit from. Originally this module was designed as an extension of SQL::Abstract in its versions prior to 1.81. Then SQL::Abstract was rewritten with a largely different architecture, published under v2.000001. A fork of the previous version is now published under SQL::Abstract::Classic. "SQL::Abstract::More" can inherit from either version; initially it used SQL::Abstract as the default parent, but now the default is back to SQL::Abstract::Classic for better compatibility with previous behaviours (see for example <https://rt.cpan.org/Ticket/Display.html?id=143837>).
The choice of the parent class is made according to the following rules :
use SQL::Abstract::More -extends => 'SQL::Abstract';
use SQL::Abstract::More -extends => 'Classic';
BEGIN {$ENV{SQL_ABSTRACT_MORE_EXTENDS} = 'Classic'; use SQL::Abstract::More; # will inherit from SQL::Abstract::Classic; }
my $sqla = SQL::Abstract::More->new(%options);
where %options may contain any of the options for the parent class (see "new" in SQL::Abstract), plus the following :
The argument can also be a coderef (see below "Overriding methods"). That coderef takes "$self, $limit, $offset" as arguments, and should return "($sql, @bind)". If $sql contains %s, it is treated as a "sprintf" format string, where the original SQL is injected into %s.
my $sqla = SQL::Abstract::More->new(max_members_IN => 3); ($sql, @bind) = $sqla->select( -from => 'Foo', -where => {foo => {-in => [1 .. 5]}}, bar => {-not_in => [6 .. 10]}}, ); # .. WHERE ( (foo IN (?,?,?) OR foo IN (?, ?)) # AND (bar NOT IN (?,?,?) AND bar NOT IN (?, ?)) )
my $sqla = SQL::Abstract::More->new(multicols_sep => '/'); ($sql, @bind) = $sqla->select( -from => 'Foo', -where => {"x/y/z" => {-in => ['1/A/foo', '2/B/bar']}}, );
Alternatively, tuple values on the right-hand side can also be given as arrayrefs instead of plain scalars with separators :
-where => {"x/y/z" => {-in => [[1, 'A', 'foo'], [2, 'B', 'bar']]}},
but the left-hand side must stay a plain scalar because an array reference wouldn't be a proper key for a Perl hash; in addition, the presence of the separator in the string is necessary to trigger the special algorithm for multicolumns.
The generated SQL depends on the boolean flag "has_multicols_in_SQL", as explained in the next paragraph.
# $sql : "WHERE (x, y, z) IN ((?, ?, ?), (?, ?, ?))" # @bind : [ qw/1 A foo 2 B bar/ ]
It is also possible to use a subquery, like this :
($sql, @bind) = $sqla->select( -from => 'Foo', -where => {"x/y/z" => {-in => \[ 'SELECT (a, b, c) FROM Bar ' . 'WHERE a > ?', 99]}}, ); # $sql : "WHERE (x, y, z) IN (SELECT (a, b, c) FROM Bar WHERE a > ?)" # @bind : [ 99 ]
If the flag is false, the condition on tuples will be automatically converted using boolean logic :
# $sql : "WHERE ( (x = ? AND y = ? AND z = ?) OR (x = ? AND y = ? AND z = ?))" # @bind : [ qw/1 A foo 2 B bar/ ]
If the flag is false, subqueries are not allowed.
my $sqla = SQL::Abstract->new(-select_implicitly_for => 'READ ONLY'); ($sql, @bind) = $sqla->select(-from => 'Foo'); # SELECT * FROM FOO FOR READ ONLY ($sql, @bind) = $sqla->select(-from => 'Foo', -for => 'UPDATE'); # SELECT * FROM FOO FOR UPDATE ($sql, @bind) = $sqla->select(-from => 'Foo', -for => undef); # SELECT * FROM FOO
Overriding methods
Several arguments to "new()" can be references to method implementations instead of plain scalars : this allows you to completely redefine a behaviour without the need to subclass. Just supply a regular method body as a code reference : for example, if you need another implementation for LIMIT-OFFSET, you could write
my $sqla = SQL::Abstract::More->new( limit_offset => sub { my ($self, $limit, $offset) = @_; defined $limit or die "NO LIMIT!"; #:-) $offset ||= 0; my $last = $offset + $limit; return ("ROWS ? TO ?", $offset, $last); # ($sql, @bind) });
# positional parameters, directly passed to the parent class ($sql, @bind) = $sqla->select($table, $columns, $where, $order); # named parameters, handled in this class ($sql, @bind) = $sqla->select( -columns => \@columns, # OR: -columns => [-distinct => @columns], -from => $table || \@joined_tables, -where => \%where, -union => [ %select_subargs ], # OR -intersect, -minus, etc -order_by => \@order, -group_by => \@group_by, -having => \%having_criteria, -limit => $limit, -offset => $offset, # OR: -page_size => $size, -page_index => $index, -for => $purpose, ); my $details = $sqla->select(..., want_details => 1); # keys in %$details: sql, bind, aliased_tables, aliased_columns
If called with positional parameters, as in SQL::Abstract, "select()" just forwards the call to the parent class. Otherwise, if called with named parameters, as in the example above, some additional SQL processing is performed.
The following named arguments can be specified :
A '|' in a column is translated into a column aliasing clause: this is convenient when using perl "qw/.../" operator for columns, as in
-columns => [ qw/table1.longColumn|t1lc table2.longColumn|t2lc/ ]
SQL column aliasing is then generated through the "column_alias" method. If "quote_char" in SQL::Abstract is defined, aliased columns will be quoted, unless they contain parentheses, in which case they are considered as SQL expressions for which the user should handle the quoting himself. For example if "quote_char" is "`",
-columns => [ qw/foo.bar|fb length(buz)|lbuz/ ]
will produce
SELECT `foo`.`bar` AS fb, length(buz) AS lbuz
and not
SELECT `foo`.`bar` AS fb, length(`buz`) AS lbuz
Initial items in @columns that start with a minus sign are shifted from the array, i.e. they are not considered as column names, but are re-injected later into the SQL (without the minus sign), just after the "SELECT" keyword. This is especially useful for
$sqla->select(..., -columns => [-DISTINCT => @columns], ...);
However, it may also be useful for other purposes, like vendor-specific SQL variants :
# MySQL features ->select(..., -columns => [-STRAIGHT_JOIN => @columns], ...); ->select(..., -columns => [-SQL_SMALL_RESULT => @columns], ...); # Oracle hint ->select(..., -columns => ["-/*+ FIRST_ROWS (100) */" => @columns], ...);
The argument to "-columns" can also be a string instead of an arrayref, like for example "c1 AS foobar, MAX(c2) AS m_c2, COUNT(c3) AS n_c3"; however this is mainly for backwards compatibility. The recommended way is to use the arrayref notation as explained above :
-columns => [ qw/ c1|foobar MAX(c2)|m_c2 COUNT(c3)|n_c3 / ]
If omitted, "-columns" takes '*' as default argument.
When using hashrefs or arrayrefs, leaf values can be "bind values with types"; see the "BIND VALUES WITH TYPES" section below.
Column names "asc" and "desc" are treated as exceptions to this rule, in order to preserve compatibility with SQL::Abstract. So "-orderBy => [-desc => 'colA']" yields "ORDER BY colA DESC" and not "ORDER BY desc DEC, colA". Any other syntax supported by SQL::Abstract is also supported here; see "ORDER BY CLAUSES" in SQL::Abstract for examples.
The whole "-order_by" parameter can also be a plain SQL string like "col1 DESC, col3, col2 DESC".
# positional parameters, directly passed to the parent class ($sql, @bind) = $sqla->insert($table, \@values || \%fieldvals, \%options); # named parameters, handled in this class ($sql, @bind) = $sqla->insert( -into => $table, -values => {col => $val, ...}, -returning => $return_structure, -add_sql => $keyword, ); # insert from a subquery ($sql, @bind) = $sqla->insert( -into => $destination_table, -columns => \@columns_into -select => {-from => $source_table, -columns => \@columns_from, -where => ...}, );
Like for "select", values assigned to columns can have associated SQL types; see "BIND VALUES WITH TYPES".
Parameters "-into" and "-values" are passed verbatim to the parent method.
Parameters "-select" and "-columns" are used for selecting from subqueries -- this is incompatible with the "-values" parameter.
Parameter "-returning" is optional and only supported by some database vendors (see "insert" in SQL::Abstract); if the $return_structure is
($sql, @bind) = $sqla->insert( -into => $table, -values => {col => $val, ...}, -returning => {key_col => \my $generated_key}, ); my $sth = $dbh->prepare($sql); $sqla->bind_params($sth, @bind); $sth->execute; print "The new key is $generated_key";
Optional parameter "-add_sql" is used with some specific SQL dialects, for injecting additional SQL keywords after the "INSERT" keyword. Examples :
$sqla->insert(..., -add_sql => 'IGNORE') # produces "INSERT IGNORE ..." -- MySQL $sqla->insert(..., -add_sql => 'OR IGNORE') # produces "INSERT OR IGNORE ..." -- SQLite
# positional parameters, directly passed to the parent class ($sql, @bind) = $sqla->update($table, \%fieldvals, \%where); # named parameters, handled in this class ($sql, @bind) = $sqla->update( -table => $table, -set => {col => $val, ...}, -where => \%conditions, -order_by => \@order, -limit => $limit, -returning => $return_structure, -add_sql => $keyword, );
This works in the same spirit as the "insert" method above. Positional parameters are supported for backwards compatibility with the old API; but named parameters should be preferred because they improve the readability of the client's code.
Few DBMS would support parameters "-order_by" and "-limit", but MySQL does -- see <http://dev.mysql.com/doc/refman/5.6/en/update.html>.
Optional parameter "-returning" works like for the "insert" method.
Optional parameter "-add_sql" is used with some specific SQL dialects, for injecting additional SQL keywords after the "UPDATE" keyword. Examples :
$sqla->update(..., -add_sql => 'IGNORE') # produces "UPDATE IGNORE ..." -- MySQL $sqla->update(..., -add_sql => 'OR IGNORE') # produces "UPDATE OR IGNORE ..." -- SQLite
# positional parameters, directly passed to the parent class ($sql, @bind) = $sqla->delete($table, \%where); # named parameters, handled in this class ($sql, @bind) = $sqla->delete ( -from => $table -where => \%conditions, -order_by => \@order, -limit => $limit, -add_sql => $keyword, );
Positional parameters are supported for backwards compatibility with the old API; but named parameters should be preferred because they improve the readability of the client's code.
Few DBMS would support parameters "-order_by" and "-limit", but MySQL does -- see <http://dev.mysql.com/doc/refman/5.6/en/update.html>.
Optional parameter "-add_sql" is used with some specific SQL dialects, for injecting additional SQL keywords after the "DELETE" keyword. Examples :
$sqla->delete(..., -add_sql => 'IGNORE') # produces "DELETE IGNORE ..." -- MySQL $sqla->delete(..., -add_sql => 'OR IGNORE') # produces "DELETE OR IGNORE ..." -- SQLite
my $new_sqla = $sqla->with_recursive( # or: $sqla->with( [ -table => $CTE_table_name, -columns => \@CTE_columns, -as_select => \%select_args ], [ -table => $CTE_table_name2, -columns => \@CTE_columns2, -as_select => \%select_args2 ], ... ); ($sql, @bind) = $new_sqla->insert(...); # or, if there is only one table expression my $new_sqla = $sqla->with_recursive( -table => $CTE_table_name, -columns => \@CTE_columns, -as_select => \%select_args, );
Returns a new instance with an encapsulated common table expression (CTE), i.e. a kind of local view that can be used as a table name for the rest of the SQL statement -- see <https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL> for an explanation of such expressions, or, if you are using Oracle, see the documentation for so-called subquery factoring clauses in SELECT statements.
Further calls to "select", "insert", "update" and "delete" on that new instance will automatically prepend a "WITH" or "WITH RECURSIVE" clause before the usual SQL statement.
Arguments to "with_recursive()" are expressed as a list of arrayrefs; each arrayref corresponds to one table expression, with the following named parameters :
($sql, @bind) = $sqla->with_recursive( -table => ..., -as_select => ..., -final_clause => "CYCLE x SET is_cycle TO '1' DEFAULT '0'", )->select(...);
If there is only one table expression, its arguments can be passed directly as an array instead of a single arrayref.
my $sql = $sqla->table_alias($table_name, $alias);
Returns the SQL fragment for aliasing a table. If $alias is empty, just returns $table_name.
Like "table_alias", but for column aliasing.
($sql, @bind) = $sqla->limit_offset($limit, $offset);
Generates "($sql, @bind)" for a LIMIT-OFFSET clause.
my $join_info = $sqla->join( <table0> <join_1> <table_1> ... <join_n> <table_n> ); my $sth = $dbh->prepare($join_info->{sql}); $sth->execute(@{$join_info->{bind}}) while (my ($alias, $aliased) = each %{$join_info->{aliased_tables}}) { say "$alias is an alias for table $aliased"; }
Generates join information for a JOIN clause, taking as input a collection of joined tables with their join conditions. The following example gives an idea of the available syntax :
($sql, @bind) = $sqla->join(qw[ Table1|t1 ab=cd Table2|t2 <=>{ef>gh,ij<kl,mn='foobar'} Table3 =>{t1.op=qr} Table4 ]);
This will generate
Table1 AS t1 INNER JOIN Table2 AS t2 ON t1.ab=t2.cd INNER JOIN Table3 ON t2.ef>Table3.gh AND t2.ij<Table3.kl AND t2.mn=? LEFT JOIN Table4 ON t1.op=Table4.qr
with one bind value "foobar".
More precisely, the arguments to "join()" should be a list containing an odd number of elements, where the odd positions are table specifications and the even positions are join specifications.
Table specifications
A table specification for join is a string containing the table name, possibly followed by a vertical bar and an alias name. For example "Table1" or "Table1|t1" are valid table specifications.
These are converted into internal hashrefs with keys "sql", "bind", "name", "aliased_tables", like this :
{ sql => "Table1 AS t1" bind => [], name => "t1" aliased_tables => {"t1" => "Table1"} }
Such hashrefs can be passed directly as arguments, instead of the simple string representation.
Join specifications
A join specification is a string containing an optional join operator, possibly followed by a pair of curly braces or square brackets containing the join conditions.
Default builtin join operators are "<=>", "=>", "<=", "==", corresponding to the following SQL JOIN clauses :
'<=>' => '%s INNER JOIN %s ON %s', '=>' => '%s LEFT OUTER JOIN %s ON %s', '<=' => '%s RIGHT JOIN %s ON %s', '==' => '%s NATURAL JOIN %s', '>=<' => '%s FULL OUTER JOIN %s ON %s',
This operator table can be overridden through the "join_syntax" parameter of the "new" method.
The join conditions are a comma-separated list of binary column comparisons, like for example
{ab=cd,Table1.ef<Table2.gh}
Table names may be explicitly given using dot notation, or may be implicit, in which case they will be filled automatically from the names of operands on the left-hand side and right-hand side of the join.
Strings within quotes will be treated as bind values instead of column names; pairs of quotes within such values become single quotes. Ex.
{ab=cd,ef='foo''bar',gh<ij}
becomes
ON Table1.ab=Table2.cd AND Table1.ef=? AND Table1.gh<Table2.ij # bind value: "foo'bar"
In accordance with SQL::Abstract common conventions, if the list of comparisons is within curly braces, it will become an "AND"; if it is within square brackets, it will become an "OR".
Join specifications expressed as strings are converted into internal hashrefs with keys "operator" and "condition", like this :
{ operator => '<=>', condition => { '%1$s.ab' => {'=' => {-ident => '%2$s.cd'}}, '%1$s.ef' => {'=' => {-ident => 'Table2.gh'}}}, }
The "operator" is a key into the "join_syntax" table; the associated value is a "sprintf" format string, with placeholders for the left and right operands, and the join condition. The "condition" is a structure suitable for being passed as argument to "where" in SQL::Abstract. Places where the names of left/right tables (or their aliases) are expected should be expressed as "sprintf" placeholders, i.e. respectively "%1$s" and "%2$s". Usually the right-hand side of the condition refers to a column of the right table; in such case it should not belong to the @bind list, so this is why we need to use the "-ident" operator from SQL::Abstract. Only when the right-hand side is a string constant (string within quotes) does it become a bind value : for example
->join(qw/Table1 {ab=cd,ef='foobar'}) Table2/)
is parsed into
[ 'Table1', { operator => '<=>', condition => { '%1$s.ab' => {'=' => {-ident => '%2$s.cd'}}, '%1$s.ef' => {'=' => 'foobar'} }, }, 'Table2', ]
Hashrefs for join specifications as shown above can be passed directly as arguments, instead of the simple string representation. For example the DBIx::DataModel ORM uses hashrefs for communicating with "SQL::Abstract::More".
joins with USING clause instead of ON
In most DBMS, when column names on both sides of a join are identical, the join can be expressed as
SELECT * FROM T1 INNER JOIN T2 USING (A, B)
instead of
SELECT * FROM T1 INNER JOIN T2 ON T1.A=T2.A AND T1.B=T2.B
The advantage of this syntax with a USING clause is that the joined columns will appear only once in the results, and they do not need to be prefixed by a table name if they are needed in the select list or in the WHERE part of the SQL.
To express joins with the USING syntax in "SQL::Abstract::More", just mention the column names within curly braces, without any equality operator. For example
->join(qw/Table1 {a,b} Table2 {c} Table3/)
will generate
SELECT * FROM Table1 INNER JOIN Table2 USING (a,b) INNER JOIN Table3 USING (c)
In this case the internal hashref representation has the following shape :
{ operator => '<=>', using => [ 'a', 'b'], }
When they are generated directy by the client code, internal hashrefs must have either a "condition" field or a "using" field; it is an error to have both.
Return value
The structure returned by "join()" is a hashref with the following keys :
my $conditions = $sqla->merge_conditions($cond_A, $cond_B, ...);
This utility method takes a list of ""where"" conditions and merges all of them in a single hashref. For example merging
( {a => 12, b => {">" => 34}}, {b => {"<" => 56}, c => 78} )
produces
{a => 12, b => [-and => {">" => 34}, {"<" => 56}], c => 78});
$sqla->bind_params($sth, @bind);
For each $value in @bind:
$sth->bind_param_inout($index, $value, $INOUT_MAX_LEN)
(see "bind_param_inout" in DBI). $INOUT_MAX_LEN defaults to 99, which should be good enough for most uses; should you need another value, you can change it by setting
local $SQL::Abstract::More::INOUT_MAX_LEN = $other_value;
$sth->bind_param($index, $value);
This method is useful either as a convenience for Oracle statements of shape "INSERT ... RETURNING ... INTO ..." (see "insert" method above), or as a way to indicate specific datatypes to the database driver.
my ($method, @args) = $sqla->is_bind_value_with_type($value);
If $value is a ref to a pair "[\%args, $orig_value]" :
Otherwise, return "()".
At places where SQL::Abstract would expect a plain value, "SQL::Abstract::More" also accepts a pair, i.e. an arrayref of 2 elements, where the first element is a type specification, and the second element is the value. This is convenient when the DBD driver needs additional information about the values used in the statement.
The usual type specification is a hashref "{dbd_attrs => \%type}", where "\%type" is passed directly as third argument to "bind_param" in DBI, and therefore is specific to the DBD driver.
Another form of type specification is "{sqlt_size => $num}", where $num will be passed as buffer size to "bind_param_inout" in DBI.
Here are some examples
($sql, @bind) = $sqla->insert( -into => 'Foo', -values => {bar => [{dbd_attrs => {ora_type => ORA_XMLTYPE}}]}, ); ($sql, @bind) = $sqla->select( -from => 'Foo', -where => {d_begin => {">" => [{dbd_attrs => {ora_type => ORA_DATE}}, $some_date]}}, );
When using this feature, the @bind array will contain references that cannot be passed directly to DBI methods; so you should use "bind_params" from the present module to perform the appropriate bindings before executing the statement.
my $clone = SQL::Abstract::More::shallow_clone($some_object, %override);
Returns a shallow copy of the object passed as argument. A new hash is created with copies of the top-level keys and values, and it is blessed into the same class as the original object. Not to be confused with the full recursive copy performed by "clone" in Clone.
The optional %override hash is also copied into $clone; it can be used to add other attributes or to override existing attributes in $some_object.
if (SQL::Abstract::More::does $ref, 'ARRAY') {...}
Very cheap version of a "does()" method, that checks whether a given reference can act as an ARRAY, HASH, SCALAR or CODE. This was designed for the limited internal needs of this module and of DBIx::DataModel; for more complete implementations of a "does()" method, see Scalar::Does, UNIVERSAL::DOES or Class::DOES.
Laurent Dami, "<laurent dot dami at cpan dot org>"
You can find documentation for this module with the perldoc command.
perldoc SQL::Abstract::More
The same documentation is also available at <https://metacpan.org/module/SQL::Abstract::More>
Copyright 2011-2022 Laurent Dami.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
2022-07-27 | perl v5.34.0 |