NAME
SQL::Concat - SQL concatenator, only cares about bind-vars, to write SQL generator
SYNOPSIS
# Functional interface
use SQL::Concat qw/SQL PAR/;
my $composed = SQL(SELECT => "*" =>
FROM => entries =>
WHERE => ("uid =" =>
PAR(SQL(SELECT => uid => FROM => authors =>
WHERE => ["name = ?", 'foo'])))
);
my ($sql, @bind) = $composed->as_sql_bind;
# ==>
# SQL: SELECT * FROM entries WHERE uid = (SELECT uid FROM authors WHERE name = ?)
# BIND: foo
# OO Interface
my $comp = SQL::Concat->new(sep => ' ')
->concat(SELECT => foo => FROM => 'bar');
DESCRIPTION
SQL::Concat is NOT a SQL generator, but a minimalistic SQL fragments concatenator with safe bind-variable handling. SQL::Concat doesn't care anything about SQL but placeholder and bind-variables. Other important topics to generate correct SQL such as SQL syntaxes, SQL keywords, quotes, or even parens are all remained your-side.
In other words, generating correct SQL is all up-to-you users of SQL::Concat. If you don't (want to) learn about SQL, use other SQL generators (e.g. SQL::Maker, SQL::Abstract, ...) instead.
This module only focuses on correctly concatenating SQL fragments with keeping their corresponding bind variables.
What concat() does is...
join($SEP, @ITEMS)
! except it knows about bind variables and placeholders. Default $SEP is a space character ' '
but you can give it as sep => $sep option for new() or constructor argument like SQL::Concat->concat_by($SEP).
- STRING
-
Non-reference values are used just as resulting SQL as-is. This means each given strings are treated as RAW SQL fragment. If you want to use foreign values, you must use next "BIND_ARRAY".
use SQL::Concat qw/SQL/; SQL(SELECT => 1)->as_sql_bind; # SQL: "SELECT 1" # BIND: () SQL(SELECT => 'foo, bar' => FROM => 'baz', "\nORDER BY bar")->as_sql_bind; # SQL: "SELECT foo, bar FROM baz # ORDER BY bar" # BIND: ()
Note:
SQL()
is just a shorthand ofSQL::Concat->new(sep => ' ')->concat( @ITEMS... )
. See SQL() for more equivalent examples. - BIND_ARRAY [$RAW_SQL, @BIND]
-
If item is ARRAY reference, it is treated as BIND_ARRAY. The first element of BIND_ARRAY is treated as RAW SQL. The rest of the elements are pushed into
->{bind}
array. This SQL fragment must contain same number of SQL-placeholders(?
) with corresponding @BIND variables.SQL(["city = ?", 'tokyo'])->as_sql_bind # SQL: "city = ?" # BIND: ('tokyo') SQL(["age BETWEEN ? AND ?", 20, 65])->as_sql_bind # SQL: "age BETWEEN ? AND ?" # BIND: (20, 65)
- SQL::Concat
-
Finally, concat() can accept SQL::Concat instances. In this case,
->{sql}
and->{bind}
are extracted and treated just like "BIND_ARRAY"SQL(SELECT => "*" => FROM => members => WHERE => SQL(["city = ?", "tokyo"]), AND => SQL(["age BETWEEN ? AND ?", 20, 65]) )->as_sql_bind; # SQL: "SELECT * FROM members WHERE city = ? AND age BETWEEN ? AND ?" # BIND: ('tokyo', 20, 65)
Helper methods/functions for complex SQL construction
To build complex SQL, we often need to put parens around some SQL fragments. For example:
SQL(SELECT =>
, SQL(SELECT => "count(*)" => FROM => "foo")
, ","
, SQL(SELECT => "count(*)" => FROM => "bar")
)
# (WRONG) SQL: SELECT SELECT count(*) FROM foo , SELECT count(*) FROM bar
Fortunately, SQL::Concat has ->paren() method, so you can write
SQL(SELECT =>
, SQL(SELECT => "count(*)" => FROM => "foo")->paren
, ","
, SQL(SELECT => "count(*)" => FROM => "bar")->paren
)
# SQL: SELECT (SELECT count(*) FROM foo) , (SELECT count(*) FROM bar)
Or you can use another function PAR().
use SQL::Concat qw/SQL PAR/;
SQL(SELECT =>
, PAR(SELECT => "count(*)" => FROM => "foo")
, ","
, PAR(SELECT => "count(*)" => FROM => "bar")
)
You may feel ","
is ugly. In this case, you can use CSV().
use SQL::Concat qw/SQL PAR CSV/;
SQL(SELECT =>
, CSV(PAR(SELECT => "count(*)" => FROM => "foo")
, PAR(SELECT => "count(*)" => FROM => "bar"))
)
# SQL: SELECT (SELECT count(*) FROM foo), (SELECT count(*) FROM bar)
You may want to use other separator to compose "UNION" query. For this, use CAT(). This will be useful to compose AND/OR too.
use SQL::Concat qw/SQL CAT/;
CAT(UNION =>
, SQL(SELECT => "*" => FROM => "foo")
, SQL(SELECT => "*" => FROM => "bar"))
)
# SQL: SELECT * FROM foo UNION SELECT * FROM bar
To construct SQL conditionally, you can use Ternary operator in list context as usual.
SQL(SELECT => "*" => FROM => members =>
($name ? SQL(WHERE => ["name = ?", $name]) : ())
)
# SQL: SELECT * FROM members WHERE name = ?
# (or when $name is empty)
# SQL: SELECT * FROM members
You may feel above cumbersome. If so, you can try another helper OPT() and PFX().
use SQL::Concat qw/SQL PFX OPT/;
SQL(SELECT => "*" => FROM => members =>
PFX(WHERE => OPT("name = ?", $name))
)
Complex example
use SQL::Concat qw/SQL PAR OPT CSV/;
sub to_find_entries {
my ($tags, $limit, $offset, $reverse) = @_;
my $pager = OPT("limit ?", $limit, OPT("offset ?", $offset));
my ($sql, @bind)
= SQL(SELECT => CSV("datetime(ts, 'unixepoch', 'localtime') as dt"
, qw/eid path/)
, FROM => entrytext =>
, ($tags
? SQL(WHERE => eid =>
IN => PAR(SELECT => eid =>
FROM =>
PAR(CAT("\nINTERSECT\n" => map {
SQL(SELECT => DISTINCT => "eid, ts" =>
FROM => entry_tag =>
WHERE => tid =>
IN => PAR(SELECT => tid =>
FROM => tag =>
WHERE => ["tag glob ?", lc($_)]))
} @$tags))
, "\nORDER BY"
, CSV(map {$reverse ? "$_ desc" : $_} qw/ts eid/)
, $pager))
: ())
, "\nORDER BY"
, CSV(map {$reverse ? "$_ desc" : $_} qw/fid feno/)
, ($tags ? () :$pager)
)->as_sql_bind;
}
FUNCTIONS
SQL( @ITEMS... )
Equiv. of
SQL::Concat->concat( @ITEMS... )
SQL::Concat->concat_by(' ', @ITEMS... )
SQL::Concat->new(sep => ' ')->concat( @ITEMS... )
CSV( @ITEMS... )
Equiv. of SQL::Concat->concat_by(', ', @ITEMS... )
Note: you can use "," anywhere in concat() items. For example, you can write SQL(SELECT => "x, y, z")
instead of SQL(SELECT => CSV(qw/x y z/))
.
CAT($SEP, @ITEMS... )
Equiv. of SQL::Concat->concat_by($SEP, @ITEMS... )
, except $SEP
is wrapped by whitespace when necessary.
XXX: Should I use "\n"
as wrapping char instead of " "
?
PAR( @ITEMS... )
Equiv. of SQL( ITEMS...)->paren
PFX($ITEM, @OTHER_ITEMS...)
Prefix $ITEM
only when @OTHER_ITEMS
are not empty. Usually used like PFX(WHERE => ...conditional...)
.
OPT(RAW_SQL, VALUE, @OTHER_ITEMS...)
If VALUE is defined, (SQL([$RAW_SQL, $VALUE]), @OTHER_ITEMS)
are returned. Otherwise empty list is returned.
This is designed to help generating "LIMIT ? OFFSET ?"
.
METHODS
SQL::Concat->new(%args)
Constructor, inherited from MOP4Import::Base::Configure.
Options
Following options has their getter. To set these options after new, use "configure" in MOP4Import::Base::Configure method.
- sep
-
Separator, used in concat().
- sql
-
SQL, constructed when concat() is called. Once set, you are not allowed to call "concat" again.
- bind
-
Bind variables, constructed when "BIND_ARRAY" is given to concat().
SQL::Concat->concat( @ITEMS... )
Central operation of SQL::Concat. It basically does:
$self->{bind} = [];
foreach my MY $item (@_) {
next unless defined $item;
if (not ref $item) {
push @sql, $item;
} else {
$item = SQL::Concat->of_bind_array($item)
if ref $item eq 'ARRAY';
$item->validate_placeholders;
push @sql, $item->{sql};
push @{$self->{bind}}, @{$item->{bind}};
}
}
$self->{sql} = join($self->{sep}, @sql);
SQL::Concat->concat_by($SEP, @ITEMS)
Equiv. of SQL::Concat->new(sep => $SEP)->concat( @ITEMS... )
paren()
Equiv. of $obj->format('(%s)')
.
format_by($FMT)
Apply sprintf($FMT, $self->{sql})
. This will create a clone of $self.
as_sql_bind()
Extract $self->{sql}
and @{$self->{bind}}
. If caller is scalar context, wrap them with []
.
LICENSE
Copyright (C) KOBAYASI, Hiroaki.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
AUTHOR
KOBAYASI, Hiroaki <hkoba @ cpan.org>