Rails SQLite vs MySQL Booleans - mysql

I'm working with Rails 3.2. My development box is using SQLite3 and my production host is using MySQL. Rails SQLite ActiveRecord connector will not save booleans as 1 or 0 and will only save it as 't' or 'f'. Of course I want DB neutral code but I cannot find any way around the following. I have a user model and a shift model. In this query I need all the shifts (work schedules) and I need to order the results by the related table as well as apply the boolean conditions.
#sh= Shift.find(:all, :include=>:user, :order=>'users.rating DESC', :conditions=>["a1=1 or a1='t'"])
I have also learned about ActiveRecord::Base.connection.quoted_true and quoted_false. I suspect I could change them but that also seems non portable and would probably be silently overridden if I upgrade.
I don't want to test for both 1 and 't' (or 0 and 'f'). Is there any way around doing this besides changing my dev environment to mysql?

You should be able to pass the following as a conditions:
:conditions => [["a1 = ?", true]]

Related

How to query array column from mysql in Rails 5.2?

I am using gem rails~> 5.2 and gem mysql2 >= 0.3.13, < 0.5.
I have a model Lawer, which has an array column lawer_filed [sic].
# Model lawer.rb
serialize :lawer_field, Array
Then I created a Lawer, and I can get the lawer_field value as follows:
=> Lawer.first.lawer_field
=> ["2", "3", "5"]
Now, I want to find one Lawer with a query using lawer_field. I tried:
#lawer = Lawer.where("lawer_field && ARRAY[?]", "2")
which raised an error like this:
ActiveRecord::StatementInvalid (Mysql2::Error: 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 '['2']) LIMIT 11' at line 1: SELECT `lawers`.* FROM `lawers` WHERE (lawer_field && ARRAY['2']) LIMIT 11)
There is a mistake in my SQL syntax, but I don't how to fix it. Can anyone help?
MySQL, unlike PostgreSQL, does not support arrays in database. Therefore you needed to add this line:
serialize :lawer_field, Array
This means that you have a string field in your database, but whenever ActiveRecord is unpacking results returned by the database, it maps them directly to an instance of Ruby Array. What this means is that your only option to filter the results in the database is with any MySQL string comparison functions, LIKE, etc.
Your options are to either use LIKE or perform some other String functions (which will not perform well as you will be unable to use indices) or build another table, add a has_many association to it and use MySQL the way it was supposed to be used. You could also, of course, migrate to PostgreSQL, but that seems to be the most extreme option.
EDIT: you could also consider using MySQL`s JSON, which has been added recently. That depends on your version of MySQL though.
I didn't try this answer because I don't have any Rails project ready for testing, but I think the problem is in the syntax.
I think it should be something like this:
Lawer.where("lawer_field IN (?)", "2")

Rails active record query using MySQL UDF

I am using MySQL user-defined functions in my Rails app (levenshtein ratio related) for selecting similar strings. For example, i am selecting similar models ordering by "similarity" like that:
#something = Something.select("*", "levenshtein_ratio(name, '#{Mysql2::Client.escape(name)}') as l_ratio").having("l_ratio > ?", minratio).order("l_ratio DESC")
It works fine, there are two questions though:
Is there a better, more "native" way to do the same thing?
How can i get l_ratio value from the selected objects? ActiveRecord object won't contain it.

Disable Rails 4.2 fractional second support on MySQL

So Rails 4.2 starts to support fractional seconds for MySQL. However it requires a migration of column change from datetime to datetime(6). Currently we do not want to do this, and just want to ignore fractional seconds as we have always been doing before.
Rails is not smart enough to see that my datetime has precision 0 and change queries accordingly, so a lot of our spec broke. For example we assert to "select all Foo created within the last hour", but values are persisted without milliseconds, but select statements still uses milliseconds, so lots of records won't be selected.
Before Rails 4.2:
where("foo_at >= ?", 1.day.ago) => foo_at >= '2015-11-02 04:48:18'
In Rails 4.2:
where("foo_at >= ?", 1.day.ago) => foo_at >= '2015-11-02 04:48:18.934162'
Is there a global setting to force AR queries to strip out fractional/milliseconds as it has been doing before?
There is no official way to turn it off, but you could overwrite the behavior,
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
def quoted_date(value)
super
end
end
end
end
It is in https://github.com/rails/rails/blob/v4.2.5/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L77-L83
And you could remove it in Rails 5, the commit in rails 5 https://github.com/rails/rails/commit/bb9d20cf3434e20ac6f275e3ad29be42ebec207f should Format the time string according to the precision of the time column
From reading Rails source for 4.2.x branch, I don't see any option to change that.
I see a merged PR, which will see what the database column's precision is, and build the sql datetime string according to that precision. This should eliminate my issue that my spec broke due to mismatch of precision between persisted data and queries. However this is not merged in 4.2.x so it probably will only be available in Rails 5.

Entity Framework converts StartsWith to MySQL's Locate, MySQL's Locate doesn't use index

I'm using Entity Framework with MySQL, and my Linq Query:
db.Persons.Where(x => x.Surname.StartsWith("Zyw")).ToList();
..is producing the SQL:
SELECT PersonId, Forename, Surname
FROM Person
WHERE (LOCATE('Zyw', Surname)) = 1
...and it would seem that this doesn't make use of the index on Surname.
If LOCATE is replaced with the equivalent LIKE, the query speedily returns the required results. As it is it takes all afternoon.
Why is Entity Framework and its connecting drivers opting for this wierd LOCATE function / how can I make it use LIKE instead / why is MySQL making a poor index decision for the LOCATE function / how can I make it better?
Update:
I'm afraid I was guilty of over simplifying my code for this post, the Linq producing the error is in fact:
var target = "Zyw";
db.Persons.Where(x => x.Surname.StartsWith(target)).ToList();
If target term is hard coded, the SQL generated does indeed use LIKE, but with a variable term the SQL changes to use LOCATE.
This is all using the latest generally available MySQL for Windows as delivered by MySQL Installer 5.6.15.
Update:
A couple more notes to go with the bounty; am using:
Visual Studio 2010
EntityFramework 6.0.2
MySQL Installer 5.6.15,
which in turn gives:
MySql.Data 6.7.4
MySql.Data.Entities 6.7.4
The Entity Framework code is generated database first style.
I've also tried it with the latest connector from Nuget (MySql.Data 6.8.3) and the problem is still there.
It's likely your problem is caused by:
You are using an older connector with the bug.
You have a special case (using a variable to hold the .Contains search) described as a bug here
Does your case fall into any of those?
This looks like a regression of MySQL bug #64935 to me.
I can confirm that, using the same builds of EF6 and MySQL Connector, I'm getting the same SQL generated too:
context.stoppoints.Where(sp => sp.derivedName.StartsWith(stopName));
...logs as:
SELECT
`Extent1`.`primaryCode`,
...
`Extent1`.`stop_timezone`
FROM `stoppoints` AS `Extent1`
WHERE (LOCATE(#p__linq__0, `Extent1`.`derivedName`)) = 1
Entity Framework: 6.0.2
MySQL Connector.Net: 6.8.3
I have reported this as a MySQL bug regression.

Drupal : How can I know if the db is mysql or postgres

I have a complicated query and since I need that my module work on both mysql and postgres, I need to write two version of it.
Unfortunately, I don't know how I can check if the db I use is mysql or postgres, to know which query use. Do you know if a function can return this value?
As #kordirko says, one option is to query the server version: SELECT version(); will work on both MySQL and PostgreSQL, though not most other database engines.
Parsing version strings is always a bit fragile though, and MySQL returns just a version number like 5.5.32 wheras PostgreSQL returns something like PostgreSQL 9.4devel on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8), 64-bit. What do you do if you're connecting to a PostgreSQL-compatible database like EnterpriseDB Postgres Plus, or a MySQL-compatible database?
It's much safer to use the Drupal function for the purpose, DatabaseConnection::databaseType. This avoids a query round-trip to the DB, will work on databases that won't understand/accept SELECT version(), and will avoid the need to parse version strings.
You'll find this bug report useful; it suggests that the correct usage is Database::getConnection()->databaseType().
(I've never even used Drupal, I just searched for this).
As long as the abstract DatabaseConnection class extends PDO class, you can invoking pdo methods in order to know the current database driver.
For instance:
$conn = Database::getConnection();
print $conn->getAttribute($conn::ATTR_DRIVER_NAME); #returns mysql, pgsql...
There is a second way to do it using DatabaseConnection::driver():
print $conn->driver();
or DatabaseConnection::databaseType();
print $conn->databaseType();
Note that DatabaseConnection::driver() and DatabaseConnection::databaseType() are similar functions but not equals!
The return value from DatabaseConnection::driver() method depends on the implementation and other factors.
in the Drupal Database API page:
database.inc abstract public DatabaseConnection::driver()
This is not necessarily the same as the type of the database itself. For instance, there could be two MySQL drivers, mysql and mysql_mock. This function would return different values for each, but both would return "mysql" for databaseType().
In the most cases you just gonna want to use only
$conn->getAttribute($conn::ATTR_DRIVER_NAME)
or $conn->databaseType()
If you want get more specific properties, you should take advantage the PHP ReflectionClass features:
$conn = Database::getConnection();
$ref = new ReflectionClass($conn);
#ref->getProperties, ref->getConstants $ref->isAbstract...
Reference:
PDO::getAttribute
PDO::ATTR_DRIVER_NAME
Drupal Database API
Drupal Base Database API class