[Dbix-class] SQLA refactor: proposal
Darren Duncan
darren at DarrenDuncan.net
Tue Nov 20 22:23:44 GMT 2007
This is an initial short reply, and I'll add more over time and/or in
response to the comments/questions of others.
My first general comment is that probably anything which can be
expressed as a pure functional expression, which generally is any SQL
SELECT, is probably the least complicated to translate, because it
isn't modifying database state.
At 8:14 PM +0000 11/20/07, Matt S Trout wrote:
>Far, far too much of the code is currently in _recurse_where, and the DWIM
>AST provides quite a few ways to say the same thing. From a few hours'
>thought, I think dduncan's
>
>http://search.cpan.org/~duncand/Language-MuldisD-0.11.0/lib/Language/MuldisD/Core.pod
>
>provides most of what we'll need - certainly the SELECT list, simple FROM
>and WHERE stuff is covered.
To help out with understanding this stuff, I'll list a few specific
Muldis D operators that correspond to different parts of a SQL
SELECT; the syntax is representative rather than exact:
----
To return a rowset of all rows in a table.
SQL:
SELECT * FROM tab1
Muldis D:
tab1
----
To return a scalar count of rows in a table.
SQL (as a scalar query):
(SELECT COUNT(*) FROM tab1)
Muldis D:
sys.Core.Relation.cardinality( topic => tab1 )
----
To return a rowset having just some columns from all rows of a table.
SQL:
SELECT DISTINCT col1, col2
FROM tab1
Muldis D:
sys.Core.Relation.project(
topic => tab1,
attrs => [col1, col2],
)
----
To return a rowset having all cols of just some rows of a table,
where certain columns match desired values.
SQL:
SELECT *
FROM tab1
WHERE col1 = 'hello' AND col2 = 5
OR col1 = 'world' AND col2 = 7
or:
SELECT *
FROM tab1
WHERE (col1, col2) IN (
SELECT 'hello' AS col1, 5 AS col2
UNION
SELECT 'world' AS col1, 7 AS col2
)
Muldis D:
sys.Core.Relation.semijoin(
source => tab1,
filter => [
{ col1 => 'hello', col2 => 5 },
{ col1 => 'world', col2 => 7 },
],
)
Alternately you can use sys.Core.Relation.semidifference if you
want rows that *don't* match aka NOT IN.
Alternately you can use sys.Core.Relation.restrict for generic
boolean expressions besides matches/not-matches, aka Perl's "grep".
----
To do a natural inner join on 2 tables, say tab1(col1,col2,col3),
tab2(col2,col3,col4), such that the result is
tab(col1,col2,col3,col4).
SQL:
SELECT DISTINCT tab1.*, tab2.col4
FROM tab1 NATURAL INNER JOIN tab2
or:
SELECT DISTINCT tab1.*, tab2.col4
FROM tab1
INNER JOIN tab2 USING (col2, col3)
or:
SELECT DISTINCT tab1.*, tab2.col4
FROM tab1
INNER JOIN tab2 ON tab2.col2 = tab1.col2 AND tab2.col3 = tab1.col3
Muldis D:
sys.Core.Relation.join(
topic => [tab1, tab2],
)
----
To select rows but with some cols renamed:
SQL:
SELECT col1 AS foo, col2, col3 AS bar
FROM tab1
Muldis D:
sys.Core.Relation.rename(
topic => tab1,
map => { col1 => foo, col3 => bar },
)
----
To select rows with extra columns:
SQL:
SELECT tab1.*, 'x' AS a, (tab1.m - tab1.n) AS b FROM tab1
Muldis D
sys.Core.Relation.extend(
topic => tab1,
func => func:{ result {
a => 'x',
b => sys.Core.Int.difference(
minuend => :topic.m,
subtrahend => :topic.n
),
} },
assuming => {},
)
Note that 'extend' works sort of like Perl's "map" operator;
"topic" is the inner function's argument corresponding to "$_", which
is each input row, and "assuming" carries any other arguments that
might be needed.
----
To just select a constant scalar value:
SQL:
(SELECT 'x')
or:
(SELECT 'x' FROM dual)
Muldis D:
'x'
----
More examples are available on demand.
Note that all Muldis D examples above can be nested arbitrarily (any
"tab1" et al can be a subquery rather than a table variable name), so
eg combine a restrict and project in either nesting order to pick
columns and rows both; this extends naturally to both what SQL calls
subqueries or derived tables, and to typical scalar value expressions.
I have not yet demonstrated group-by etc, but they are supported too
by way of operators like sys.Core.Relation.summarize() and
sys.Core.Relation.group().
I also have not yet demonstrated union/intersect/minus, but they are
all represented by sys.Core.Relation.union/intersection/difference().
I have not yet demonstrated hierarchical queries, but they are
represented by sys.Core.Relation.tclose().
As for outer-joins, they have yet no specific operator for them, but
you can achieve the desired effect using your choice of group() or
extend() and union() etc; in the near future I can add operators
specific to outer-joins which essentially are short-hand for another
combination.
As for joins that want to work on matches between columns with
different names (or avoid matches on columns with the same names),
just invoke rename() before join() to get the desired effect; this
includes to make self-joins work as desired.
As for invoking user-defined functions, you do it the same way as
invoking any sys.Core.whatever function, except it is spelled eg
dep.myschema.myfunction().
As for SQL's ORDER and LIMIT clauses, I haven't put analogies of
those in Muldis D yet, but they are pending.
Note that all Muldis D relational operators are set oriented, and
have an implicit SQL DISTINCT clause / GROUP BY all rowset columns.
If you want to express a count of things, use an extra count-value
column rather than storing duplicate rows. Or add some
distinguishing column (eg, a date stamp) to make otherwise duplicate
rows non-duplicated.
>There are a few SQL-y operators such as LIKE or BETWEEN that don't seem to
>be in there, but I don't see why they can't be in sqla.Core.Str.Between
>etc.
If you want to extend the system-defined operators, better namespaces
are the likes of imp.sqla.Str.Between, or sys.sqla.Str.Between, and
the first is preferable. Also, Muldis D differentiates character and
binary strings with Text and Blob respectively, so I recommend using
those rather than Str as names.
Note that Muldis D has distinct function and procedure routines,
unlike Perl 5, and functions can't have side-effects. Between()
would be a function, as would the main body of any Select(), but
insert/update/delete would be a procedure.
As for LIKE, the common case where you just want to see if something
is a substring of something else is covered by
sys.Core.Text.contains(); this has the flexability to specify
at-start/at-end/may-be-middle. But the less common cases covered by
a generic LIKE aren't in Muldis D yet.
As for BETWEEN, there is currently no Muldis D operator specifically
for that, though one could be added easily enough; meanwhile, the
likes of "x >= 4 and x <= 7" works, as does "x IN (4,5,6,7)",
assuming integers; the latter form is semijoin().
>So I think the first stage is to create an SQL::Abstract::Parser that
>parses SQLA syntax into MuldisD (+extra sqla. definitions) and an
>SQL::Abstract::Producer::SQL that converts from MuldisD to SQL, and then
>make core SQL::Abstract use that and pass all tests.
>
>Thoughts?
Superficially, that sounds fine with me. I'll wait for further
comments on this to address them directly.
-- Darren Duncan
More information about the DBIx-Class
mailing list