invert needs a Pair? - raku

The invert method for Lists should return the inverted sequence, or at least that's what the source seems to imply. However:
say (1,3,2).invert
fails with:
(exit code 1) Type check failed in invert; expected Pair but got Int (1)␤ in block <unit>
The documentation does not help, because it's LTA in this area (and missing for List). But the source code does not admit any ambiguous interpretation. Is there anything I'm missing here?

Perhaps you intended to use the reverse method. Well, if you want the result to be (2,3,1).
It looks like the documentation of List.invert is indeed missing. It is intended to work only on a List that consists of Pairs. I'll write that up now.
EDIT: doc added with https://github.com/perl6/doc/commit/0ee3245776

In Perl6, operators subroutines and methods have a singular purpose for a given name.
In the case of the infix + operator it is to add two numbers. So if it is given something that is not a number it tries to turn it into a number before adding.
In the case of the .invert method, its fundamental purpose is to invert a Pair object. That is swap the .key and the .value of a Pair.
So everywhere that .invert can be used, it does so in the way that is most like inverting a Pair object.
On a Pair object with a singular .value, it swaps the key and the value.
say ('a' => 'A').invert;
# (A => a)
If the .value is not singular it gives you a sequence where each value is now the key of its own Pair.
say ('a' => ('A', 'B')).invert;
# (A => a B => a)
Note that .invert always returns a sequence to be consistent. Even on that first example.
On a Hash it does it on all of the key / value pairs.
say %( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).invert.sort;
# (A => 1 A => a B => a B => b)
On a List, it could do it one of two ways.
It could use the index as the key, exactly like .antipairs does.
say ( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).antipairs.sort;
# ((1 => A) => 2 (a => (A B)) => 0 (b => B) => 1)
say ( 'a', 'b', 'c' ).antipairs;
# (a => 0 b => 1 c => 2)
It could go through each of the Pairs in the list like it currently does.
say ( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).invert.sort
(A => 1 A => a B => a B => b)
Since .antipairs already works like .pairs except the opposite, there is really no reason for .invert to also work like it. That would have also made .invert less like a Pair method.
This also has the nice effect that you can get the Pairs from a Hash as a list then call .invert on it, and it will work like just calling .invert on the Hash directly.
say %( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).invert.sort;
# (A => 1 A => a B => a B => b)
say %( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).list.invert.sort;
# (A => 1 A => a B => a B => b)
It also means that you can call .invert several times and it stays consistent.
say %( 'a' => ('A','B'), 'b' => 'B', 1 => 'A' ).invert.invert.invert.sort;
# (A => 1 A => a B => a B => b)

Related

NEST (ElasticSearch) Terms Aggregation does not acknowledge the Query

I have a search request as below:
var response = client.Search<ProductElastic>(s => s
.Query(q => q
.Bool(b => b
.Should(mu => mu
.Match(m => m
.Field(f => f.title)
.Boost(1.5)
.Query(inputfilter.q)
), mu => mu
.Match(m => m
.Field(f => f.content)
.Query(inputfilter.q)
)
)
)
)
.Aggregations(a => a
.Terms("doctype_i", he => he
.Field(g => g.doctype_i)
)
.Terms("category_i", e => e
.Field(ge => ge.category_i)
.Size(100)
)
)
);
inputfilter.q holds the search term. When inputfilter.q is null it returns all the results and my aggregation bucket numbers are accurately represented. When inputfilter.q is defined (ex. searching for "test") it refines the results set, however, my aggregation buckets still give me the full numbers as before, as if nothing was searched for. For the record, doctype_i and category_i are integer type fields in Elastic.
How do I make my .Aggregations acknowledge the .Query so the aggregation buckets reflect numbers based on the results set?

Mix syntax for Boolean elements

