How to dump the query in Phalcon Framework using Model - database

$content = Content::findFirst([
'conditions' => 'state = :state: AND URLid = :url: AND city = :city:',
'bind' => [
'state' => $geodata_usstates->statecode,
'url' => $company,
'city' => $geodata_geocity->city
]
]);
I want to dump the query generated for this. If I were using Laravel, I would simply do
$content->toSql();
But here I'm using Phalcon. How can I achieve the same thing in Phalcon?

Query is not available in your model. Query is build based on model using query builder, passed to Query instance and executed against your db connection.
What you could do is use the events manager and read using the db:beforeQuery event
Example here https://forum.phalconphp.com/discussion/18371/check-the-connection-before-querying-into-database

I don't believe you can output the complete query, because it's a prepared query - thus the best you'd get is:
SELECT * FROM `content` WHERE state = ? AND URLid = ? AND city = ? LIMIT 1
Personally, I don't bother trying to log queries in code. I've enabled the query log on my MariaDB server, and just check the log. The query logged is guaranteed to be the query run.

Related

Can an aliased query use a contain clause?

I use a union to join two datasets and then the following query to setup for pagination correctly
$paginationQuery = $this->find('all')
->contain(['EmailAddresses' => [
'foreignKey' => false,
'queryBuilder' => function($q) {
return $q->where(['Members__id' => 'EmailAddresses.member_id']);
}
]])
->select( $selectMainUnion )
->from([$this->getAlias() => $query])
->order(['Members__last_name' => 'ASC', 'Members__first_name' => 'ASC']);
I have also tried
$paginationQuery = $this->find('all')
->contain(['EmailAddresses'])
->select( $selectMainUnion )
->from([$this->getAlias() => $query])
->order(['Members__last_name' => 'ASC', 'Members__first_name' => 'ASC']);
and tried
$query->loadInto($query, ['EmailAddresses']); where $query is the result of the union.
Neither of these result in email addresses added to $paginationQuery.
Is there a way to do this?
Adding to clarify the code
$selectMain =['Members.id',
'Members.member_type',
'Members.first_name',
'Members.middle_name',
'Members.last_name',
'Members.suffix',
'Members.date_joined'];
foreach($selectMain as $select) {
$selectMainUnion[] = str_replace('.', '__', $select);
}
$this->hasMany('EmailAddresses', [
'foreignKey' => 'member_id',
'dependent' => true,
]);
Looking at the SQL in DebugKit SQL Log, there is no reference to the EmailAddresses table.
Generally containments do work fine irrespective of the queries FROM clause, whether that's a table or a subquery should be irrelevant. The requirement for this to work however is that the required primary and/or foreign key fields are being selected, and that they are in the correct format.
By default CakePHP's ORM queries automatically alias selected fields, ie they are being selected like Alias.field AS Alias__field. So when Alias is a subquery, then Alias.field doesn't exist, you'd have to select Alias.Alias__field instead. So with the automatic aliases, your select of Members__id would be transformed to Members.Members__id AS Members__Members__id, and Members__Members__id is not something the ORM understands, it would end up as Members__id in your entities, where the eager loader would expect id instead, ie the name of the primary key which is used to inject the results of the queried hasMany associated records (this happens in a separate query), your custom queryBuilder won't help with that, as the injecting happens afterwards on PHP level.
Long story short, to fix the problem, you can either change how the fields of the union queries are selected, ie ensure that they are not selected with aliases, that way the pagination query fields do not need to be changed at all:
$fields = $table->getSchema()->columns();
$fields = array_combine($fields, $fields);
$query->select($fields);
This will create a list of fields in the format of ['id' => 'id', ...], looks a bit whacky, but it works (as long as there's no ambiguity because of joined tables for example), the SQL would be like id AS id, so your pagination query can then simply reference the fields like Members.id.
Another way would be to select the aliases of the subquery, ie not just select Member__id, which the ORM turns into Member__Member__id when it applies automatic aliasing, but use Members.Member__id, like:
[
'Member__id' => 'Members.Member__id',
// ...
]
That way no automatic aliasing takes place, on SQL level it would select the field like Members.Member__id AS Member__id, and the field would end up as id in your entities, which the eager loader would find and could use for injecting the associated records.

CakePHP 3.x Contains GroupBy

I am attempting to do a GroupBy on an associated table via contains -> conditions, however I am getting the following error...
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'group = 'BrandUsers.user_id' AND BrandUsers.brand_id in (1,2,3,4,5,6))' at line 1
with the following query
SELECT
BrandUsers.id AS `BrandUsers__id`,
BrandUsers.user_id AS `BrandUsers__user_id`,
BrandUsers.brand_id AS `BrandUsers__brand_id`,
Users.id AS `Users__id`,
Users.username AS `Users__username`,
Users.email AS `Users__email`,
Users.password AS `Users__password`,
Users.first_name AS `Users__first_name`,
Users.last_name AS `Users__last_name`,
Users.token AS `Users__token`,
Users.token_expires AS `Users__token_expires`,
Users.api_token AS `Users__api_token`,
Users.activation_date AS `Users__activation_date`,
Users.secret AS `Users__secret`,
Users.secret_verified AS `Users__secret_verified`,
Users.tos_date AS `Users__tos_date`,
Users.active AS `Users__active`,
Users.is_superuser AS `Users__is_superuser`,
Users.role AS `Users__role`,
Users.created AS `Users__created`,
Users.modified AS `Users__modified`
FROM
brand_users BrandUsers
INNER JOIN
users Users
ON Users.id =
(
BrandUsers.user_id
)
WHERE
(
group = :c0
AND BrandUsers.brand_id in
(
:c1,
:c2,
:c3,
:c4,
:c5,
:c6
)
)
I have taken a look at the following links, but the above error persists
Group By within contain cakephp
cakephp GROUP BY within containable
Here is my code
$this->paginate = [
'contain' => [
'BrandUsers' => [
'conditions' => [
'group' => 'BrandUsers.user_id'
]
],
'BrandUsers.Users'
]
];
$brands = $this->paginate(
$this->Brands
->find('all')
->where(['Brands.user_id' => $this->Auth->user('id')])
);
As mentioned in the answers/comments to the questions that you've linked, there is no group option for containments, that's true for CakePHP 2.x as well as 3.x, and if there was such an option you would have placed it wrong, as you've nested it inside the conditions option, hence it is being compiled into the queries WHERE clause.
If you need to modify the query used for obtaining containments on the fly, then you can for example pass a callable as known from other query builder methods:
'BrandUsers' => function (\Cake\ORM\Query $query) {
return $query->group('BrandUsers.user_id');
}
or use the finder option to point to a finder that modifies the passed query accordingly:
'BrandUsers' => [
'finder' => 'groupedByUser'
]
It should be noted that grouping only works for HasMany and BelongsToMany associations, as they are not being joined into the main/parent query.
See also
Cookbook > Database Access & ORM > Query Builder > Passing Conditions to Contain
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods

logstach: jdbc_page_size doesn't dump all my data to elastic search

I'd like to export tom_test2 postgresql table to elastic search. The table has 176805 rows:
=> select count(*) from tom_test2;
count
--------
176805
(1 row)
The following logstach conf file import correctly my data to elastic search:
input {
jdbc {
# Postgres jdbc connection string to our database, mydb
jdbc_connection_string => "xxx"
# The user we wish to execute our statement as
jdbc_user => "xxx"
jdbc_password => "xxx"
# The path to our downloaded jdbc driver
jdbc_driver_library => "xxx"
# The name of the driver class for Postgresql
jdbc_driver_class => "org.postgresql.Driver"
# our query
statement => "select * from tom_test2"
}
}
output {
elasticsearch {
hosts => ["xxx"]
index => "tom"
document_type => "tom_test"
}
}
In elastic search:
GET tom/tom_test/_search
"hits": {
"total": 176805,
"max_score": 1,
}
I'm deleting my index in elastic search:
delete tom
And I now would like to do the same operation using jdbc_page_size in case my data becomes bigger, my logstach conf file is now:
input {
jdbc {
# Postgres jdbc connection string to our database, mydb
jdbc_connection_string => "xxx"
# The user we wish to execute our statement as
jdbc_user => "xxx"
jdbc_password => "xxx"
# The path to our downloaded jdbc driver
jdbc_driver_library => "xxx"
# The name of the driver class for Postgresql
jdbc_driver_class => "org.postgresql.Driver"
# our query
statement => "select * from tom_test2"
jdbc_page_size => 1000
jdbc_paging_enabled => true
}
}
output {
elasticsearch {
hosts => ["xxx"]
index => "tom"
document_type => "tom_test"
}
}
My count is now wrong:
GET tom/tom_test/_search
"hits": {
"total": 106174,
"max_score": 1,
}
as 176805-106174=70631 rows are missing
The reason you are facing this - you have ordering problems: your query doesn't controlls the order in which the data is received, and in general postgresql should not guarantie that in unordered consequent paging calls you don't fetch the same data: this produces situation when some data will be not fetched at all, and some data will be fetched multiple times :( even when the data is not modified during these calls, the background vacuum worker may change the order of the data in the physical file, and thus reproduce described situation.
Either add order to your statement SELECT * FROM tom_test2 ORDER BY id and page your data. But be aware: in this case your upload to elasticsearch will not assure the exact replica of the table at moment of time. The cause of that will be, that during logstash processing of consequent paging request the update of data in upcoming page introduced, i.e. you are uploading at the moment page 1 to 10000 and update happened at data on page 10001 and 20000, and then later otherwise... so you have problem in consistency of your data.
Or if you want to fetch all the data and generously use memory on logstash... , then you need to control the jdbc_fetch_size parameter: i.e. you are performing the same SELECT * FROM tom_test2. With this approach you will create a single query resultset, but will "pump" it in pieces, and data modification during your "pumping" will not cause you: you will be fetching the state at the moment of query start.
Because ordering is not guaranteed between queries in jdbc_page_size as WARNED in the documentation of jdbc_paging_enabled.
I recommend using jdbc_fetch_size instead of using jdbc_page_size as the documentation also says that for large result-sets.
P.S: sometimes ;) asking your questions at http://discuss.elastic.co is better answered by elastic maintainers

Pagination of query ignoring the LIMIT clause in CakePHP 3.x

I am trying to create a query that would only return 2 results, and by following the documentation I get the query to run, however the limit is still set to 20 by default.
Here is how the query is built:
$upcomingMeetings = $this->Meetings->find('all')
->where(['Meetings.user_id' => $this->Auth->User('user_id')])
->andWhere(["Meetings.date >= " => date('Y-m-d') ])
->order(['Meetings.date' => 'ASC'])
->limit(2);
The result is being passed to the view like following:
$this->set('upcomingMeetings', $this->paginate($upcomingMeetings));
Here is the query that is being run on the database:
SELECT
Meetings.id AS `Meetings__id`,
Meetings.date AS `Meetings__date`,
Meetings.user_id AS `Meetings__user_id`,
FROM
meetings Meetings
WHERE
(
Meetings.group_id = 7
AND Meetings.date >= '2016-01-14'
)
ORDER BY
Meetings.date ASC
LIMIT
20 OFFSET 0
Any help or guidance is much appreciated.
When paginating a Query object, CakePHP will ignore the limit() clause and use the value defined in the $paginate configuration array instead.
This is what can be concluded after inspecting the source code.
Try adding the following to your controller:
public $paginate = [
'limit' => 2,
];

Cakephp insert sql statement

I'd like to insert data into a mysql table using 'the cakephp way'.
I have a multi-stage program that stores data to a session, and toward the end of the program I'd like to write the session data to the database. I could do this using a standard sql insert statement but would like to know how this should be done using cakephp. (Most of the cakephp doc discusses sending data from a webform, and I'd like to manually submit session data.)
Should I manually format the session data in this format and then send this to the model? And if so, is there a helper function for this?
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)
Yes, that's the way to do it. There's really no need for a helper function, just use the ones you normally would.
$name = 'Foo';
$city = 'Bar';
$this->ModelName->save(
array(
'name' => $name,
'city' => $city
)
);

Resources