How to delete multiple entities using Doctrine QueryBuilder - sql

I am working on a Symfony 2.8 based project to manage contact. The user can select from a list any number of contacts and should be able to delete all selected contacts at once. How can this be done in a single Query Builder statement?
// Contact entity uses a GUID as ID
$guids = array(...);
try {
$this->getEntityManager()->getConnection()->beginTransaction();
$qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
->delete('AppBundle:Contact', 'c')
->where('c.guid in (:guids)')
->setParameter(':guids', array($guids, Connection::PARAM_STR_ARRAY));
log($qb->getSql());
$qb->execute();
$this->getEntityManager()->flush();
$this->getEntityManager()->getConnection()->commit();
} catch (\Exception $ex) {
// Rollback the transaction
$this->getEntityManager()->getConnection()->rollback();
}
1. Problem
Addressing the entity with AppBundle:Contact (which works without any problem when building a SELECT statement) does not work. This is the log output:
Query: DELETE FROM AppBundle:Contact c WHERE c.guid in (:guids)
Exception: Doctrine\DBAL\SQLParserUtilsException: Value for :Contact not found in params array. Params array key should be "Contact" in
2. Problem
Using the table name instead (->delete('contact', 'c')) does not work as well:
Query: DELETE FROM contact c WHERE c.guid in (:guids)
Exception: 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 'c WHERE c.guid in ('Array')'
3. Problem
Deleting a single entity does not work either:
->delete('contact', 'c')
->where('c.guid = (:guid)')
->setParameter(':guid', $guids[0]);
Query: DELETE FROM contact c WHERE c.guid = :guid
Exception: 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 'c WHERE c.guid = 'E7516B91-0549-4FFB-85F2-4BD03DC3FFC1''
What might be wrong her?

1st. Problem. Change setParameter line to the following, you don't need to use : in name of param.
->setParameter('guids', $guids);
Second problem - you should not use real table name if you're dealing with queryBuilder.
Third problem - your logic is not correct. If you want to delete single then
$qb = $this->getEntityManager()->createQueryBuilder()
->delete('AppBundle:Contact', 'c')
->where('c.guid = :guid')
->setParameter('guid', $guids[0]);
Additionally
I don't really know what doctrine version you're using, but
$this->getEntityManager()->getConnection()->createQueryBuilder() - seems wrong, because usually you're getting connection if you want to execute RAW SQL.
Try to change to
$qb = $this->getEntityManager()->createQueryBuilder()
And you need to use brackets around the variable only if it's array. Check code below
$queryBuilder->andWhere('r.id IN (:ids)')
->setParameter('ids', $ids);

Unless you want to execute raw SQL, you don't have to use your entity manager's connection, so you can replace $this->getEntityManager()->getConnection()->createQueryBuilder() by
$em->createQueryBuilder()
You could do something like
$qb = $this->createQueryBuilder()
->delete('AppBundle:Contact', 'c')
->where('c.guid in (:guids)')
->setParameter(':guids', $guids);
And if you want to log/execute it
$query = $qb->getQuery();
log($query->getSql());
$query->execute();
You also don't need to add the beginTransaction and rollback, if the query fails and an exception is thrown, doctrine will rollback automatically.

Related

GORM many2many preload error

Currently using GORM to connect to two databases: POSTGRES AND sqlite (using a code switch to choose which one to use). I have a 2 database tables defined in my schema that look like this:
type TableClient struct {
Model
Synchronised bool
FacilityID string `gorm:"primary_key"`
Age int
ClientSexID int
MaritalStatusID int
SpecificNeeds []TableOptionList`gorm:"many2many:options_specific_needs"`
}
type TableOptionList struct {
ID int `gorm:"primary_key"`
Name string
Value string
Text string
SortKey int
}
Previously, I would preload related table with code like this:
var dbClient TableClient
Db.Where("facility_id = ? AND client_id = ? AND id = ?;", URLFacilityID, URLClientID, URLIncidentID).
Preload("ClientSex").
Preload("MaritalStatus").
Preload("CareTakerRelationShip").
Preload("HighestLevelOfEducation").
Preload("Occupation").
Preload("SpecificNeeds").
First(&dbClient)
Now that lookup fails with a syntax error and when I look at the SQL generated, it shows the following SQL is generated:
SELECT * FROM "table_option_lists" INNER JOIN "options_specific_needs" ON "options_specific_needs"."table_option_list_id" = "table_option_lists"."id" WHERE (("options_specific_needs"."table_client_id","options_specific_needs"."table_client_facility_id") IN (('one','LS034')))
Pasting that into the sqlite console also fails with the same error: (near ",": syntax error)
The crux of your issue is in your WHERE clause, you need to use "OR", not ",".
This is likely an issue with GORM and you could open a GitHub issue with them for this. You can get around the issue using db.Raw() from GORM. In general, I prefer to avoid ORMs. Part of the reason is it is often easier to just build an SQL query by hand than try to figure out the strange way to do it in the ORM (for things beyond basic CRUD).

How to convert SOQL query to SQL query?

