simple-sql 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|