From 6ffa4e88f0425c967ddee9e1417178234ec768d9 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Wed, 22 Nov 2017 17:22:27 +0100 Subject: [PATCH] Normalize best-effort vs strict operator usage --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/rebel/sql.rb | 26 +++++++++++------------ test/test_raw.rb | 18 +++++++++------- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 0b3489b..e6fb495 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,60 @@ Rebel::SQL() { select count(:id).as(:count), from: :customers, where: { :age => # While we're counting things, let's group results Rebel::SQL() { select count(:id).as(:count), :country, from: :customers, group: by(:country).having(count(:customer_id) => 5) } #=> SELECT COUNT("id") AS "count", "country" FROM "customers" GROUP BY "country" HAVING COUNT("customer_id") = 5 + +# Passing a hash does a best effort to map Ruby to SQL +Rebel::SQL() { select :id, from: :customers, where: { :age => 42 } } +#=> SELECT "id" FROM "customers" WHERE "age" = 42 +Rebel::SQL() { select :id, from: :customers, where: { :age => [20, 21, 22] } } +#=> SELECT "id" FROM "customers" WHERE "age" IN (20, 21, 22) +Rebel::SQL() { select :id, from: :customers, where: { :age => nil } } +#=> SELECT "id" FROM "customers" WHERE "age" IS NULL + +# Using operators ensures the expected SQL operator is used +Rebel::SQL() { select :id, from: :customers, where: name(:age).eq(42) } +#=> SELECT "id" FROM "customers" WHERE "age" = 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age).eq(nil) } +#=> SELECT "id" FROM "customers" WHERE "age" = NULL +Rebel::SQL() { select :id, from: :customers, where: name(:age).ne(42) } +#=> SELECT "id" FROM "customers" WHERE "age" != NULL +Rebel::SQL() { select :id, from: :customers, where: name(:age).is(42) } +#=> SELECT "id" FROM "customers" WHERE "age" IS 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age).is(nil) } +#=> SELECT "id" FROM "customers" WHERE "age" IS NULL +Rebel::SQL() { select :id, from: :customers, where: name(:age).is_not(nil) } +#=> SELECT "id" FROM "customers" WHERE "age" IS NOT NULL + +# Other operators are available +Rebel::SQL() { select :id, from: :customers, where: name(:age).ge(42) } +#=> SELECT "id" FROM "customers" WHERE "age" >= 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age).in(21, 22, 23) } +#=> SELECT "id" FROM "customers" WHERE "age" IN (21, 22, 23) +Rebel::SQL() { select :id, from: :customers, where: name(:first_name).like("J%") } +#=> SELECT "id" FROM "customers" WHERE "first_name" LIKE "J%" + +# Aliases to overload operators are available +Rebel::SQL() { select :id, from: :customers, where: name(:age) == 42 } +#=> SELECT "id" FROM "customers" WHERE "age" = 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age) < 42 } +#=> SELECT "id" FROM "customers" WHERE "age" < 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age) >= 42 } +#=> SELECT "id" FROM "customers" WHERE "age" >= 42 +Rebel::SQL() { select :id, from: :customers, where: name(:age) != 42 } +#=> SELECT "id" FROM "customers" WHERE "age" != 42 + +# Conditions can be combined +Rebel::SQL() { select :id, from: :customers, where: name(:age).gt(42).or(name(:age).lt(21)) } +#=> SELECT "id" FROM "customers" WHERE ("age" > 42 OR "age" < 21) +Rebel::SQL() { select :id, from: :customers, where: (name(:age) < 42).and(name(:age) > 21) } +#=> SELECT "id" FROM "customers" WHERE ("age" < 42 AND "age" > 21) + +# Binary-wise operators can be used to tie conditions +# WARNING: Usefulness if this hack is still debated. It might be removed in the future. +Rebel::SQL() { select :id, from: :customers, where: ((name(:age) > 42) | (name(:age) < 21)) } +#=> SELECT "id" FROM "customers" WHERE ("age" > 42 OR "age" < 21) +Rebel::SQL() { select :id, from: :customers, where: name(:age).lt(42) & (name(:age).gt(21)) } +#=> SELECT "id" FROM "customers" WHERE ("age" < 42 AND "age" > 21) + ``` ### Query execution diff --git a/lib/rebel/sql.rb b/lib/rebel/sql.rb index 3a5132a..b79d56d 100644 --- a/lib/rebel/sql.rb +++ b/lib/rebel/sql.rb @@ -117,26 +117,22 @@ module Rebel alias | or def eq(n) - case n - when nil - sql.raw("#{self} IS NULL") - else - sql.raw("#{self} = #{sql.name_or_value(n)}") - end + sql.raw("#{self} = #{sql.name_or_value(n)}") end alias == eq - alias is eq + + def is(n) + sql.raw("#{self} IS #{sql.name_or_value(n)}") + end def ne(n) - case n - when nil - sql.raw("#{self} IS NOT NULL") - else - sql.raw("#{self} != #{sql.name_or_value(n)}") - end + sql.raw("#{self} != #{sql.name_or_value(n)}") end alias != ne - alias is_not ne + + def is_not(n) + sql.raw("#{self} IS NOT #{sql.name_or_value(n)}") + end def lt(n) sql.raw("#{self} < #{sql.name_or_value(n)}") @@ -343,6 +339,8 @@ module Rebel case right when Array name(left).in(*right) + when nil + name(left).is(name_or_value(right)) else name(left).eq(name_or_value(right)) end diff --git a/test/test_raw.rb b/test/test_raw.rb index 76dd28c..185f84c 100644 --- a/test/test_raw.rb +++ b/test/test_raw.rb @@ -48,19 +48,19 @@ class TestRaw < Minitest::Test def test_is assert_sql('"foo" IS NULL') { name(:foo).is(nil) } - assert_sql('"foo" = 42') { name(:foo).is(42) } - assert_sql('"foo" = "bar"') { name(:foo).is(name(:bar)) } + assert_sql('"foo" IS 42') { name(:foo).is(42) } + assert_sql('"foo" IS "bar"') { name(:foo).is(name(:bar)) } end def test_is_not assert_sql('"foo" IS NOT NULL') { name(:foo).is_not(nil) } - assert_sql('"foo" != 42') { name(:foo).is_not(42) } - assert_sql('"foo" != "bar"') { name(:foo).is_not(name(:bar)) } + assert_sql('"foo" IS NOT 42') { name(:foo).is_not(42) } + assert_sql('"foo" IS NOT "bar"') { name(:foo).is_not(name(:bar)) } end def test_eq - assert_sql('"foo" IS NULL') { name(:foo).eq(nil) } - assert_sql('"foo" IS NULL') { name(:foo) == nil } + assert_sql('"foo" = NULL') { name(:foo).eq(nil) } + assert_sql('"foo" = NULL') { name(:foo) == nil } assert_sql('"foo" = "bar"') { name(:foo).eq(name(:bar)) } assert_sql('"foo" = "bar"') { name(:foo) == name(:bar) } end @@ -68,8 +68,8 @@ class TestRaw < Minitest::Test def test_ne assert_sql('"foo" != "bar"') { name(:foo).ne(name(:bar)) } assert_sql('"foo" != "bar"') { name(:foo) != name(:bar) } - assert_sql('"foo" IS NOT NULL') { name(:foo).ne(nil) } - assert_sql('"foo" IS NOT NULL') { name(:foo) != nil } + assert_sql('"foo" != NULL') { name(:foo).ne(nil) } + assert_sql('"foo" != NULL') { name(:foo) != nil } end def test_lt @@ -112,6 +112,8 @@ class TestRaw < Minitest::Test assert_sql('WHERE "foo" = 1 AND "bar" = 2 AND "baz" = 3') { where?(foo: 1, bar: 2, baz: 3) } assert_sql('WHERE ("foo" = 1 OR "bar" = 2) AND "baz" = 3') { where?(name(:foo).eq(1).or(name(:bar).eq(2)), name(:baz).eq(3)) } assert_sql('WHERE ("foo" = 1 OR "bar" = 2)') { where?(name(:foo).eq(1).or(name(:bar).eq(2))) } + assert_sql('WHERE "foo" IS NULL') { where?(foo: nil) } + assert_sql('WHERE "foo" IN (1, 2, 3)') { where?(foo: [1, 2, 3]) } end def test_join