Perl Catalyst: Resultset and Relations - perl

I have two tables in my database and one of the tables is associated with my Accounts table.
So in my Schema Result for Account.pm I added the following line.
__PACKAGE__->has_many('subjects', 'MyApp::DBIC::Schema::Subject', {'foreight.account_id' => 'self.account_id'});
Then in my controller I make a search like this.
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
rows => '3',
},
{
order_by => 'first name ASC',
page => 1,
rows => 10,
}
);
It does not output any errors, but I can't figure out how to output the results on my view file. Is this a correct method of making relations between two tables?
My goal: provided a search_term, search two tables and output the result in view file. My SQL would look something like this:
SELECT FROM Accounts,Subjects WHERE Accounts.firstname=$search_term OR Subjects.subject_title=$search_term LEFT JOIN Subjects ON Accounts.account_id=Subject.account_id
And would want to output the result in view file, as I stated above.
I am fairly new to Perl and some of the documentations don't make that much sense to me, still. So any help and tips are appreciated.

The join looks OK to me, but it would make sense to try a simplified version without the join to check that everything else is OK.
The behaviour of DBIx::Class::ResultSet::search differs depending on the context in which it's called. If it's called in list context then it executes the database query and returns an array of MyApp::DBIC::Schema::Account objects. For example:
my #accounts = $c->model('DB::Account')->search();
In your case you're calling search in scalar context, which means that rather than returning an array it will return a DBIx::Class::ResultSet object (or a subclass thereof), and crucially it won't actually execute a db query. For that to happen you need to call the all method on your resultset. So, assuming you're using the default template toolkit view you probably want something like this:
[% FOREACH search_result IN search_results.all %]
[% search_result.first_name %]
[% END %]
This 'lazy' behaviour of DBIx::Class is actually very useful, and in my opinion somewhat undersold in the documentation. It means you can keep a resultset in a variable and keep executing different search calls on it without actually hitting the DB, it can allow much nicer code in cases where you want to conditionally build up a complex query. See the DBIx::Class::Resultset documentation for further details.

You have error in your query:
Try:
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
order_by => 'firstname ASC',
page => 1,
rows => 10,
}
);

Related

Move existing pages into newly created pages with TYPO3 DataHandler

I am using the DataHandler to create and move pages like in this snippet. While new pages are created fine, existing subpages are not moved into their newly created parents.
This creates the new page but does not move the existing page
$data = [
'pages' => [
'NEW_IT' => [
'pid' => 1,
'hidden' => false,
'title' => 'IT',
],
591 => [
// this is not set
'pid' => 'NEW_IT',
// but this is set
'title' => 'I am a child of IT',
],
]
];
I tried ['pages'][591]['move'] = 'NEW_IT' but also to no avail.
I also tried '591' instead of 591, dataHandler->reverseOrder = true and dataHandler->copyTree = true.
dataHandler->errorLog is empty.
In contrary, this works (new page into new page)
$data = [
'pages' => [
'NEW_IT' => [
'pid' => 1,
'hidden' => false,
'title' => 'IT',
],
'NEW_IT_SUB' => [
'pid' => 'NEW_IT',
],
]
];
Also I wonder which IDs (NEW<any string> vs. NEW<base64> etc.) are acceptable as I did not find anything in the documentation and the examples use different styles. The "must be unique" is obvious. But I don't get why some people generate UUIDs there.
References
https://docs.typo3.org/m/typo3/reference-coreapi/9.5/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
EDIT: I opened a forge ticket: https://forge.typo3.org/issues/90939
I have checked the code / DataHandler in v8/v9 and master. The corresponding logic in the DataHandler has not changed for that.
I have created a test case and xdebugged through it, also i was pretty sure after looking into the code, what happens. But have done this to validate that "it is working the way i think the code tells" - although it is not the way you expected. And I'm too. Remembering that I had such an issue last year through a migration script, but did not digged deeper into it (because of time), i changed it and made this as loops.
Creating page => retrieving the replaced id => using it for the other data commands.
The datahandler loops through each record for a table in the provided dataset array.
foreach ($this->datamap[$table] as $id => $incomingFieldArray) { ... }
First it prepares some stuff, and calling registered processDatamap_preProcessFieldArray hooks, it checks the given $id
// Is it a new record? (Then Id is a string)
if (!MathUtility::canBeInterpretedAsInteger($id)) { ... } else { ... }
If the id is not an integer or an integer string, it executed the true-branch, otherwise the false-branch. So long, so good.
The TRUE-Branch handles creating a new record (page, tt_content, ...), adding the replacement for the provided NEWxxx as substituion and so on.
It also checks, if a 'pid' field is in the record, and if it contains with NEW. If it is so, it replaces NEWxxx with the uid of the previously created record.
On the otherhand, if $id is not a integer or a integer string, it assumes it is an existing record. (FALSE-Branch). On this branch there is no checking for 'pid', if it contains NEW and looking up for replacement.
But .. why is there no error ? If it did not replace it, it should crash or someting like that ? - Yes, that is what we would assume - at least for that.
'pid' is always ignored in appling to the record ( see method fillInFieldArray() )
switch ($field) {
case 'uid':
case 'pid':
// Nothing happens, already set
break;
...
}
and so .. as long as you do not provide further fields/values in the second page array ( that with the integer id and the pid with NEW from the initial), it has nothing to do. Nothing to do is not an error - and so, it do nothing and even do not notify anything about it.
Is it a bug ? Don't know, personally I would say yes. But maybe this should be created as an issue, and eventually discussed with the core developer/other contributers.
Maybe there were/are reasons WHY it only do this one run.
Either the documentation should be changed to make clear, that 'pid' replacement only works, id $id => [] is also a new / playholder id .. or you have to do it in 2 rounds.
Or .. put as as feature/bug to the issue tracker, and let's discuss it. And maybe giv it a try to implement it in the FALSE/existing record branch too. But let's heare some other opionons about it.
If others thing this should be also be done for the existing records in the same round, I would take the time tommorrow/at the weekend to provide an patch for it / the issue. (Would be fun to fight with the datahandler ).

