PostgreSQL - incorrect syntax, when in condition is "false" value - sql

I have this scope in Rails:
scope :by_default_checks, {:conditions => ['cars.sold IS ? AND cars.deactivated IS ?', nil, false]}
#cars = Car.by_title(params[:search][:title_like]).by_greater(params[:search][:amount_gte]).by_smaller(params[:search][:amount_lte]).by_default_checks
and on Heroku I am getting this error:
Completed 500 Internal Server Error in 6ms
... AND cars.sold IS NULL AND cars.deactivated IS 'f')
SELECT "cars".* FROM "cars" WHERE (cars.title LIKE '%iphoe%') AND (cars.sold IS NULL AND cars.deactivated IS 'f')
PG::SyntaxError: ERROR: syntax error at or near "'f'"
This code is working on SQLite, but doesn't on PostgreSQL. How to replace it?
Thanks

You should use = to check for equality with non-null values:
['cars.sold IS ? AND cars.deactivated = ?', nil, false]
You usually use is in is null, is not null, is distinct from, and is not distinct from when you're faced with NULLs and a simple = comparison will not work the way you want it to. You can use is for booleans if you're using the true or false values but not the 't' and 'f' strings that ActiveRecord uses to represent PostgreSQL booleans.
See Comparison Operators in the manual for details.
Alternatively, you could let ActiveRecord build the whole thing instead of using the old-school :conditions stuff:
scope :by_default_checks, where(:sold => nil).where(:deactivated => false)
That way ActiveRecord is responsible for all the native-to-PostgreSQL type conversions and it will choose the correct comparison operators for you.
Also, developing on top of one database (SQLite) and deploying on another (PostgreSQL) is a really bad idea that will just lead to pain, suffering, and hair loss. There are all sorts of differences between databases that no ORM can insulate you from. Please fix this bug and then immediately switch your development environment to PostgreSQL.

Related

Create PostgreSQL table comment using a prepared statement

Is it possible to prepare a Postgres 'COMMENT ON' statement?
I have a program that allows users to create tables. I'd like to give them the option to add a description of the table's contents. As this data is coming from users, I'd like to use prepared statements.
Using Ruby and the 'pg' gem, I use the below to setup a Postgres connection and example data:
table_name = "test_shakespeare"
description = "Shakespeare's sonnets"
con = PG.connect(
:dbname => "poefy",
:user => "poefy",
:password => "poefy"
)
sql = "CREATE TABLE #{table_name} (example TEXT);"
con.exec(sql)
Here are my failed approaches, together with the errors they throw.
# ERROR: syntax error at or near "$1" (PG::SyntaxError)
sql = "COMMENT ON TABLE #{table_name} IS $1;"
con.exec(sql, [*description])
# ERROR: syntax error at or near "$1" (PG::SyntaxError)
sql = "COMMENT ON TABLE #{table_name} IS $1;"
con.prepare("comment", sql)
con.exec_prepared("comment", [*description])
# ERROR: could not determine data type of parameter $1 (PG::IndeterminateDatatype)
sql = "COMMENT ON TABLE #{table_name} IS '$1';"
con.exec(sql, [*description])
# ERROR: bind message supplies 1 parameters, but prepared statement "comment" requires 0 (PG::ProtocolViolation)
sql = "COMMENT ON TABLE #{table_name} IS '$1';"
con.prepare("comment", sql)
con.exec_prepared("comment", [*description])
It seems that preparation is not possible for this type of statement, and I should resort to SQL string manipulation. That being the case, what is the best way to go about this? The data is not sensitive or critical, and I am only really concerned with correctly represented quote marks and apostrophes.
Thanks in advance.
I assume ruby supports same statements as postgres, which does it for
Any SELECT, INSERT, UPDATE, DELETE, or VALUES statement
not COMMENT IS
It does appear that this is not possible.
So I went with the old "double all the single quotes" method.
safe_desc = description.gsub("'", "''")
con.exec "COMMENT ON TABLE #{table_name} IS '#{safe_desc}';"
This feels really hacky. But for now I'm marking it as the answer.
If there's a safer way, please let me know.

Two arrays of conditions in single ActiveRecord 'where' clause

Let's say I have two pairs of SQL conditions like these:
a = [ "users.accepted = ? AND users.active_at > ?", true, Time.zone.now ]
b = [ "users.accepted = ? AND users.active_at > ?", false, Time.zone.now + 3.days ]
I can use code like User.where(a) to get all rows that satisfy the a condition. How can I use where to get rows that satisfy either a or b conditions? The result should be ActiveRecord::Relation.
There are a couple ways to go about this.
get meta_where or squeel depending upon your rails version. These are really great gems that enhance the Arel behavior of ActiveRecord::Relation.
write sql manually and pass it into the where method as a string. You might have to mess with sql injection more manually, but from your example above I didn't see any incoming values that were user generated strings.