If I try to declare a Mix with Boolean components:
my $mix= (True => 0.3, False => 0.7).Mix;
dd $mix; # OUTPUT: «Mix $mix = ("True"=>0.3,"False"=>0.7).Mix␤»
They use Pair syntax, which quotes automatically those bare identifiers. In order to avoid that, you either have to define the Pairs explicitly via Pair.new, or else use the fully qualified name.
my $mix= (Bool::True => 0.3, Bool::False => 0.7).Mix;
Is there any other way of doing that? A simpler way maybe?
You can use anything that isn't seen as a bare-word.
Fully qualified names work.
Bool::True => 1
The reason they work is bare-words don't have :: in them.
So you can just prepend :: as well.
::True => 1
You can use ::(…)
::(True) => 1
::('True') => 1
::< True > => 1
You can also use () around True.
(True) => 1
You could declare it backwards and use .antipair
( 1 => True ).antipair
( :foo ).antipair # (Bool::True) => 'foo'
If you don't mind getting a sequence you can use .invert, or .antipairs
# Seq.new( Bool::True => 1 )
( 1 => True ).invert
( 1 => True ).antipairs
# Seq.new( Bool::True => 1, Bool::False => 2 )
( 1 => True, 2 => False ).invert
( 1 => True, 2 => False ).antipairs
If True was a subroutine instead of a term, you could append ()
sub True ( --> True ){}
True() => 1
Then there is using Pair.new.
Pair.new( True, 1 )
Using parens as in (True) => 0.3 or the null pseudo-package as in ::True => 0.3 would be another option.

Merge nested params via link_to

