Skip to content

Commit 5e233e5

Browse files
authored
V0.8 (#124)
* Fix TimeInDay issues * Fix issue when using DISTINCT with JOIN in models with custom SELECT clause defined AFTER the joins. * Fix mistake in spec and add specs * Add changelog; update shard * Add seed command in the CLI * add `or_where` feature * Fix FTS to remove ambiguous clauses * Fix issue with nilable belongs_to which cannot be saved when set to nil * Add RFC3339 support while converting string to time * Fix caching with belongs_to * Add colorize parameter to Clear::SQL::Logger module * Migration: Add datatype conversion in add_column and alter_column methods * Migration: Update migration add_column operation to allow contraints, nullable and default value * Update to latest version of pg gem * Fix ambigous column name in with_xxx method for belongs_to relation * Add possibility to have nulls first and nulls last in `order_by` method * WIP on a SQL parser * Add the possibility to convert from Array(JSON:Any) * Fix misc typos
1 parent 06491e0 commit 5e233e5

File tree

30 files changed

+617
-86
lines changed

30 files changed

+617
-86
lines changed

manual/CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
# v0.8
2+
3+
## Features
4+
5+
- Add `or_where` clause
6+
7+
This provide a way to chain where clause with `or` operator instead of `and`:
8+
9+
```crystal
10+
query.where{ a == b }.or_where{ b == c } # WHERE (A = B) OR (b = C)
11+
query.where{ a == b }.where{ c == d}.or_where{ a == nil } # WHERE ( A=B AND C=D ) OR A IS NULL
12+
```
13+
14+
- Add `raw` method into `Clear::SQL` module.
15+
16+
This provide a fast way to create SQL fragment while escaping items, both with `?` and `:key` system:
17+
18+
```crystal
19+
query = Mode.query.select( Clear::SQL.raw("CASE WHEN x=:x THEN 1 ELSE 0 END as check", x: "blabla") )
20+
query = Mode.query.select( Clear::SQL.raw("CASE WHEN x=? THEN 1 ELSE 0 END as check", "blabla") )
21+
```
22+
23+
## Bugfixes
24+
25+
- Migrate to crystal v0.29
26+
- Fix issue with combinaison of `join`, `distinct` and `select`
27+
128
# v0.7
229

330
## Features

sample/cli/cli.cr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class ApplyChange2
3434
end
3535
end
3636

37+
Clear.seed do
38+
puts "This is a seed"
39+
end
40+
3741
Clear.with_cli do
3842
puts "Usage: crystal sample/cli/cli.cr -- clear [args]"
3943
end

shard.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: clear
2-
version: 0.7.2
2+
3+
version: 0.8
34

45
authors:
56
- Yacine Petitprez <anykeyh@gmail.com>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require "../spec_helper"
2+
3+
module ModelDifferentColumnNameSpec
4+
class Brand
5+
include Clear::Model
6+
7+
primary_key
8+
column name : String, column_name: "brand_name"
9+
self.table = "brands"
10+
end
11+
12+
class ModelDifferentColumnNameSpecMigration8273
13+
include Clear::Migration
14+
15+
def change(dir)
16+
create_table "brands" do |t|
17+
t.column "brand_name", "string"
18+
19+
t.timestamps
20+
end
21+
end
22+
end
23+
24+
def self.reinit
25+
reinit_migration_manager
26+
ModelDifferentColumnNameSpecMigration8273.new.apply(Clear::Migration::Direction::UP)
27+
end
28+
29+
describe "Clear::Model" do
30+
context "Column definition" do
31+
it "can define properties in the model with a name different of the column name in PG" do
32+
# Here the column "name" is linked to "brand_name" in postgreSQL
33+
temporary do
34+
reinit
35+
36+
Brand.create! name: "Nike"
37+
Brand.query.first!.name.should eq "Nike"
38+
Brand.query.where(brand_name: "Nike").count.should eq 1
39+
end
40+
end
41+
end
42+
end
43+
44+
45+
end

spec/model/model_spec.cr

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@ module ModelSpec
266266
#reload the model now
267267
u.reload.first_name.should eq "Malcom"
268268
u.changed?.should be_false
269+
270+
u2 = User.create!({id: 2, first_name: "y"})
271+
272+
p = Post.create! user: u, title: "Reload testing post"
273+
274+
p.user.id.should eq(1)
275+
p.user = u2 #Change the user, DO NOT SAVE.
276+
p.reload # Reload the model now:
277+
p.user.id.should eq(1) # Cache should be invalidated
269278
end
270279
end
271280

@@ -697,6 +706,25 @@ module ModelSpec
697706
user_with_a_post_minimum.with_posts.each { } # Should just execute
698707
end
699708
end
709+
710+
it "should wildcard with default model only if no select is made (before OR after)" do
711+
temporary do
712+
reinit
713+
u = User.create!({first_name: "Join User"})
714+
Post.create!({title: "A Post", user_id: u.id})
715+
716+
user_with_a_post_minimum = User.query.distinct
717+
.join(:model_posts) { model_posts.user_id == model_users.id }
718+
.select(:first_name, :last_name)
719+
720+
user_with_a_post_minimum.to_sql.should eq \
721+
"SELECT DISTINCT \"first_name\", \"last_name\" FROM \"model_users\" INNER JOIN " +
722+
"\"model_posts\" ON (\"model_posts\".\"user_id\" = \"model_users\".\"id\")"
723+
724+
user_with_a_post_minimum.with_posts.each { } # Should just execute
725+
end
726+
end
727+
700728
end
701729

702730
context "with pagination" do

spec/sql/misc_spec.cr

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@ module SQLMiscSpec
1111

1212
describe "Clear::SQL" do
1313
describe "miscalleanous" do
14+
15+
it "can escape for SQL-safe object" do
16+
Clear::SQL.escape("order").should eq "\"order\""
17+
Clear::SQL.escape("").should eq "\"\""
18+
Clear::SQL.escape(:hello).should eq "\"hello\""
19+
20+
Clear::SQL.escape("some.weird.column name").should eq "\"some.weird.column name\""
21+
Clear::SQL.escape("\"hello world\"").should eq "\"\"\"hello world\"\"\""
22+
end
23+
24+
it "can sanitize for SQL-safe string" do
25+
Clear::SQL.sanitize(1).should eq "1"
26+
Clear::SQL.sanitize("").should eq "''"
27+
Clear::SQL.sanitize(nil).should eq "NULL"
28+
Clear::SQL.sanitize("l'hotel").should eq "'l''hotel'"
29+
end
30+
31+
it "can create SQL fragment" do
32+
Clear::SQL.raw("SELECT * FROM table WHERE x = ?", "hello").should eq(
33+
%(SELECT * FROM table WHERE x = 'hello')
34+
)
35+
36+
Clear::SQL.raw("SELECT * FROM table WHERE x = :x", x: 1).should eq(
37+
%(SELECT * FROM table WHERE x = 1)
38+
)
39+
end
40+
1441
it "can truncate a table" do
1542

1643
begin

spec/sql/parser_spec.cr

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require "../spec_helper"
2+
3+
module ParserSpec
4+
extend self
5+
6+
describe "Clear::SQL" do
7+
describe "Parser" do
8+
9+
it "parse correctly" do
10+
Clear::SQL::Parser.parse(<<-SQL
11+
SELECT * FROM "users" where (id > 100 and active is null);
12+
SELECT 'string' as text;
13+
-- This is a comment
14+
SQL
15+
) do |token|
16+
pp token
17+
end
18+
end
19+
end
20+
end
21+
end

spec/sql/select_spec.cr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ module SelectSpec
5353
r.to_sql.should eq "SELECT *"
5454
end
5555

56+
it "can select distinct" do
57+
r = select_request.distinct.select("*")
58+
r.to_sql.should eq "SELECT DISTINCT *"
59+
60+
r = select_request.distinct.select("a", "b", "c")
61+
r.to_sql.should eq "SELECT DISTINCT a, b, c"
62+
63+
r = select_request.distinct.select(:first_name, :last_name, :id)
64+
r.to_sql.should eq "SELECT DISTINCT \"first_name\", \"last_name\", \"id\""
65+
end
66+
5667
it "can select any string" do
5768
r = select_request.select("1")
5869
r.to_sql.should eq "SELECT 1"
@@ -80,6 +91,13 @@ module SelectSpec
8091
end
8192
end
8293

94+
describe "the ORDER BY clause" do
95+
it "can add NULLS FIRST and NULLS LAST" do
96+
r = select_request.from("users").order_by("email", "ASC", "NULLS LAST")
97+
r.to_sql.should eq "SELECT * FROM users ORDER BY email ASC NULLS LAST"
98+
end
99+
end
100+
83101
describe "the FROM clause" do
84102
it "can build simple from" do
85103
r = select_request.from(:users)
@@ -156,6 +174,18 @@ module SelectSpec
156174
r.to_sql.should eq "SELECT * FROM \"users\" WHERE (\"user_id\" IS NULL)"
157175
end
158176

177+
it "can use or_where" do
178+
select_request.from(:users).where("a = ?", {1}).or_where("b = ?", {2}).to_sql.should(
179+
eq %(SELECT * FROM "users" WHERE ((a = 1) OR (b = 2)))
180+
)
181+
182+
# First OR WHERE acts as a simple WHERE:
183+
select_request.from(:users).or_where("a = ?", {1}).or_where("b = ?", {2}).to_sql.should(
184+
eq %(SELECT * FROM "users" WHERE ((a = 1) OR (b = 2)))
185+
)
186+
end
187+
188+
159189
it "can use `in` operators in case of array" do
160190
r = select_request.from(:users).where({user_id: [1, 2, 3, 4, "hello"]})
161191
r.to_sql.should eq "SELECT * FROM \"users\" WHERE \"user_id\" IN (1, 2, 3, 4, 'hello')"
@@ -199,6 +229,11 @@ module SelectSpec
199229
select_request.from(:users).where("a LIKE :halo AND b LIKE :world",
200230
{hello: "h", world: "w"})
201231
end
232+
233+
expect_raises Clear::SQL::QueryBuildingError do
234+
select_request.from(:users).or_where("a LIKE :halo AND b LIKE :world",
235+
{hello: "h", world: "w"})
236+
end
202237
end
203238

204239
it "can prepare group by query" do

src/clear/cli.cr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ require "admiral"
33
require "./core"
44
require "./cli/command"
55
require "./cli/migration"
6+
require "./cli/seed"
67
require "./cli/generator"
78

89
module Clear
@@ -19,6 +20,7 @@ module Clear
1920

2021
register_sub_command migrate, type: Clear::CLI::Migration
2122
register_sub_command generate, type: Clear::CLI::Generator
23+
register_sub_command seed, type: Clear::CLI::Seed
2224

2325
def run_impl
2426
STDOUT.puts help

src/clear/cli/db.cr

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)