simple-sql 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/simple/sql.rb +1 -0
- data/lib/simple/sql/connection_adapter.rb +35 -5
- data/lib/simple/sql/scope.rb +120 -0
- data/lib/simple/sql/version.rb +1 -1
- data/spec/simple/sql_scope_spec.rb +163 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54dcb90e07a18f552fcaa0f48a29ca7e60dd3888
|
4
|
+
data.tar.gz: ea2093eb587de387ecff564f46f4c86857cd5e5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35493961c28b9140d4a18508acc4d7bfe28c638fff760418173cfee3f583ba643d427ca4670eb5c4dd2d812ea7c63347b9d37bb4c82ed560d73f1651a9845199
|
7
|
+
data.tar.gz: 5978e812fec0928e0ff5dcf020409775c55406d20b635244a71a238a74fe094368bc02453106028d848594affbbcfde068982fb1b1a3b5db90c83e211cdc9d17
|
data/Gemfile.lock
CHANGED
data/lib/simple/sql.rb
CHANGED
@@ -7,6 +7,7 @@ require_relative "sql/encoder.rb"
|
|
7
7
|
require_relative "sql/config.rb"
|
8
8
|
require_relative "sql/logging.rb"
|
9
9
|
require_relative "sql/simple_transactions.rb"
|
10
|
+
require_relative "sql/scope.rb"
|
10
11
|
require_relative "sql/connection_adapter.rb"
|
11
12
|
require_relative "sql/connection.rb"
|
12
13
|
require_relative "sql/reflection.rb"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# rubocop:disable Style/IfUnlessModifier
|
2
|
+
# rubocop:disable Metrics/AbcSize
|
1
3
|
# rubocop:disable Metrics/MethodLength
|
2
4
|
|
3
5
|
# This module implements an adapter between the Simple::SQL interface
|
@@ -6,9 +8,10 @@
|
|
6
8
|
# This module can be mixed onto objects that implement a raw_connection
|
7
9
|
# method, which must return a Pg::Connection.
|
8
10
|
module Simple::SQL::ConnectionAdapter
|
9
|
-
Logging = Simple::SQL::Logging
|
10
|
-
Encoder = Simple::SQL::Encoder
|
11
|
-
Decoder = Simple::SQL::Decoder
|
11
|
+
Logging = ::Simple::SQL::Logging
|
12
|
+
Encoder = ::Simple::SQL::Encoder
|
13
|
+
Decoder = ::Simple::SQL::Decoder
|
14
|
+
Scope = ::Simple::SQL::Scope
|
12
15
|
|
13
16
|
# execute one or more sql statements. This method does not allow to pass in
|
14
17
|
# arguments - since the pg client does not support this - but it allows to
|
@@ -35,7 +38,11 @@ module Simple::SQL::ConnectionAdapter
|
|
35
38
|
|
36
39
|
def all(sql, *args, into: nil, &block)
|
37
40
|
result = exec_logged(sql, *args)
|
38
|
-
enumerate(result, into: into, &block)
|
41
|
+
result = enumerate(result, into: into, &block)
|
42
|
+
if sql.is_a?(Scope) && sql.paginated?
|
43
|
+
add_page_info(sql, result)
|
44
|
+
end
|
45
|
+
result
|
39
46
|
end
|
40
47
|
|
41
48
|
# Runs a query and returns the first result row of a query.
|
@@ -53,7 +60,30 @@ module Simple::SQL::ConnectionAdapter
|
|
53
60
|
end
|
54
61
|
end
|
55
62
|
|
56
|
-
def
|
63
|
+
def add_page_info(scope, results)
|
64
|
+
raise ArgumentError, "expect Array but get a #{results.class.name}" unless results.is_a?(Array)
|
65
|
+
raise ArgumentError, "per must be > 0" unless scope.per > 0
|
66
|
+
|
67
|
+
# optimization: add empty case (page <= 1 && results.empty?)
|
68
|
+
if scope.page <= 1 && results.empty?
|
69
|
+
Scope::PageInfo.attach(results, total_count: 0, per: scope.per, page: scope.page)
|
70
|
+
else
|
71
|
+
sql = "SELECT COUNT(*) FROM (#{scope.to_sql(pagination: false)}) simple_sql_count"
|
72
|
+
total_count = ask(sql, *scope.args)
|
73
|
+
Scope::PageInfo.attach(results, total_count: total_count, per: scope.per, page: scope.page)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def exec_logged(sql_or_scope, *args)
|
78
|
+
if sql_or_scope.is_a?(Scope)
|
79
|
+
raise ArgumentError, "You cannot call .all with a scope and additional arguments" unless args.empty?
|
80
|
+
|
81
|
+
sql = sql_or_scope.to_sql
|
82
|
+
args = sql_or_scope.args
|
83
|
+
else
|
84
|
+
sql = sql_or_scope
|
85
|
+
end
|
86
|
+
|
57
87
|
Logging.yield_logged sql, *args do
|
58
88
|
raw_connection.exec_params(sql, Encoder.encode_args(raw_connection, args))
|
59
89
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# rubocop:disable Style/Not
|
2
|
+
# rubocop:disable Style/MultipleComparison
|
3
|
+
# rubocop:disable Style/IfUnlessModifier
|
4
|
+
# rubocop:disable Metrics/AbcSize
|
5
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
6
|
+
# rubocop:disable Metrics/MethodLength
|
7
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
8
|
+
|
9
|
+
# The Simple::SQL::Scope class helps building scopes; i.e. objects
|
10
|
+
# that start as a quite basic SQL query, and allow one to add
|
11
|
+
# sql_fragments as where conditions.
|
12
|
+
class Simple::SQL::Scope
|
13
|
+
SELF = self
|
14
|
+
|
15
|
+
attr_reader :args
|
16
|
+
attr_reader :per, :page
|
17
|
+
|
18
|
+
# Build a scope object
|
19
|
+
def initialize(sql)
|
20
|
+
@sql = sql
|
21
|
+
@args = []
|
22
|
+
@filters = []
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def duplicate
|
28
|
+
dupe = SELF.new(@sql)
|
29
|
+
dupe.instance_variable_set :@args, @args.dup
|
30
|
+
dupe.instance_variable_set :@filters, @filters.dup
|
31
|
+
dupe.instance_variable_set :@per, @per
|
32
|
+
dupe.instance_variable_set :@page, @page
|
33
|
+
dupe
|
34
|
+
end
|
35
|
+
|
36
|
+
public
|
37
|
+
|
38
|
+
# scope = Scope.new("SELECT * FROM tablename")
|
39
|
+
# scope = scope.where("id > ?", 12)
|
40
|
+
#
|
41
|
+
# The placeholder (usually a '?') is being replaced with the numbered
|
42
|
+
# argument (since postgres is using $1, $2, etc.) If your SQL fragment
|
43
|
+
# uses '?' as part of some fixed text you must use an alternative
|
44
|
+
# placeholder symbol.
|
45
|
+
#
|
46
|
+
# TODO: Add support for hash arguments, i.e.
|
47
|
+
# scope = scope.where(title: "foobar")
|
48
|
+
def where(sql_fragment, arg = :__dummy__no__arg, placeholder: "?")
|
49
|
+
duplicate.send(:where!, sql_fragment, arg, placeholder: placeholder)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def where!(sql_fragment, arg = :__dummy__no__arg, placeholder: "?")
|
55
|
+
if arg == :__dummy__no__arg
|
56
|
+
@filters << sql_fragment
|
57
|
+
else
|
58
|
+
@args << arg
|
59
|
+
@filters << sql_fragment.gsub(placeholder, "$#{@args.length}")
|
60
|
+
end
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
public
|
66
|
+
|
67
|
+
# Set pagination
|
68
|
+
def paginate(per:, page:)
|
69
|
+
duplicate.send(:paginate!, per: per, page: page)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def paginate!(per:, page:)
|
75
|
+
@per = per
|
76
|
+
@page = page
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
public
|
82
|
+
|
83
|
+
# Is this a paginated scope?
|
84
|
+
def paginated?
|
85
|
+
not @per.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
# generate a sql query
|
89
|
+
def to_sql(pagination: :auto)
|
90
|
+
raise ArgumentError unless pagination == :auto || pagination == false
|
91
|
+
|
92
|
+
sql = @sql
|
93
|
+
active_filters = @filters.compact
|
94
|
+
unless active_filters.empty?
|
95
|
+
sql += " WHERE (" + active_filters.join(") AND (") + ")"
|
96
|
+
end
|
97
|
+
if pagination == :auto && @per && @page
|
98
|
+
raise ArgumentError, "per must be > 0" unless @per > 0
|
99
|
+
raise ArgumentError, "page must be > 0" unless @page > 0
|
100
|
+
|
101
|
+
sql += "LIMIT #{@per} OFFSET #{(@page - 1) * @per}"
|
102
|
+
end
|
103
|
+
|
104
|
+
sql
|
105
|
+
end
|
106
|
+
|
107
|
+
# The Scope::PageInfo module can be mixed into other objects to
|
108
|
+
# hold total_count, total_pages, and current_page.
|
109
|
+
module PageInfo
|
110
|
+
attr_reader :total_count, :total_pages, :current_page
|
111
|
+
|
112
|
+
def self.attach(results, total_count:, per:, page:)
|
113
|
+
results.extend(self)
|
114
|
+
results.instance_variable_set :@total_count, total_count
|
115
|
+
results.instance_variable_set :@total_pages, (total_count + (per - 1)) / per
|
116
|
+
results.instance_variable_set :@current_page, page
|
117
|
+
results
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/simple/sql/version.rb
CHANGED
@@ -0,0 +1,163 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Simple::SQL::Scope" do
|
4
|
+
def expects(expected_result, sql, *args)
|
5
|
+
expect(SQL.ask(sql, *args)).to eq(expected_result)
|
6
|
+
end
|
7
|
+
|
8
|
+
let!(:users) { 1.upto(2).map { create(:user) } }
|
9
|
+
|
10
|
+
it 'allows chaining of scopes' do
|
11
|
+
scope1 = SQL::Scope.new "SELECT 1, 2 FROM users"
|
12
|
+
scope2 = scope1.where("FALSE")
|
13
|
+
expect(scope1.to_sql).not_to eq(scope2.to_sql)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "without conditions" do
|
17
|
+
let(:scope) { SQL::Scope.new "SELECT 1, 2 FROM users" }
|
18
|
+
|
19
|
+
it "runs with SQL.ask" do
|
20
|
+
expect(SQL.ask(scope)).to eq([1, 2])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "runs with SQL.all" do
|
24
|
+
expect(SQL.all(scope)).to eq([[1, 2], [1, 2]])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with non-argument conditions" do
|
29
|
+
context "that do not match" do
|
30
|
+
let(:scope) do
|
31
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
32
|
+
scope = scope.where("id < 0")
|
33
|
+
scope.where("TRUE")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "runs with SQL.ask" do
|
37
|
+
expect(SQL.ask(scope)).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "runs with SQL.all" do
|
41
|
+
expect(SQL.all(scope)).to eq([])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "that do match" do
|
46
|
+
let(:scope) do
|
47
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
48
|
+
scope = scope.where("id >= 0")
|
49
|
+
scope.where("TRUE")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "runs with SQL.ask" do
|
53
|
+
expect(SQL.ask(scope)).to eq([1, 2])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "runs with SQL.all" do
|
57
|
+
expect(SQL.all(scope)).to eq([[1, 2], [1, 2]])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with argument conditions" do
|
63
|
+
context "that do not match" do
|
64
|
+
let(:scope) do
|
65
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
66
|
+
scope = scope.where("first_name NOT LIKE ?", "First%")
|
67
|
+
scope.where("id < ?", 0)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "runs with SQL.ask" do
|
71
|
+
expect(SQL.ask(scope)).to be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "runs with SQL.all" do
|
75
|
+
expect(SQL.all(scope)).to eq([])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "where both match" do
|
80
|
+
let(:scope) do
|
81
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
82
|
+
scope = scope.where("first_name LIKE ?", "First%")
|
83
|
+
scope.where("id >= ?", 0)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "runs with SQL.ask" do
|
87
|
+
expect(SQL.ask(scope)).to eq([1,2])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "runs with SQL.all" do
|
91
|
+
expect(SQL.all(scope)).to eq([[1,2], [1,2]])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "where first condition matches" do
|
96
|
+
let(:scope) do
|
97
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
98
|
+
scope = scope.where("first_name LIKE ?", "First%")
|
99
|
+
scope.where("id < ?", 0)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "runs with SQL.ask" do
|
103
|
+
expect(SQL.ask(scope)).to be_nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "where second condition matches" do
|
108
|
+
let(:scope) do
|
109
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
110
|
+
scope = scope.where("first_name LIKE ?", "Boo%")
|
111
|
+
scope.where("id >= ?", 0)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "runs with SQL.ask" do
|
115
|
+
expect(SQL.ask(scope)).to be_nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "describe pagination" do
|
121
|
+
let(:scope) do
|
122
|
+
scope = SQL::Scope.new "SELECT 1, 2 FROM users"
|
123
|
+
scope = scope.where("first_name LIKE ?", "First%")
|
124
|
+
scope.where("id > ?", 0)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "sets paginated?" do
|
128
|
+
s = scope.paginate(per: 1, page: 1)
|
129
|
+
expect(s.paginated?).to eq(true)
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with per=1" do
|
133
|
+
it "adds pagination info to the .all return value" do
|
134
|
+
result = SQL.all(scope.paginate(per: 1, page: 1))
|
135
|
+
|
136
|
+
expect(result).to eq([[1, 2]])
|
137
|
+
expect(result.total_pages).to eq(2)
|
138
|
+
expect(result.current_page).to eq(1)
|
139
|
+
expect(result.total_count).to eq(2)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "with per=2" do
|
144
|
+
it "returns an empty array after the last page" do
|
145
|
+
result = SQL.all(scope.paginate(per: 2, page: 2))
|
146
|
+
|
147
|
+
expect(result).to eq([])
|
148
|
+
expect(result.total_pages).to eq(1)
|
149
|
+
expect(result.current_page).to eq(2)
|
150
|
+
expect(result.total_count).to eq(2)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "adds pagination info to the .all return value" do
|
154
|
+
result = SQL.all(scope.paginate(per: 2, page: 1))
|
155
|
+
|
156
|
+
expect(result).to eq([[1, 2], [1, 2]])
|
157
|
+
expect(result.total_pages).to eq(1)
|
158
|
+
expect(result.current_page).to eq(1)
|
159
|
+
expect(result.total_count).to eq(2)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-04-
|
12
|
+
date: 2018-04-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg_array_parser
|
@@ -177,6 +177,7 @@ files:
|
|
177
177
|
- lib/simple/sql/insert.rb
|
178
178
|
- lib/simple/sql/logging.rb
|
179
179
|
- lib/simple/sql/reflection.rb
|
180
|
+
- lib/simple/sql/scope.rb
|
180
181
|
- lib/simple/sql/simple_transactions.rb
|
181
182
|
- lib/simple/sql/version.rb
|
182
183
|
- log/.gitkeep
|
@@ -193,6 +194,7 @@ files:
|
|
193
194
|
- spec/simple/sql_duplicate_unique_spec.rb
|
194
195
|
- spec/simple/sql_insert_spec.rb
|
195
196
|
- spec/simple/sql_reflection_spec.rb
|
197
|
+
- spec/simple/sql_scope_spec.rb
|
196
198
|
- spec/spec_helper.rb
|
197
199
|
- spec/support/001_database.rb
|
198
200
|
- spec/support/002_database_cleaner.rb
|
@@ -235,6 +237,7 @@ test_files:
|
|
235
237
|
- spec/simple/sql_duplicate_unique_spec.rb
|
236
238
|
- spec/simple/sql_insert_spec.rb
|
237
239
|
- spec/simple/sql_reflection_spec.rb
|
240
|
+
- spec/simple/sql_scope_spec.rb
|
238
241
|
- spec/spec_helper.rb
|
239
242
|
- spec/support/001_database.rb
|
240
243
|
- spec/support/002_database_cleaner.rb
|