Rails 3 ActiveRecord query using both SQL IN and SQL OR operators

I'm writing a Rails 3 ActiveRecord query using the "where" syntax, that uses both the SQL IN and the SQL OR operator and can't figure out how to use both of them together.
This code works (in my User model):
Question.where(:user_id => self.friends.ids)
#note: self.friends.ids returns an array of integers
but this code
Question.where(:user_id => self.friends.ids OR :target => self.friends.usernames)
returns this error
syntax error, unexpected tCONSTANT, expecting ')'
...user_id => self.friends.ids OR :target => self.friends.usern...
Any idea how to write this in Rails, or just what the raw SQL query should be?
You don't need to use raw SQL, just provide the pattern as a string, and add named parameters:
Question.where('user_id in (:ids) or target in (:usernames)',
:ids => self.friends.ids, :usernames => self.friends.usernames)
Or positional parameters:
Question.where('user_id in (?) or target in (?)',
self.friends.ids, self.friends.usernames)
You can also use the excellent Squeel gem, as #erroric pointed out on his answer (the my { } block is only needed if you need access to self or instance variables):
Question.where { user_id.in(my { self.friends.ids }) |
target.in(my { self.friends.usernames }) }
Though Rails 3 AR doesn't give you an or operator you can still achieve the same result without going all the way down to SQL and use Arel directly. By that I mean that you can do it like this:
t = Question.arel_table
Question.where(t[:user_id].in(self.friends.ids).or(t[:username].in(self.friends.usernames)))
Some might say it ain't so pretty, some might say it's pretty simply because it includes no SQL. Anyhow it most certainly could be prettier and there's a gem for it too: MetaWhere
For more info see this railscast: http://railscasts.com/episodes/215-advanced-queries-in-rails-3
and MetaWhere site: http://metautonomo.us/projects/metawhere/
UPDATE: Later Ryan Bates has made another railscast about metawhere and metasearch: http://railscasts.com/episodes/251-metawhere-metasearch
Later though Metawhere (and search) have become more or less legacy gems. I.e. they don't even work with Rails 3.1. The author felt they (Metawhere and search) needed drastic rewrite. So much that he actually went for a new gem all together. The successor of Metawhere is Squeel. Read more about the authors announcement here:
http://erniemiller.org/2011/08/31/rails-3-1-and-the-future-of-metawhere-and-metasearch/
and check out the project home page:
http://erniemiller.org/projects/squeel/
"Metasearch 2.0" is called Ransack and you can read something about it from here:
http://erniemiller.org/2011/04/01/ransack-the-library-formerly-known-as-metasearch-2-0/
Alternatively, you could use Squeel. To my eyes, it is simpler. You can accomplish both the IN (>>) and OR (|) operations using the following syntax:
Question.where{(:user_id >> my{friends.id}) | (:target >> my{friends.usernames})}
I generally wrap my conditions in (...) to ensure the appropriate order of operation - both the INs happen before the OR.
The my{...} block executes methods from the self context as defined before the Squeel call - in this case Question. Inside of the Squeel block, self refers to a Squeel object and not the Question object (see the Squeel Readme for more). You get around this by using the my{...} wrapper to restore the original context.
raw SQL
SELECT *
FROM table
WHERE user_id in (LIST OF friend.ids) OR target in (LIST OF friends.usernames)
with each list comma separate. I don't know the Rails ActiveRecord stuff that well. For AND you would just put a comma between those two conditions, but idk about OR

Why does this Rails named scope return empty (uninitialized?) objects?

