THERE ARE FOUR LIGHTS
[scpubgit/Q-Branch.git] / lib / SQL / Abstract / Reference.pm
index ebf3407..257fd73 100644 (file)
@@ -371,7 +371,7 @@ Directly appended to the key, remember you need to provide an operator:
 
   # expr
   { id => \[
-        "= seriously(?, ?, ?, ?, ?)",
+        "= seriously(?, ?, ?, ?)",
         "use",
         "-ident",
         "and",
@@ -381,11 +381,11 @@ Directly appended to the key, remember you need to provide an operator:
 
   # aqt
   { -literal =>
-      [ 'id = seriously(?, ?, ?, ?, ?)', 'use', -ident => 'and', '-func' ]
+      [ 'id = seriously(?, ?, ?, ?)', 'use', -ident => 'and', '-func' ]
   }
 
   # query
-  id = seriously(?, ?, ?, ?, ?)
+  id = seriously(?, ?, ?, ?)
   [ 'use', -ident => 'and', '-func' ]
 
 (you may absolutely use this when there's no built-in expression type for
@@ -653,4 +653,499 @@ behaves the same way as the now-directly-supported
   foo
   []
 
+=head2 row
+
+Expands the elements of the value arrayref:
+
+  # expr
+  { -row => [ 1, { -ident => 'foo' }, 2, 3 ] }
+
+  # aqt
+  { -row => [
+      { -bind => [ undef, 1 ] }, { -ident => [ 'foo' ] },
+      { -bind => [ undef, 2 ] }, { -bind => [ undef, 3 ] },
+  ] }
+
+  # query
+  (?, foo, ?, ?)
+  [ 1, 2, 3 ]
+
+=head2 op
+
+If an expander is registered for the op name, delegates to the expander; if
+not, expands the argument values:
+
+  # expr
+  { -op => [ 'ident', 'foo.bar' ] }
+
+  # aqt
+  { -ident => [ 'foo', 'bar' ] }
+
+  # query
+  foo.bar
+  []
+
+  # expr
+  { -op => [ '=', { -ident => 'foo' }, 3 ] }
+
+  # aqt
+  { -op => [ '=', { -ident => [ 'foo' ] }, { -bind => [ undef, 3 ] } ] }
+
+  # query
+  foo = ?
+  [ 3 ]
+
+=head2 func
+
+Expands the argument values:
+
+  # expr
+  { -func => [ 'coalesce', { -ident => 'thing' }, 'fallback' ] }
+
+  # aqt
+  { -func => [
+      'coalesce', { -ident => [ 'thing' ] },
+      { -bind => [ undef, 'fallback' ] },
+  ] }
+
+  # query
+  COALESCE(thing, ?)
+  [ 'fallback' ]
+
+=head2 values
+
+A hashref value is expanded as an expression:
+
+  # expr
+  { -values => { -row => [ 1, 2 ] } }
+
+  # aqt
+  { -values => [
+      { -row => [ { -bind => [ undef, 1 ] }, { -bind => [ undef, 2 ] } ] }
+  ] }
+
+  # query
+  VALUES (?, ?)
+  [ 1, 2 ]
+
+An arrayref value's elements are either expressions or arrayrefs to be
+treated as rows:
+
+  # expr
+  { -values => [ { -row => [ 1, 2 ] }, [ 3, 4 ] ] }
+
+  # aqt
+  { -values => [
+      { -row => [ { -bind => [ undef, 1 ] }, { -bind => [ undef, 2 ] } ] },
+      { -row => [ { -bind => [ undef, 3 ] }, { -bind => [ undef, 4 ] } ] },
+  ] }
+
+  # query
+  VALUES (?, ?), (?, ?)
+  [ 1, 2, 3, 4 ]
+
+=head2 list
+
+Expects a value or an arrayref of values, expands them, and returns just
+the expanded aqt for a single entry or a comma operator for multiple:
+
+  # expr
+  { -list => [ { -ident => 'foo' } ] }
+
+  # aqt
+  { -op => [ ',', { -ident => [ 'foo' ] } ] }
+
+  # query
+  foo
+  []
+
+  # expr
+  { -list => [ { -ident => 'foo' }, { -ident => 'bar' } ] }
+
+  # aqt
+  { -op => [ ',', { -ident => [ 'foo' ] }, { -ident => [ 'bar' ] } ] }
+
+  # query
+  foo, bar
+  []
+
+=head2 between op
+
+The RHS of between must either be a pair of exprs/plain values, or a single
+literal expr:
+
+  # expr
+  { -between => [ 'size', 3, { -ident => 'max_size' } ] }
+
+  # aqt
+  { -op => [
+      'between', { -ident => [ 'size' ] }, { -bind => [ undef, 3 ] },
+      { -ident => [ 'max_size' ] },
+  ] }
+
+  # query
+  ( size BETWEEN ? AND max_size )
+  [ 3 ]
+
+  # expr
+  { size => { -between => [ 3, { -ident => 'max_size' } ] } }
+
+  # aqt
+  { -op => [
+      'between', { -ident => [ 'size' ] }, { -bind => [ 'size', 3 ] },
+      { -ident => [ 'max_size' ] },
+  ] }
+
+  # query
+  ( size BETWEEN ? AND max_size )
+  [ 3 ]
+
+  # expr
+  { size => { -between => \"3 AND 7" } }
+
+  # aqt
+  { -op =>
+      [
+        'between', { -ident => [ 'size' ] },
+        { -literal => [ '3 AND 7' ] },
+      ]
+  }
+
+  # query
+  ( size BETWEEN 3 AND 7 )
+  []
+
+not_between is also expanded:
+
+  # expr
+  { size => { -not_between => [ 3, 7 ] } }
+
+  # aqt
+  { -op => [
+      'not_between', { -ident => [ 'size' ] },
+      { -bind => [ 'size', 3 ] }, { -bind => [ 'size', 7 ] },
+  ] }
+
+  # query
+  ( size NOT BETWEEN ? AND ? )
+  [ 3, 7 ]
+
+=head2 in op
+
+The RHS of in/not_in is either an expr/value or an arrayref of
+exprs/values:
+
+  # expr
+  { foo => { -in => [ 1, 2 ] } }
+
+  # aqt
+  { -op => [
+      'in', { -ident => [ 'foo' ] }, { -bind => [ 'foo', 1 ] },
+      { -bind => [ 'foo', 2 ] },
+  ] }
+
+  # query
+  foo IN ( ?, ? )
+  [ 1, 2 ]
+
+  # expr
+  { bar => { -not_in => \"(1, 2)" } }
+
+  # aqt
+  { -op =>
+      [ 'not_in', { -ident => [ 'bar' ] }, { -literal => [ '1, 2' ] } ]
+  }
+
+  # query
+  bar NOT IN ( 1, 2 )
+  []
+
+A non-trivial LHS is expanded with ident as the default rather than value:
+
+  # expr
+  { -in => [
+      { -row => [ 'x', 'y' ] }, { -row => [ 1, 2 ] },
+      { -row => [ 3, 4 ] },
+  ] }
+
+  # aqt
+  { -op => [
+      'in', { -row => [ { -ident => [ 'x' ] }, { -ident => [ 'y' ] } ] },
+      { -row => [ { -bind => [ undef, 1 ] }, { -bind => [ undef, 2 ] } ] },
+      { -row => [ { -bind => [ undef, 3 ] }, { -bind => [ undef, 4 ] } ] },
+  ] }
+
+  # query
+  (x, y) IN ( (?, ?), (?, ?) )
+  [ 1, 2, 3, 4 ]
+
+=head2 and/or ops
+
+expands the same way as a plain arrayref/hashref expression but with the
+logic type set to the op name.
+
+=head2 is op
+
+Expands is and is_not to null checks, RHS value must be undef:
+
+  # expr
+  { -is => [ 'foo', undef ] }
+
+  # aqt
+  { -op => [ 'is_null', { -ident => [ 'foo' ] } ] }
+
+  # query
+  foo IS NULL
+  []
+
+  # expr
+  { bar => { -is_not => undef } }
+
+  # aqt
+  { -op => [ 'is_not_null', { -ident => [ 'bar' ] } ] }
+
+  # query
+  bar IS NOT NULL
+  []
+
+=head2 ident op
+
+Expands a string ident to an arrayref by splitting on the configured
+separator, almost always '.':
+
+  # expr
+  { -ident => 'foo.bar' }
+
+  # aqt
+  { -ident => [ 'foo', 'bar' ] }
+
+  # query
+  foo.bar
+  []
+
+=head2 value op
+
+Expands to a bind node with the currently applicable column name if known:
+
+  # expr
+  { foo => { '=' => { -value => 3 } } }
+
+  # aqt
+  { -op => [ '=', { -ident => [ 'foo' ] }, { -bind => [ 'foo', 3 ] } ] }
+
+  # query
+  foo = ?
+  [ 3 ]
+
+=head1 Query Types
+
+=head2 select
+
+A select node accepts select, from, where and order_by clauses.
+
+The select clause is expanded as a list expression with a -ident default:
+
+  # expr
+  { -select => { _ => [ 'foo', 'bar', { -count => 'baz' } ] } }
+
+  # aqt
+  { -select => { select => { -op => [
+          ',', { -ident => [ 'foo' ] }, { -ident => [ 'bar' ] },
+          { -func => [ 'count', { -ident => [ 'baz' ] } ] },
+  ] } } }
+
+  # query
+  SELECT foo, bar, COUNT(baz)
+  []
+
+The from clause is expanded as a list expression with a -ident default:
+
+  # expr
+  { -select => {
+      from => [ 'schema1.table1', { -ident => [ 'schema2', 'table2' ] } ]
+  } }
+
+  # aqt
+  { -select => { from => { -from_list => [
+          { -ident => [ 'schema1', 'table1' ] },
+          { -ident => [ 'schema2', 'table2' ] },
+  ] } } }
+
+  # query
+  FROM schema1.table1, schema2.table2
+  []
+
+The where clause is expanded as a plain expression:
+
+  # expr
+  { -select => { where => { foo => 3 } } }
+
+  # aqt
+  { -select => { where => {
+        -op => [ '=', { -ident => [ 'foo' ] }, { -bind => [ 'foo', 3 ] } ]
+  } } }
+
+  # query
+  WHERE foo = ?
+  [ 3 ]
+
+The order_by clause expands as a list expression at top level, but a hashref
+element may be either an expr or a hashpair with key -asc or -desc to indicate
+an order by direction:
+
+  # expr
+  { -select =>
+      { order_by => [ 'foo', { -desc => 'bar' }, { -max => 'baz' } ] }
+  }
+
+  # aqt
+  { -select => { order_by => { -op => [
+          ',', { -ident => [ 'foo' ] }, {
+            -op => [ ',', { -op => [ 'desc', { -ident => [ 'bar' ] } ] } ]
+          }, { -func => [ 'max', { -ident => [ 'baz' ] } ] },
+  ] } } }
+
+  # query
+  ORDER BY foo, bar DESC, MAX(baz)
+  []
+
+=head2
+
+An insert node accepts an into/target clause, a fields clause, a values/from
+clause, and a returning clause.
+
+The target clause is expanded with an ident default.
+
+The fields clause is expanded as a list expression if an arrayref, and
+otherwise passed through.
+
+The from clause may either be an expr, a literal, an arrayref of column
+values, or a hashref mapping colum names to values.
+
+The returning clause is expanded as a list expr with an ident default.
+
+  # expr
+  { -insert => {
+      into => 'foo',
+      returning => 'id',
+      values => { bar => 'yay', baz => 'argh' },
+  } }
+
+  # aqt
+  { -insert => {
+      fields =>
+        { -row => [ { -ident => [ 'bar' ] }, { -ident => [ 'baz' ] } ] },
+      from => { -values => [ { -row => [
+              { -bind => [ 'bar', 'yay' ] },
+              { -bind => [ 'baz', 'argh' ] },
+      ] } ] },
+      returning => { -op => [ ',', { -ident => [ 'id' ] } ] },
+      target => { -ident => [ 'foo' ] },
+  } }
+
+  # query
+  INSERT INTO foo (bar, baz) VALUES (?, ?) RETURNING id
+  [ 'yay', 'argh' ]
+
+  # expr
+  { -insert => {
+      fields => [ 'bar', 'baz' ],
+      from => { -select => { _ => [ 'bar', 'baz' ], from => 'other' } },
+      into => 'foo',
+  } }
+
+  # aqt
+  { -insert => {
+      fields => { -row => [ { -op =>
+              [ ',', { -ident => [ 'bar' ] }, { -ident => [ 'baz' ] } ]
+      } ] },
+      from => { -select => {
+          from => { -ident => [ 'other' ] },
+          select => { -op =>
+              [ ',', { -ident => [ 'bar' ] }, { -ident => [ 'baz' ] } ]
+          },
+      } },
+      target => { -ident => [ 'foo' ] },
+  } }
+
+  # query
+  INSERT INTO foo (bar, baz) SELECT bar, baz FROM other
+  []
+
+=head2 update
+
+An update node accepts update/target (either may be used at expansion time),
+set, where, and returning clauses.
+
+The target clause is expanded with an ident default.
+
+The set clause (if not already a list expr) is expanded as a hashref where
+the keys are identifiers to be set and the values are exprs/values.
+
+The where clauses is expanded as a normal expr.
+
+The returning clause is expanded as a list expr with an ident default.
+
+  # expr
+  { -update => {
+      _ => 'foo',
+      returning => [ 'id', 'baz' ],
+      set => { bar => 3, baz => { baz => { '+' => 1 } } },
+      where => { -not => { -ident => 'quux' } },
+  } }
+
+  # aqt
+  { -update => {
+      returning =>
+        {
+          -op => [ ',', { -ident => [ 'id' ] }, { -ident => [ 'baz' ] } ]
+        },
+      set => { -op => [
+          ',', { -op =>
+              [ '=', { -ident => [ 'bar' ] }, { -bind => [ 'bar', 3 ] } ]
+          }, { -op => [
+              '=', { -ident => [ 'baz' ] }, { -op => [
+                  '+', { -ident => [ 'baz' ] },
+                  { -bind => [ 'baz', 1 ] },
+              ] },
+          ] },
+      ] },
+      target => { -ident => [ 'foo' ] },
+      where => { -op => [ 'not', { -ident => [ 'quux' ] } ] },
+  } }
+
+  # query
+  UPDATE foo SET bar = ?, baz = baz + ? WHERE (NOT quux) RETURNING id, baz
+  [ 3, 1 ]
+
+=head2 delete
+
+delete accepts from/target, where, and returning clauses.
+
+The target clause is expanded with an ident default.
+
+The where clauses is expanded as a normal expr.
+
+The returning clause is expanded as a list expr with an ident default.
+
+  # expr
+  { -delete => {
+      from => 'foo',
+      returning => 'id',
+      where => { bar => { '<' => 10 } },
+  } }
+
+  # aqt
+  { -delete => {
+      returning => { -op => [ ',', { -ident => [ 'id' ] } ] },
+      target => { -op => [ ',', { -ident => [ 'foo' ] } ] },
+      where => { -op =>
+          [ '<', { -ident => [ 'bar' ] }, { -bind => [ 'bar', 10 ] } ]
+      },
+  } }
+
+  # query
+  DELETE FROM foo WHERE bar < ? RETURNING id
+  [ 10 ]
+
 =cut