|
NAMESQL::Abstract::More - extension of SQL::Abstract with more constructs and more flexible APIDESCRIPTIONThis module generates SQL from Perl data structures. It is a subclass of SQL::Abstract or SQL::Abstract::Classic, 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. SYNOPSISuse SQL::Abstract::More; # will inherit from SQL::Abstract; #or use SQL::Abstract::More -extends => 'Classic'; # will inherit from SQL::Abstract::Classic; 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', ); CLASS METHODSimportThe "import()" method is called automatically when a client writes "use SQL::Abstract::More". It can choose to inherit either from SQL::Abstract or from SQL::Abstract::Classic, according to the following rules :
newmy $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 :
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) }); INSTANCE METHODSselect# 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 :
insert# 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
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 update# 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 delete# 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 with_recursive, withmy $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 :
If there is only one table expression, its arguments can be passed directly as an array instead of a single arrayref. table_aliasmy $sql = $sqla->table_alias($table_name, $alias); Returns the SQL fragment for aliasing a table. If $alias is empty, just returns $table_name. column_aliasLike "table_alias", but for column aliasing.limit_offset($sql, @bind) = $sqla->limit_offset($limit, $offset); Generates "($sql, @bind)" for a LIMIT-OFFSET clause. joinmy $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 :
merge_conditionsmy $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}); bind_params$sqla->bind_params($sth, @bind); For each $value in @bind:
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. is_bind_value_with_typemy ($method, @args) = $sqla->is_bind_value_with_type($value); If $value is a ref to a pair "[\%args, $orig_value]" :
Otherwise, return "()". BIND VALUES WITH TYPESAt 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. UTILITY FUNCTIONSshallow_clonemy $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. does()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. AUTHORLaurent Dami, "<laurent dot dami at cpan dot org>"BUGSPlease report any bugs or feature requests to "bug-sql-abstract-more at rt.cpan.org", or through the web interface at <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=SQL-Abstract-More>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.SUPPORTYou can find documentation for this module with the perldoc command.perldoc SQL::Abstract::More You can also look for information at:
LICENSE AND COPYRIGHTCopyright 2011-2017 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.
Visit the GSP FreeBSD Man Page Interface. |