I am working on SQL query, I have pre-created SOQL query, I am looking for a way to convert it to SQL query. The query is:
SELECT CronJobDetail.Name, Id, CreatedDate, State
FROM CronTrigger
WHERE CronjobDetail.JobType = 'bla bla'
AND CronJobDetail.Name LIKE '%bla bla2%'
But It does not run on terminal when I try to create monitoring script in Ruby. The error that I get:
(Got exception: INVALID_FIELD: No such relation 'CronJobDetail' on
entity 'CronTrigger'. If you are attempting to use a custom field, be
sure to append the '__c' after the custom field name. Please reference
your WSDL or the describe call for the appropriate names. in
/Users/gakdugan/.rvm/gems/ruby-1.9.3-p547/gems/restforce-2.2.0/lib/restforce/middleware/raise_error.rb:18:in
`on_complete'
Do you have any idea how can I fix it and make it run on SQL?
You are trying to access a relation without adding it to your FROM clause. Alternatively, if that's a custom field name, then do what the error message suggests you to do (add __c after the custom field name).
You probably want to do something like this:
SELECT CronJobDetail.Name, CronTrigger.Id, CronTrigger.CreatedDate, CronTrigger.State
FROM CronTrigger
INNER JOIN CronJobDetail ON CronJobDetail.id = CronTrigger.foreign_id // this you have to do yourself
WHERE CronjobDetail.JobType = 'bla bla'
AND CronJobDetail.Name LIKE '%bla bla2%'

Syntax error in WHERE clause near '?) AND (Date = ?)'

I am trying to send a SQL prepared statement to MySQL DB. This is what I have:
String sql1 = "SELECT idReimbursed_Funds As idReimFunds FROM reimbursedfunds Where ReimFundsName = ? AND Date = ?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setString(1, reimfund.getReimFundsName());
pstmt1.setDate(2, (Date) reimfund.getDate());
ResultSet rs1 = pstmt1.executeQuery(sql1);
while(rs1.next()){
idReimFunds = rs1.getInt("idReimFunds");
}
After googling this problem, I found solutions to use parenthesis around the question marks or the whole where clause such as:
String sql1 = "SELECT idReimbursed_Funds As idReimFunds FROM reimbursedfunds Where (ReimFundsName = ?) AND (Date = ?)";
This didn't work though. I get the same error message that is generated by my original code:
"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 '?) AND (Date = ?)' at line 1.
When I try the SQL statement in MySQL Workbench is works fine. Is there a way to use 2 where clauses with JDBC? I know in other posts people have answered that it has to be sent as two different queries, but I thought I would ask just in case someone else reads this posts and knows of a way. Thank you!
The problem (apart from the Date issue as mentioned by bgp), is the line:
ResultSet rs1 = pstmt1.executeQuery(sql1);
You are trying to execute a query string on a prepared statement, which is not allowed by the JDBC standard (MySQL should actually throw an exception instead of sending it to the server as it currently does, but the end result is the same). The documentation of Statement.executeQuery(String sql) says:
Throws:
SQLException - if a database access error occurs, this method is called on a closed Statement, the given SQL statement produces anything other than a single ResultSet object, the method is called on a PreparedStatement or CallableStatement
(emphasis mine)
The reason is that you want to execute the prepared statement, not any other query. You should call PreparedStatement.executeQuery() (so without a parameter):
ResultSet rs1 = pstmt1.executeQuery();
Pretty sure this is because "Date" is a MySQL keyword (reserved). Call the field something else or escape it with backticks, i.e. `Date`

SQL Injection: is this secure?

I have this site with the following parameters:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc
I use the values of each of the parameters as a value in a SQL query.
I am trying to test my application and ultimately hack my own application for learning purposes.
I'm trying to inject this statement:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc' or 1=1 --
But It fails, and MySQL says this:
Warning: mysql_fetch_assoc() expects parameter 1 to be resource,
boolean given in /home/dir/public_html/pagination.php on line 132
Is my application completely free from SQL injection, or is it still possible?
EDIT: Is it possible for me to find a valid sql injection statement to input into one of the parameters of the URL?
The application secured from sql injection never produces invalid queries.
So obviously you still have some issues.
Well-written application for any input produces valid and expected output.
That's completely vulnerable, and the fact that you can cause a syntax error proves it.
There is no function to escape column names or order by directions. Those functions do not exist because it is bad style to expose the DB logic directly in the URL, because it makes the URLs dependent on changes to your database logic.
I'd suggest something like an array mapping the "order" parameter values to column names:
$order_cols = array(
'time' => 'comment_time',
'popular' => 'comment_score',
... and so on ...
);
if (!isset($order_cols[$_GET['order'])) {
$_GET['order'] = 'time';
}
$order = $order_cols[$_GET['order']];
Restrict "sc" manually:
if ($_GET['sc'] == 'asc' || $_GET['sc'] == 'desc') {
$order .= ' ' . $_GET['sc'];
} else {
$order .= ' desc';
}
Then you're guaranteed safe to append that to the query, and the URL is not tied to the DB implementation.
I'm not 100% certain, but I'd say it still seems vulnerable to me -- the fact that it's accepting the single-quote (') as a delimiter and then generating an error off the subsequent injected code says to me that it's passing things it shouldn't on to MySQL.
Any data that could possibly be taken from somewhere other than your application itself should go through mysql_real_escape_string() first. This way the whole ' or 1=1 part gets passed as a value to MySQL... unless you're passing "sc" straight through for the sort order, such as
$sql = "SELECT * FROM foo WHERE page='{$_REQUEST['page']}' ORDER BY data {$_REQUEST['sc']}";
... which you also shouldn't be doing. Try something along these lines:
$page = mysql_real_escape_string($_REQUEST['page']);
if ($_REQUEST['sc'] == "desc")
$sortorder = "DESC";
else
$sortorder = "ASC";
$sql = "SELECT * FROM foo WHERE page='{$page}' ORDER BY data {$sortorder}";
I still couldn't say it's TOTALLY injection-proof, but it's definitely more robust.
I am assuming that your generated query does something like
select <some number of fields>
from <some table>
where sc=desc
order by comment_time
Now, if I were to attack the order by statement instead of the WHERE, I might be able to get some results... Imagine I added the following
comment_time; select top 5 * from sysobjects
the query being returned to your front end would be the top 5 rows from sysobjects, rather than the query you try to generated (depending a lot on the front end)...
It really depends on how PHP validates those arguments. If MySQL is giving you a warning, it means that a hacker already passes through your first line of defence, which is your PHP script.
Use if(!preg_match('/^regex_pattern$/', $your_input)) to filter all your inputs before passing them to MySQL.

Can I pretty-print the DBIC_TRACE output in DBIx::Class?

Setting the DBIC_TRACE environment variable to true:
BEGIN { $ENV{DBIC_TRACE} = 1 }
generates very helpful output, especially showing the SQL query that is being executed, but the SQL query is all on one line.
Is there a way to push it through some kinda "sql tidy" routine to format it better, perhaps breaking it up over multiple lines? Failing that, could anyone give me a nudge into where in the code I'd need to hack to add such a hook? And what the best tool is to accept a badly formatted SQL query and push out a nicely formatted one?
"nice formatting" in this context simply means better than "all on one line". I'm not particularly fussed about specific styles of formatting queries
Thanks!
As of DBIx::Class 0.08124 it's built in.
Just set $ENV{DBIC_TRACE_PROFILE} to console or console_monochrome.
From the documentation of DBIx::Class::Storage
If DBIC_TRACE is set then trace information is produced (as when the
debug method is set). ...
debug Causes trace information to be emitted on the debugobj
object. (or STDERR if debugobj has not specifically been set).
debugobj Sets or retrieves the object used for metric collection.
Defaults to an instance of DBIx::Class::Storage::Statistics that is
compatible with the original method of using a coderef as a callback.
See the aforementioned Statistics class for more information.
In other words, you should set debugobj in that class to an object that subclasses DBIx::Class::Storage::Statistics. In your subclass, you can reformat the query the way you want it to be.
First, thanks for the pointers! Partial answer follows ....
What I've got so far ... first some scaffolding:
# Connect to our db through DBIx::Class
my $schema = My::Schema->connect('dbi:SQLite:/home/me/accounts.db');
# See also BEGIN { $ENV{DBIC_TRACE} = 1 }
$schema->storage->debug(1);
# Create an instance of our subclassed (see below)
# DBIx::Class::Storage::Statistics class
my $stats = My::DBIx::Class::Storage::Statistics->new();
# Set the debugobj object on our schema's storage
$schema->storage->debugobj($stats);
And the definition of My::DBIx::Class::Storage::Statistics being:
package My::DBIx::Class::Storage::Statistics;
use base qw<DBIx::Class::Storage::Statistics>;
use Data::Dumper qw<Dumper>;
use SQL::Statement;
use SQL::Parser;
sub query_start {
my ($self, $sql_query, #params) = #_;
print "The original sql query is\n$sql_query\n\n";
my $parser = SQL::Parser->new();
my $stmt = SQL::Statement->new($sql_query, $parser);
#printf "%s\n", $stmt->command;
print "The parameters for this query are:";
print Dumper \#params;
}
Which solves the problem about how to hook in to get the SQL query for me to "pretty-ify".
Then I run a query:
my $rs = $schema->resultset('SomeTable')->search(
{
'email' => $email,
'others.some_col' => 1,
},
{ join => 'others' }
);
$rs->count;
However SQL::Parser barfs on the SQL generated by DBIx::Class:
The original sql query is
SELECT COUNT( * ) FROM some_table me LEFT JOIN others other_table ON ( others.some_col_id = me.id ) WHERE ( others.some_col_id = ? AND email = ? )
SQL ERROR: Bad table or column name '(others' has chars not alphanumeric or underscore!
SQL ERROR: No equijoin condition in WHERE or ON clause
So ... is there a better parser than SQL::Parser for the job?