Ordering query using lambda expression and Include method

I am working on a Entity-Framework-Core 2.0 query. The query needs to sort 2 tables by the "order" field. So far that's what I have:
return await _context.FieldsetGroup
.Include(e => e.Fieldsets.OrderBy(o => o.Order))
.ThenInclude(e => e.FieldsetFields.OrderBy(o => o.Field.Order))
.ThenInclude(e => e.Field)
.FirstOrDefaultAsync(fsg => fsg.FieldsetGroupId == fieldSetGroupId);
This query returns an exception:
"The property expression 'e => {from Fieldset o in e.Fieldsets orderby [o].Order asc select [o]}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393."
How can I sort the 2 tables?
One of the slower parts of database queries is the transport of your selected data from the DBMS to your local process. Hence it is wise to limit the amount of transferred data.
Apparently your FieldSetGroup has zero or more FieldSets. Each FieldSet belongs to exactly one FieldsetGroup. This is identified by the foreign key FieldSetGroupId. The value of this field equals the Id of the FieldSetGroup.
So if you have FieldSetGroupwith Id = 10, and this FieldSetGroup has, 1000 FieldSets, then every FieldSet will have a value of foreign key FieldSetGroupId of 10. No need to transfer this value 1000 times.
Advice: To limit the amount of transferred data, avoid transferring more data than needed, use Select instead of Include and select only the data you actually plan to
use. Use Include if you plan to update the fetched data.
If you use Select, you can order whatever you want:
var result = dbContext.FieldsetGroup
.Where((fieldSetGroup => fieldSetGroup.FieldsetGroupId == fieldSetGroupId)
.Select(fieldSetGroup => new
{
... // select the fieldSetGroup properties you plan to use
FieldSets = fieldSetGroup.FieldSets
.OrderBy(fieldSet => fieldSet.Order)
.Select(fieldSet => new
{
... // only select the fieldSet properties you plan to use
FieldSetFields = fieldSet.FieldSetFields
.OrderBy(fieldSetField => fieldSetField.Order)
.Select(fieldSetField => new
{
...
})
.ToList(),
})
.ToList(),
})
.ToList(),
})
.FirstOrDefault();
You cannot do sorting (OrderBy) inside the Include method. Sort the data after the query.

Does Npgsql support projection queries?

If I do this...
Context.Orders.Select(o => o.User.UserId);
... I get an exception because User is null. I can use Include instead,
Context.Orders.Include(o => o.User).Select(o => o.User.UserId);
... but shouldn't User be loaded automatically?
EDIT:
The first snippet of code doesn't work when the Select is applied to the result of a function. Which type should the function return in order to tack the Select onto the database query?
I've tried IEnumerable<Order> and IQueryable<Order>.

DBIx::Class has_many join on substring of a column

I have two class that are linked on
foreign.weirdkey => substr(self.key, 1, 9)
...and cannot for the life of me figure out how to construct the has_many call to indicate this.
The underlying database (a set of Oracle tables) defines no foreign keys, is fixed, and is outside of my control.
I've been through the docs and can't seem to find a syntax that will work within the confines of a manual has_many definition.
Any help would be much appreciated.
something like this should work:
__PACKAGE__->has_many( baubles => 'My::Schema::Result::Thing', sub {
my $args = shift;
return ({
"$args->{foreign_alias}.weirdkey" => \"substr($args->{self_alias}.key, 1, 9)",
},
$args->{self_rowobj} && {
"$args->{foreign_alias}.weirdkey" => substr($args->{self_rowobj}->key, 1, 9)
})
});
Note that I use perl's substr if you have the current row object, so then the join will collapse into merely a where clause and won't use the database for the string munging. Remove that or fiddle with it if you have problems. DBIC_TRACE should make it clear what goes on.
Documentation here: https://metacpan.org/pod/DBIx::Class::Relationship::Base#condition

Can I search resultset from within a toolkit template file?

I use Catalyst and put a resultset into the stash for TT to access:
$c->stash->{tournament} = $c->model('DB::Tournament')->find($id);
This class has a relationship with "tournament_participant" which I access from inside a TT page like this:
[% FOREACH participant IN tournament.tournament_participants -%]
problem is, I want to sort the result by a column like this:
[% FOREACH participant IN tournament.tournament_participants.search( {}, { sort_by => 'position' } ) -%]
but the above does not work (nothing is returned). Is this possible to do?
This should do the trick (assuming the relationship really is tournament_participants (which seems a little redundant and ungainly; tournament.participants feels more natural and is easy to change in the result class if desired)–
[% FOR participant IN tournament.search_related("tournament_participants", {}, { sort_by => 'position' } ) -%]
Doc: DBIx::Class::Relationship::Base.