Custom functions for non-select queries Cake Query Builder - mysql

I would like to use the mariadb INET_ATON() on an insert query with Cakephp Query Builder.
INSERT INTO failed_logins
SET email = 'example#test.com', ip_address = INET_ATON('192.168.0.1'), sent_email = 1;
Then I'd like to retrieve the data with INET_NTOA() in a select query.
SELECT id, email, INET_NTOA(ip_address) AS ip_address, sent_email FROM failed_logins;
How do I use these functions with an insert and select on the Cake Query Builder?
I saw Using SQL functions but couldn't solve my issue.

After a lot of playing around I managed to make it work.
$this->connection->newQuery()->into('failed_logins');
$newIp = $query->func()->inet_aton([$ip]);
$query->insert(['email', 'ip_address', 'sent_email'])->values(
['email' => $email, 'ip_address' => $newIp, 'sent_email' => $sentEmail]
)->execute()->lastInsertId();
Quite complicated and my IDE and PHPStan show me warnings that the function "inet_aton" is not defined.
I would have loved it if in the values() array I could have just done it like ['ip_address' => "INET_ATON($ip)"]. Edit: This is not a good idea see comments. But something similar that stays safe can be done with ->bind() (code snippet below).
Edit: Removed 'literal' from the code snippet (thanks #ndm)
IDE and Analysis Tool - friendly solution
$this->connection->newQuery()->into('failed_logins');
$query->insert(
[
'email',
'ip_address',
'sent_email',
]
)->values(
[
'email' => $email,
'ip_address' => $query->newExpr("INET_ATON(:ip)"),
'sent_email' => $sentEmail,
]
)->bind(':ip', $ip, 'string')->execute()->lastInsertId();

Related

Laravel parameter binding causing occasional MySQL general error

I have an array of integers that need to be inserted as a batch of rows. Each row needs some other data.
$ids = [1,2]
$thing = 1
$now = Carbon::now(); // This is just a timestamp.
$binding_values = trim(str_repeat("({$thing}, ?, '{$now}'),", count($ids)), ',');
The string $binding_values looks like this:
"(1, ?, '2019-01-01 00:00:00'), (1, ?, '2019-01-01 00:00:00')"
Then I prepare my query string and bind the parameters to it. The IGNORE is used because I have a composite unique index on the table. It doesn't seem relevant to the problem though so I've left the details out.
DB::insert("
INSERT IGNORE INTO table (thing, id, created_at)
VALUES {$binding_values}
", $ids);
This works almost all the time but every now and then I get an error SQLSTATE[HY000]: General error: 2031.
Is the way I'm doing this parameter binding some kind of anti-pattern with Laravel? What might the source of this error be?
Because there is no risk of injection in this method and there is no chance that this method would be extended to a use case with a risk of injection, I've modified it to bake in all the parameters and skip parameter binding. I haven't seen any errors so far.
I would still like to know what might cause this behaviour so I can manage it better in the future. I'd be grateful for any insight.
I don't see a big issue with your query other than baking parameters into the query, which is vulnerable to SQL injection.
From what I can see, your problem is that you need INSERT ÌGNORE INTO which is not supported out of the box by Laravel. Have you thought about using a third-party package like staudenmeir/laravel-upsert?
An alternative could be to wrap your query in a transaction and select existing entries first, giving you the chance to not insert them a second time:
$ids = [1, 2];
$thing = 1;
$time = now();
DB::transaction(function () use ($ids, $thing, $time) {
$existing = DB::table('some_table')->whereIn('id', $ids)->pluck('id')->toArray();
$toInsert = array_diff($ids, $existing);
$dataToInsert = array_map(function ($id) use ($thing, $time) {
return [
'id' => $id,
'thing' => $thing,
'created_at' => $time
];
}, $toInsert);
DB::table('some_table')->insert($dataToInsert);
});
This way you will only insert what is not present yet, but you will also stay within the framework capabilities of the query builder. Only downside is that it will be slightly slower due to a second roundtrip to the database.

Yii2 Query not quoting correctly

I run into this problem, time and time again. It would be nice to find out how to build queries properly so I can stop resorting to Yii::$app->db->createCommand() as a workaround.
My Yii2 query:
$users = UserSpatial::find()
->select('user_id, harvesine(y(coordinates), x(coordinates), :lat, :lon) as dist, astext(coordinates)')
->where('st_within(coordinates, envelope(linestring(point(:rlon1, :rlat1), point(:rlon2, :rlat2))))')
->orderBy('st_distance(point(:lon, :lat), coordinates)')
->params([
':lon' => $geo->lon,
':lat' => $geo->lat,
':rlon1' => $rlon1,
':rlat1' => $rlat1,
':rlon2' => $rlon2,
':rlat2' => $rlat2
])
->all();
The generated query ends up with backticks in all the wrong places and, oddly enough, not all parameters were backticked (sorry but you'll need to look closely for the misplaced backticks because I didn't know how best to highlight the incorrect placements):
SELECT \`user_id\`, harvesine(y(coordinates), x(coordinates), \`32.7699547\`, \`-116.9911288)\` AS \`dist\`, astext(coordinates)
FROM \`user_spatial\`
WHERE st_within(coordinates, envelope(linestring(point(-117.07730792871, 32.697490931884), point(-116.90494967129, 32.842418468116))))
ORDER BY st_distance(point(-116.9911288, \`32.7699547)\`, \`coordinates)\`
The query should look like the following as I did not wrap double-square-brackets around any of the fields or values:
SELECT \`user_id\`, harvesine(y(coordinates), x(coordinates), 32.7699547, -116.9911288) AS dist, astext(coordinates)
FROM \`user_spatial\`
WHERE st_within(coordinates, envelope(linestring(point(-117.07730792871, 32.697490931884), point(-116.90494967129, 32.842418468116))))
ORDER BY st_distance(point(-116.9911288, 32.7699547), coordinates)
I can live with Yii2 adding some backticks around field names and table names but why on earth is it backticking numerical values? (FYI: the $rlon and $rlat values don't seem to get backticked but I was assuming that was because they are a result of math calculations!?!?).
I've already tried forcing $geo->lon and $geo->lat to float values like so:
'lon' => (float)$geo->lon;
or
'lon' => (float)$geo->lon * 1;
but it didn't help.
Try to use array format for select and orderBy methods, like docs suggest:
Besides column names, you can also select DB expressions. You must use
the array format when selecting a DB expression that contains commas
to avoid incorrect automatic name quoting. For example,
$query->select(["CONCAT(first_name, ' ', last_name) AS full_name",
'email']);
In you case it will be like this:
$users = UserSpatial::find()
->select([
'user_id',
'harvesine(y(coordinates), x(coordinates), :lat, :lon) as dist',
'astext(coordinates)'
])
->where('st_within(coordinates, envelope(linestring(point(:rlon1, :rlat1), point(:rlon2, :rlat2))))')
->orderBy(['st_distance(point(:lon, :lat)', 'coordinates)'])
->params([
':lon' => $geo->lon,
':lat' => $geo->lat,
':rlon1' => $rlon1,
':rlat1' => $rlat1,
':rlon2' => $rlon2,
':rlat2' => $rlat2
])
->all();

Drupal 7 Mysql query (Insert)

I'm new to Drupal.
So I was wondering if you can help me.
I saw a lot of documents regarding the Drupal API mysql thing-y and It's been bugging me that I have to study once more to finish my work done.
And here's the documentation that I'm applying to my problem
Regarding my problem about the INSERT function, I have this table entitled embed
and here is my data from the table embed.
Then on my basic page I'm trying to insert a query.
$id = db_insert("embed")
->fields(array(
'uid' => 1,
'fbp_id' => 22222,
'prom_stat' => 3333,
'status' => 1,
))
->execute();
Instead of inserting a data to the table, it outputs an error like this.
Anyone knows the solution for this stuff? I'm really confused about this right now.
As #steve has suggested in the comment, the issue is not on drupal side but on MySql side. You need to modify your insert code to
$id = db_insert("embed")
->fields(array(
'uid' => 1,
'fbp_id' => 22222,
'prom_stat' => 3333,
'status' => 1,
'prom_id' => 0,
'sweep_stat' => 0,
'sweep_id' => 0,
'comp_id' => 0,
'comp_stat' => 0,
'polls_stat' => 0,
'polls_id' => 0
))
->execute();
Since i can see that your MySql table already contains value, i assume the earlier inserts where done by explicitly providing all values, instead of relying on default values for the field in MySql configuration.
Whenever you have a PDOException you should read it carefully for clues. These kind of errors are really very verbal and gives lots of pointers to resolve the issues. For example in your case,
'prom_id' doesn't have a default value
explains a lot.

WordPress Custom Select Query

I really don't know enough about MySQL queries and it's showing.
I have a custom field set for every post. The custom field stores the posts source URL in a key called "source_url".
I have it working with the below WP_Query parameters, but it's incredibly slow. Keep in mind it's possible to 50+ urls to search for.
So, given an array of source URL's, I want to fetch the matching posts.
For example, here is what I currently have that's slow in WP_Query:
// var_dump of $urls array (this could be 50+ urls)
array(7) {
[0]=>
string(42) "http://www.youtube.com/watch?v=FMghvnqDhT8"
[1]=>
string(42) "http://www.youtube.com/watch?v=RY-yUFpXTnM"
[2]=>
string(58) "http://www.youtube.com/watch?v=nIm2dnyJ1Ps&feature=related"
[3]=>
string(42) "http://www.youtube.com/watch?v=NoCtRQlJAqM"
[4]=>
string(57) "http://holidaycustoms.blogspot.com/2012/08/busy-week.html"
[5]=>
string(42) "http://www.youtube.com/watch?v=DcZvg197Ie4"
[6]=>
string(42) "http://www.youtube.com/watch?v=7P3UEbLmLuo"
}
// Build Media Query
$meta_query = array(
'relation' => 'OR'
);
foreach( $urls as $url ) {
$meta_query[] = array(
'key' => 'source_url',
'value' => $url
);
}
// Get 20 matching posts from a category set by a variable
$args = array(
'post_type' => 'post',
'posts_per_page' => 20,
'orderby' => 'rand',
'cat' => $cat_ID,
'meta_query' => $meta_query
);
$posts = get_posts($args);
What I'm looking to do is replace the above code with a custom query select, which I have read is much faster than WP_Query.
But I don't know enough about MySQL or the WP database to build the custom select query. Can anyone help? Thanks in advance!
In the post you linked yourself, the first reply already states that
[...] the default schema doesn't even have an index on the value column
Which is far more severe a problem than any you would have with a query generator, because without an index the DBMS has to traverse the whole table and compare strings of each field.
Adding an index is fairly easy with an appropriate management tool like PHPMyAdmin. The offending table you will need to add an index to is called wp_postmeta and the field that needs an index is meta_value, and the index type should be INDEX.
Adding an index is transparent and does not affect wordpress other than in performance. It could take some time though since, well MySQL needs to traverse the whole table. Also, because you are indexing string data, the index will be quite big.
You should also try using appropriate structures for your query. You are currently using a big ORed selection with different values but always the same field. There is a construct for just that, and it's called IN.
...
// Build Media Query
$meta_query = array();
$meta_query[] = array(
'key' => 'source_url',
'value' => $urls,
'compare' => 'IN'
);
// Get 20 matching posts from a category set by a variable
..
(Untested. I actually never did this, Reference)
The performance gain would be negligible compared to adding an index I assume, but your code would become a lot simpler.

codeigniter mysql query problem

I execute a simple insert query, however this insert is done multiple times sometimes unexpectedly. The code for insert is :
$query=$this->db->query("INSERT INTO clientaccesshistory (jobid, clientid,firstname,lastname,clientname,menu,submenu,starttime) VALUES ('$time','$userID','$firstname','$lastname','$clientname','Monitor/Verify', '$this->job_name',current_timestamp() )");
When i look in the database though this information is sometimes there 3 times, sometimes its just once like it is supposed to be. I think this is some issue with connecting to mysql, and then retries till it inserts three times?
I tested the front end to see if the function is actually be called more than once by putting an alert there, but no problem there whatsoever.
Your code almost certainly has to be in a variable loop of some kind. This code, like wonk says, will not add more than one record, ever.
This won't be of much help, but you can try using this-
$arr = array(
jobid => $time,
clientid => $userID,
firstname => $firstname,
lastname => $lastname,
clientname => $clientname,
menu => 'Monitor/Verify',
submenu => $this->job_name,
starttime => current_timestamp()
);
$this->db->insert('clientaccesshistory', $arr);