I have a problem in using Rails / ActiveRecord.
I want to insert record with MySQL function, for example GeomFromText('POINT(1 1)').
Using ActiveRecord normally, these functions are quoted automatically. I want not to quote these values.
Model.create(geo: GeomFromText('POINT(1 1)'))
this ActiveRecord statement will generate following SQL
INSERT INTO `Model` (`geo`) VALUES ('GeomFromText(\'POINT(1 1)\')')
It may be easy to use raw SQL, but I want to use ActiveRecord because my Model set several callbacks include self table.
How can use MySQL function with ActiveRecord statement?
Summary
You can't by design; this behavior is important for preventing SQL injection attacks. You would need to explicitly execute raw SQL in conjunction with ActiveRecord.
Details
As you saw, the SQL statement gets interpolated as a string by design, which doesn't do what you want (Rails ~> 4.0):
> Country.create(name: 'LOWER("CANADA")')
=> SQL (0.3ms) INSERT INTO `Country` (`Name`) VALUES ('LOWER(\"CANADA\")')
Nor can you use the same tricks that would work for the .where method:
> Country.create(["name = LOWER(:country)", { country: 'CANADA' }])
=> ArgumentError: When assigning attributes, you must pass a hash as an argument.
You would need to execute arbitrary SQL first to get the proper value, then make another SQL call via ActiveRecord to achieve your callback:
> Country.create( name: ActiveRecord::Base.connection.execute(%q{ SELECT LOWER('CANADA') }).first[0] )
=> (0.3ms) SELECT LOWER('CANADA')
=> SQL (0.3ms) INSERT INTO `Country` (`Name`) VALUES ('canada')
That said, it's probably cleaner to re-implement the SQL function at the application layer instead of the DB layer (unless you've got a really complex SQL function).
Related
I'm using a MySql database and was trying to find a MySQL alternative to tedious.js (a SQL server parameterised query builder).I'm using Node.js for my backend.
I read that the .raw() command from knex.js is susceptible to sql injection, if not used with bindings.
But are the other commands and knex.js as a whole safe to use to prevent sql injection? Or am I barking up the wrong tree?
Read carefully from knex documentation how to pass values to knex raw (http://knexjs.org/#Raw).
If you are passing values as parameter binding to raw like:
knex.raw('select * from foo where id = ?', [1])
In that case parameters and query string are passed separately to database driver protecting query from SQL injection.
Other query builder methods always uses binding format internally so they are safe too.
To see how certain query is passed to database driver one can do:
knex('foo').where('id', 1).toSQL().toNative()
Which will output SQL string and bindings that are given to driver for running the query (https://runkit.com/embed/2yhqebv6pte6).
Biggest mistake that one can do with knex raw queries is to use javascript template string and interpolate variables directly to SQL string format like:
knex.raw(`select * from foo where id = ${id}`) // NEVER DO THIS
One thing to note is that knex table/identifier names cannot be passed as bindings to driver, so with those one should be extra careful to not read table / column names from user and use them without properly validating them first.
Edit:
By saying that identifier names cannot be passed as bindings I mean that when one is using ?? knex -binding for identifier name, that will be rendered as part of SQL string when passed to the database driver.
I am dissecting some code and came across this,
$sql = 'SELECT page.*, author.name AS author, updator.name AS updator '
. 'FROM '.TABLE_PREFIX.'page AS page '
. 'LEFT JOIN '.TABLE_PREFIX.'user AS author ON author.id = page.created_by_id '
. 'LEFT JOIN '.TABLE_PREFIX.'user AS updator ON updator.id = page.updated_by_id '
. 'WHERE slug = ? AND parent_id = ? AND (status_id='.Page::STATUS_REVIEWED.' OR status_id='.Page::STATUS_PUBLISHED.' OR status_id='.Page::STATUS_HIDDEN.')';
I am wondering what the "?" does in the WHERE statement. Is it some sort of parameter holder?
Prepared statments use the '?' in MySQL to allow for binding params to the statement. Highly regarded as more secure against SQL injections if used properly. This also allows for quicker SQL queries as the request only has to be compiled once and can be reused.
The question mark represents a parameter that will later be replaced. Using parameterized queries is more secure than embedding the parameters right into the query.
SQL Server calls this parameterize queries, and Oracle calls it bind variables.
The usage varies with the language that you are executing the query from.
Here is an example of how it is used from PHP.
assuming that $mysqli is a database connection and people is a table with 4 columns.
$stmt = $mysqli->prepare("INSERT INTO People VALUES (?, ?, ?, ?)");
$stmt->bind_param('sssd', $firstName, $lastName, $email, $age);
The 'sssd' is a flag identifying the rest of the parameters, where s represents string and d represents digits.
? has no special meaning in MySQL WHERE = statements, only in prepared statements
The most common case where we see it is due to special meaning given to ? by several web frameworks like PHP and Rails.
? is just a syntax error at:
CREATE TABLE t (s CHAR(1));
SELECT * FROM t WHERE s = ?;
because it is unquoted, and in:
INSERT INTO t VALUES ('a');
INSERT INTO t VALUES ("?");
SELECT * FROM t WHERE s = '?';
it returns:
s
?
thus apparently without special meaning.
MySQL 5.0 prepared statements
MySQL 5.0 added the prepared statement feature which has similar semantics to the question mark in web frameworks.
Example from the docs:
PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
SET #a = 3;
SET #b = 4;
EXECUTE stmt1 USING #a, #b;
Output:
hypotenuse
5
These also escape special characters as expected:
PREPARE stmt1 FROM 'SELECT ? AS s';
SET #a = "'";
EXECUTE stmt1 USING #a;
Output:
s
'
Rails example
In Rails for example, the question mark is replaced by an argument given by a variable of the library's programming language (Ruby), e.g.:
Table.where("column = ?", "value")
and it automatically quotes arguments to avoid bugs and SQL injection, generating a statement like:
SELECT * FROM Table WHERE column = 'value';
The quoting would save us in case of something like:
Table.where("column = ?", "; INJECTION")
These are prepared statements ,prepared statements offer two major benefits:
The query only needs to be parsed (or prepared) once, but can be
executed multiple times with the same or different parameters. When
the query is prepared, the database will analyze, compile and optimize
its plan for executing the query. For complex queries this process can
take up enough time that it will noticeably slow down an application
if there is a need to repeat the same query many times with different
parameters. By using a prepared statement the application avoids
repeating the analyze/compile/optimize cycle. This means that prepared
statements use fewer resources and thus run faster.
The parameters to prepared statements don't need to be quoted; the
driver automatically handles this. If an application exclusively uses
prepared statements, the developer can be sure that no SQL injection
will occur (however, if other portions of the query are being built up
with unescaped input, SQL injection is still possible).
http://php.net/manual/en/pdo.prepared-statements.php
Is it possible to use ActiveRecord's update_all function to create a MySQL query like:
UPDATE `key_value_store`
SET `key` =
CASE `key`
WHEN 'xxxxxxx' THEN 'yyyyyyyy'
WHEN 'zzzzzzz' THEN 'wwwwwwww'
...
END
WHERE `key` IN ('some key', 'some other key', ...)
LIMIT 1000
What I have now is
keys = ['some key', 'some other key', ...]
Keyvaluemodel.where(:key => keys).limit(1000).update_all()
So the question is really, what code goes in the update_all brackets so this update works the way I want it?
This isn't possible unless you write some raw SQL or a custom query. But considering that you only have two cases, it's easy enough to write two queries.
If you look at the docs you will see that the only parameter update_all accepts is the SET part of the SQL query.
updates - A string, array, or hash representing the SET part of an SQL statement.
So either write a custom query, or split the update into several queries, one for each CASE.
Model.where(key: key_1).limit(1000).update_all(key: new_key)
Model.where(key: key_2).limit(1000).update_all(key: new_key)
use key-value pair in the parameter like:
Keyvaluemodel.where(:key => keys).limit(1000).update_all(:key=>'new key')
$xyz = mysql_fetch_array(mysql_query('select sum(value) points where userId = $userIdDB'))['suma'];
How that query will looks in zend framework? I need to select sum of records from DB as int.
And another question: can i make mysql queries. I dont have in real any knowledge from zend, so I please for full explanation.
What about mysql connections in zend?
In ZEND
$select = $db->select()
->from('points',array(new Zend_Db_Expr('sum(value)')))
->where('userId = ?', $userIdDB);
When Adding Expression Columns
Columns in SQL queries are sometimes expressions, not simply column
names from a table. Expressions should not have correlation names or
quoting applied. If your column string contains parentheses,
Zend_Db_Select recognizes it as an expression.
You also can create an object of type Zend_Db_Expr explicitly, to
prevent a string from being treated as a column name. Zend_Db_Expr is
a minimal class that contains a single string. Zend_Db_Select
recognizes objects of type Zend_Db_Expr and converts them back to
string, but does not apply any alterations, such as quoting or
correlation names.
EXAMPLE IN ZEND
// Build this query using Zend_Db_Expr explicitly:
// SELECT p."product_id", p.cost * 1.08 AS cost_plus_tax
// FROM "products" AS p
$select = $db->select()
->from(array('p' => 'products'),
array('product_id',
'cost_plus_tax' =>
new Zend_Db_Expr('p.cost * 1.08'))
);
While using a code like this:
$Model->find('all', array(
'conditions' => array(
'field' => '1111'
)
));
where field is varchar mysql field cake generates a query like this:
SELECT * FROM Models WHERE field = 1111;
instead of expected
SELECT * FROM Models WHERE field = '1111';
This also makes mysql cast the entire DB to int instead of using string index.
I'm trying to optimize an already working system written by someone else, and a quick-grep shows thousands of find's I need to "fix". So an only acceptable solution should be either model-layer or mysql-layer.
tl;dr: How to make Cake pass integer strings from conditions to mysql as string and not as numbers?
After running out of chars in the comment.
Would you should really be doing:
Optimizing at the PHP (Cake) level.
the find() wrappers are not ideal. they incredibly powerful but very slow. they allow rapid development but need more queries than necessary.
so there you should try to do some bottle neck fixing.
the DB is probably (internally) still the fastest piece in the dispatcher chain - with or without casts.
To get more concrete:
use manual query() if you feel you have to
try to use atomic methods (updateAll, deleteAll) where possible
try to use containable and linkable behavior (especially the last) if you use find() calls with joins to cut down the amount of queries
cache your db results somehow