In a Rails app, I have a model, Machine, that contains the following named scope:
named_scope :needs_updates, lambda {
{ :select => self.column_names.collect{|c| "\"machines\".\"#{c}\""}.join(','),
:group => self.column_names.collect{|c| "\"machines\".\"#{c}\""}.join(','),
:joins => 'LEFT JOIN "machine_updates" ON "machine_updates"."machine_id" = "machines"."id"',
:having => ['"machines"."manual_updates" = ? AND "machines"."in_use" = ? AND (MAX("machine_updates"."date") IS NULL OR MAX("machine_updates"."date") < ?)', true, true, UPDATE_THRESHOLD.days.ago]
}
}
This named scope works fine in development mode. In production mode, however, it returns the 2 models as expected, but the models are empty or uninitialized; that is, actual objects are returned (not nil), but all the fields are nil. For example, when inspecting the return value of the named scope in the console, the following is returned:
[#<Machine >, #<Machine >]
But, as you can see, all the fields of the objects returned are set to nil.
The production and development environments are essentially the same. Both are using a SQLite database. Here is the SQL statement that is generated for the query:
SELECT
"machines"."id",
"machines"."machine_name",
"machines"."hostname",
"machines"."mac_address",
"machines"."ip_address",
"machines"."hard_drive",
"machines"."ram",
"machines"."machine_type",
"machines"."use",
"machines"."comments",
"machines"."in_use",
"machines"."model",
"machines"."vendor_id",
"machines"."operating_system_id",
"machines"."location",
"machines"."acquisition_date",
"machines"."rpi_tag",
"machines"."processor",
"machines"."processor_speed",
"machines"."manual_updates",
"machines"."serial_number",
"machines"."owner"
FROM
"machines"
LEFT JOIN
"machine_updates" ON "machine_updates"."machine_id" = "machines"."id"
GROUP BY
"machines"."id",
"machines"."machine_name",
"machines"."hostname",
"machines"."mac_address",
"machines"."ip_address",
"machines"."hard_drive",
"machines"."ram",
"machines"."machine_type",
"machines"."use",
"machines"."comments",
"machines"."in_use",
"machines"."model",
"machines"."vendor_id",
"machines"."operating_system_id",
"machines"."location",
"machines"."acquisition_date",
"machines"."rpi_tag",
"machines"."processor",
"machines"."processor_speed",
"machines"."manual_updates",
"machines"."serial_number",
"machines"."owner"
HAVING
"machines"."manual_updates" = 't'
AND "machines"."in_use" = 't'
AND (MAX("machine_updates"."date") IS NULL
OR MAX("machine_updates"."date") < '2010-03-26 13:46:28')
Any ideas what's going wrong?
This might not be related to what is happening to you, but it sounds similar enough, so here it goes: are you using the rails cache for anything?
I got nearly the same results as you when I tried to cache the results of a query (as explained on railscast #115).
I tracked down the issue to a still open rails bug that makes cached ActiveRecords unusable - you have to choose between not using cached AR or applying a patch and getting memory leaks.
The cache works ok with non-AR objects, so I ended up "translating" the stuff I needed to integers and arrays, and cached that.
Hope this helps!
Seems like the grouping may be causing the problem. Is the data also identical in both dev & production?
Um, I'm not sure you're having the problem you think you're having.
[#<Machine >, #<Machine >]
implies that you have called "inspect" on the array... but not on each of the individual machine-objects inside it. This may be a silly question, but have you actually tried calling inspect on the individual Machine objects returned to really see if they have nil in the columns?
Machine.needs_updates.each do |m|
p m.inspect
end
?
If that does in fact result in nil-column data. My next suggestion is that you copy the generated SQL and go into the standard mysql interface and see what you get when you run that SQL... and then paste it into your question above so we can see.

Encapsulating SQL in a named_scope

I was wondering if there was a way to use "find_by_sql" within a named_scope. I'd like to treat custom sql as named_scope so I can chain it to my existing named_scopes. It would also be good for optimizing a sql snippet I use frequently.
While you can put any SQL you like in the conditions of a named scope, if you then call find_by_sql then the 'scopes' get thrown away.
Given:
class Item
# Anything you can put in an sql WHERE you can put here
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
end
This works (it just sticks the SQL string in there - if you have more than one they get joined with AND)
Item.mine.find :all
=> SELECT * FROM items WHERE ('user_id' = 887 and IS_A_NINJA() = 1)
However, this doesn't
Items.mine.find_by_sql 'select * from items limit 1'
=> select * from items limit 1
So the answer is "No". If you think about what has to happen behind the scenes then this makes a lot of sense. In order to build the SQL rails has to know how it fits together.
When you create normal queries, the select, joins, conditions, etc are all broken up into distinct pieces. Rails knows that it can add things to the conditions without affecting everything else (which is how with_scope and named_scope work).
With find_by_sql however, you just give rails a big string. It doesn't know what goes where, so it's not safe for it to go in and add the things it would need to add for the scopes to work.
This doesn't address exactly what you asked about, but you might investigate 'contruct_finder_sql'. It lets you can get the SQL of a named scope.
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
named_scope :additional {
:condtions => mine.send(:construct_finder_sql,{}) + " additional = 'foo'"
}
sure why not
:named_scope :conditions => [ your sql ]