I'm using nested params (via ransack nested as q) alongside normal params to build links on a page and am having trouble with getting the two to play nicely when I try and merge the nested params with the other params.
For example if I have:
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
and try and build a link to change the param "lowEquity_true" with
rates_url(params[:q].merge(:lowEquity_true => '0'))
then I end up with the new params below, which looks like its updated q but dropped the rest of the params.
{"lowEquity_true"=>"0", "s"=>"rate asc"}
If I instead try to merge q & merge into the other params it doesn't update q, and just merges what was in q into the other params instead
rates_url(params[:q].merge(:lowEquity_true => '0').merge(params))
{"freq"=>"weekly", "loan_amount"=>"350000", "lowEquity_true"=>"0",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"},
"s"=>"rate asc"}
I've tried all sorts of various combinations and don't appear to be getting anywhere so am sure that I'm missing something basic!
You are doing it wrong.
Let me explain with an example :
params = {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
At this point, params[:q] is
{:x=>24, :y=>25}
If I do,
params[:q].merge(:x => 99)
then my params[:q] will become
{:x=>99, :y=>25}
and this is what you are supplying as an argument to rates_url(params[:q].merge(:lowEquity_true => '0'))
that's why only {"lowEquity_true"=>"0", "s"=>"rate asc"} is passed to rates_url as parameters.
Now, if you do something like
params[:q].merge(:x => 99).merge(params)
then params[:q].merge(:x => 99) gives you {:x=>99, :y=>25} and then it merges {:x=>99, :y=>25} into the original params {:a => 1, :b => 2, :q => {:x => 24, :y => 25}}
, so this results into
{:x=>99, :y=>25, :a=>1, :b=>2, :q=>{:x=>24, :y=>25}}
Now, let me explain you what you should do :-
You params is
{"freq"=>"weekly", "loan_amount"=>"350000",
"q"=>{"lowEquity_true"=>"1", "s"=>"rate asc"}}
So, you should do :
params[:q].merge!(:lowEquity_true => '0')
rates_url(params)
That's it
I hope you khow the difference between merge and merge! :-
merge! is destructive, it will modify the original paramter where as merge will not unless you take it in a variable and use it.
Alternatively, if you want to do the same thing stated above in a single line then, just do
rates_url(params.merge!(:q => {:lowEquity_true => '0', "s"=>"rate asc"}))
OR
rates_url(params.merge(:q => params[:q].merge(:lowEquity_true => '0')))

PDO: Passing extra parameters to a prepared statment than needed

Can you send more parameters than needed to a prepared statement using PDO with no undesired side effects?
That mights seem like a strange question but I ask because I have 4 queries in a row which all use similar and different parameters. The relevant parts of the queries:
1st (select, different table to others):
WHERE threadID = :tid
2nd (select):
WHERE user_ID = :u_ID AND thread_ID = :tid
3rd (update if 2nd was successful):
SET time = :current_time WHERE user_ID = :u_ID AND thread_ID = :tid
4th (insert if 2nd was unsuccessful):
VALUES (:u_ID, :tid, :current_time)
Can I declare one array with the three parameters at the beginning and use it for all 4 queries?
To sort out any confusion, the queries would be executed seperately. It is the parameters variable being reused and so that would mean some queries would receive parameters they don't need. So something like:
$parameters = array(':tid' => $tid, ':u_ID' => $u_ID, ':current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute($parameters);
$2nd = $db->prepare($query2);
$2nd->execute($parameters);
$3rd = $db->prepare($query3);
$3rd->execute($parameters);
$4th = $db->prepare($query4);
$4th->execute($parameters);
If I can, should I? Will this slow down or cause security flaws to my database or scripts?
If I can make this question a bit clearer, please ask.
Thank you!
Perhaps the documentation has been updated since this question was first asked, but now it is quite clearly stated "No"
You cannot bind more values than specified; if more keys exist in input_parameters than in the SQL specified in the PDO::prepare(), then the statement will fail and an error is emitted.
These answers should be useful in filtering out the extra parameters.
I know this is already answered and it's only asking about whether you can send extra params, but I thought people might arrive at this question, and want to know how to get around this limitation. Here's the solution I use:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(array_intersect_key($parameters, array_flip(array('tid'))));
$2nd = $db->prepare($query2);
$2nd->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid'))));
$3rd = $db->prepare($query3);
$3rd->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid', 'current_time'))));
$4th = $db->prepare($query4);
$4th->execute(array_intersect_key($parameters, array_flip(array('u_ID', 'tid', 'current_time'))));
That array_interset_key and array_flip maneuver could be extracted to its own function, like:
function filter_fields($params,$field_names) {
return array_intersect_key($params, array_flip($field_names))
}
I just haven't got around to it yet.
The function flips your array of key names, so you have an array with no values, but the right keys. Then intersect filters the first array so you only have the keys that are in both arrays (in this case, only the ones in your array_flipped array). But you get the values for the original array (not the empties). So you make one array of parameters, but specify which params are actually sent to PDO.
So, with the function, you'd do:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(filter_fields($parameters, array('tid')));
$2nd = $db->prepare($query2);
$2nd->execute(filter_fields($parameters, array('u_ID', 'tid')));
$3rd = $db->prepare($query3);
$3rd->execute(filter_fields($parameters, array('u_ID', 'tid', 'current_time')));
$4th = $db->prepare($query4);
$4th->execute(filter_fields($parameters, array('u_ID', 'tid', 'current_time')));
If you have PHP 5.4, you can use the square bracket array syntax, to make it even cooler:
$parameters = array('tid' => $tid, 'u_ID' => $u_ID, 'current_time' => $time);
$1st = $db->prepare($query1);
$1st->execute(filter_fields($parameters, ['tid']));
$2nd = $db->prepare($query2);
$2nd->execute(filter_fields($parameters, ['u_ID', 'tid']));
$3rd = $db->prepare($query3);
$3rd->execute(filter_fields($parameters, ['u_ID', 'tid', 'current_time']));
$4th = $db->prepare($query4);
$4th->execute(filter_fields($parameters, ['u_ID', 'tid', 'current_time']));
I got a chance to test my question, and the answer is you cannot send more parameters than the query uses. You get the following error:
PDOException Object
(
[message:protected] => SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
[string:Exception:private] =>
[code:protected] => HY093
[file:protected] => C:\Destination\to\file.php
[line:protected] => line number
[trace:Exception:private] => Array
(
[0] => Array
(
[file] => C:\Destination\to\file.php
[line] => line number
[function] => execute
[class] => PDOStatement
[type] => ->
[args] => Array
(
[0] => Array
(
[:u_ID] => 1
[:tid] => 1
[:current_time] => 1353524522
)
)
)
[1] => Array
(
[file] => C:\Destination\to\file.php
[line] => line number
[function] => function name
[class] => class name
[type] => ->
[args] => Array
(
[0] => SELECT
column
FROM
table
WHERE
user_ID = :u_ID AND
thread_ID = :tid
[1] => Array
(
[:u_ID] => 1
[:tid] => 1
[:current_time] => 1353524522
)
)
)
)
[previous:Exception:private] =>
[errorInfo] => Array
(
[0] => HY093
[1] => 0
)
)
I don't know a huge amount about PDO, hence my question, but I think that because :current_time is sent but not used and the error message is "Invalid parameter number: parameter was not defined" you cannot send extra parameters which are not used.
Additionally the error code HY093 is generated. Now I can't seem to find any documentation explaining PDO codes anywhere, however I came across the following two links specifically about HY093:
What is PDO Error HY093
SQLSTATE[HY093]
It seems HY093 is generated when you incorrectly bind parameters. This must be happening here because I am binding too many parameters.
executing different type of multiple queries with one execute leads to problems. you can run multiple selects or multiple updates with one execute. For this case to create different prepared statements objects and pass the the parameters accordingly.
// for WHERE threadID = :tid
$st1 = $db->prepare($sql);
$st1->bindParam(':tid', $tid);
$st1->execute();
or
$st1->execute(array(':tid'=>$tid);
// for WHERE user_ID = :u_ID AND thread_ID = :tid
$st2 = $db->prepare($sql);
$st2->bindParam(':u_ID', $u_ID);
$st2->bindParam(':tid', $tid);
$st2->execute();
or
$st2->execute(array(':tid'=>$tid, ':u_ID' => $u_ID);
// for SET time = :current_time WHERE user_ID = :u_ID AND thread_ID = :tid
$st3 = $db->prepare($sql);
$st3->bindParam(':u_ID', $u_ID);
$st3->bindParam(':tid', $tid);
$st3->bindParam(':current_time', $current_time);
$st3->execute();
or
$st3->execute(array(':tid'=>$tid, ':u_ID' => $u_ID, ':current_time' => $current_time);
// for VALUES (:u_ID, :tid, :current_time)
$st4 = $db->prepare($sql);
$st4->bindParam(':u_ID', $u_ID);
$st4->bindParam(':tid', $tid);
$st4->bindParam(':current_time', $current_time);
$st4->execute();
or
$st4->execute(array(':tid'=>$tid, ':u_ID' => $u_ID, ':current_time' => $current_time);

Zend_Db fetchAll() to return values as keys, not as key => value

Is there a way to change the default functionality in Zend_Db fetchall() method, so that it doesn't return this:
[0] => 100000055944231
[1] => 100000089064543
[2] => 100000145893011
[3] => 100000160760965
but this:
[100000055944231]
[100000089064543]
[100000145893011]
[100000160760965]
Although your question is actually flawed (noted by BartekR), I suppose you're trying to receive a simple array, instead of a multidimensional one.
You could do:
$results = $this->db->fetchAll($select);
$values = array_map(function($row) { return $row['column']; }, $results);
This will turn:
array(
array('column' => 'test'),
array('column' => 'test2'),
array('column' => 'test3'),
);
into
array(
'test',
'test2',
'test3'
);
(note; my example only works in PHP5.3+, if you're working with PHP5.2, you can define the function and use it by its name with array_map (e.g. array_map('methodName', $results))
I'm looking for a similar solution, I'm trying to load a field returned by the fetchAll($select) as the array key.. Without looping through the entire resultset.
So:
$results = $this->db->fetchAll($select, <FIELDNAME_TO_MAKE_KEY_OF_RESULTS_ARRAY>);
results[<my fieldname>]->dbfield2;
Further to Pieter's, I'd add the case where the rows are themselves arrays, and not just scalars; it's possible to nest the results, to as many fields as the query contains.
e.g. Here with two levels of nesting, respectively on field1 and field2.
$complex_results = array_map(function($row) { return array($row['field1'] => array($row['field2'] => $row)); }, $results);
As always, each row contains all fields, but $complex_results is indexed by field1, then field2 only.