sqb 1.0.2 → 1.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36cfacce28d143bb31c2b714cc2b4a99744a7dfd117832e0952979767bfcd7af
4
- data.tar.gz: 72efb2953532af5f444b55833e3342138bfe68a94560567658e81981d38db043
3
+ metadata.gz: b3c57f511a3e18b3718cbbcd5e6703b52bd0b3d6e92ead8aca02a04ef46b087a
4
+ data.tar.gz: 2fd0e2d396c722bf2d0fcac632a8322be95ab3aa65b64ba70d86b62ca5859d98
5
5
  SHA512:
6
- metadata.gz: 9bad00a3fad97d424d51d7a150ca64055885fdd730d8c925712895176ac6fa61e575433f3bc18b90f165d5a2fa2bda5b32c5e07ba463d0460d39578c9cdcaa76
7
- data.tar.gz: 0b927ede58eaa630e8a665a1d5ee9e564eaaebfd96d8ed29a66a0b0606aab22fe000f75142428386084d512d6238f3450a1642ebf13bdce57bade20a449023a6
6
+ metadata.gz: ba298f55cf459a29818711e146c78b58911a1673c277879d013f2333e31729b733fa686af429931797aad1b1d5dfb0ea43c0b31364d4ac3c1f68f99ded75d4d9
7
+ data.tar.gz: 8e4c5e633e78f78689a2d60e8574bb5e86de9d0c0305ace9e4c46e64daadc9c971a3615da18fa5825e295081e2b3eabd6c61b43d9f185b18de10f98ebdfa83e0
data/lib/sqb.rb CHANGED
@@ -1,7 +1,12 @@
1
- require 'sqb/query'
2
1
  require 'sqb/version'
3
2
  require 'sqb/safe_string'
4
3
 
4
+ require 'sqb/query'
5
+ require 'sqb/select'
6
+ require 'sqb/update'
7
+ require 'sqb/delete'
8
+ require 'sqb/insert'
9
+
5
10
  module SQB
6
11
  def self.safe(string)
7
12
  SafeString.new(string)
data/lib/sqb/base.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'sqb/error'
2
+ require 'sqb/escaping'
3
+
4
+ module SQB
5
+ class Base
6
+
7
+ include SQB::Escaping
8
+
9
+ attr_reader :prepared_arguments
10
+
11
+ def initialize(table_name, options = {}, &block)
12
+ @table_name = table_name
13
+ @options = options
14
+ @prepared_arguments = []
15
+ block.call(self) if block_given?
16
+ end
17
+
18
+ # Generate the full SQL query for this query.
19
+ #
20
+ # @return [String]
21
+ def to_sql
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module SQB
2
+ module Columns
3
+
4
+ # Add a column to the query
5
+ #
6
+ # @param column [String, Symbol, Hash] the column name (or a hash with table & column name)
7
+ # @option options [String] :function a function to wrap around the column
8
+ # @options options [String] :as the name to return this column as
9
+ # @return [Query] returns the query
10
+ def column(column, options = {})
11
+ @columns ||= []
12
+ with_table_and_column(column) do |table, column|
13
+ @columns << [].tap do |query|
14
+ if options[:function]
15
+ query << "#{escape_function(options[:function])}("
16
+ end
17
+ query << escape_and_join(table, column)
18
+ if options[:function]
19
+ query << ")"
20
+ end
21
+ if options[:as]
22
+ query << "AS"
23
+ query << escape(options[:as])
24
+ end
25
+ end.join(' ')
26
+ end
27
+ self
28
+ end
29
+
30
+ # Replace all existing columns with the given column
31
+ def column!(*args)
32
+ @columns = []
33
+ column(*args)
34
+ end
35
+
36
+
37
+ end
38
+ end
data/lib/sqb/delete.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'sqb/base'
2
+ require 'sqb/filtering'
3
+ require 'sqb/ordering'
4
+ require 'sqb/limiting'
5
+
6
+ module SQB
7
+ class Delete < Base
8
+
9
+ include SQB::Filtering
10
+ include SQB::Ordering
11
+ include SQB::Limiting
12
+
13
+ def to_sql
14
+ [].tap do |query|
15
+ query << "DELETE FROM"
16
+ query << escape_and_join(@options[:database_name], @table_name)
17
+
18
+ if @where && !@where.empty?
19
+ query << "WHERE"
20
+ query << @where.join(' AND ')
21
+ end
22
+
23
+ if @orders && !@orders.empty?
24
+ query << "ORDER BY"
25
+ query << @orders.join(', ')
26
+ end
27
+
28
+ if @limit
29
+ query << "LIMIT #{@limit.to_i}"
30
+ end
31
+ end.join(' ')
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ module SQB
2
+ module Distinct
3
+
4
+ def distinct
5
+ @distinct = true
6
+ self
7
+ end
8
+
9
+ end
10
+ end
data/lib/sqb/error.rb CHANGED
@@ -10,4 +10,7 @@ module SQB
10
10
 
11
11
  class InvalidOperatorError < Error
12
12
  end
13
+
14
+ class NoValuesError < Error
15
+ end
13
16
  end
@@ -0,0 +1,56 @@
1
+ module SQB
2
+ module Escaping
3
+
4
+ private
5
+
6
+ def escape(name)
7
+ if name.is_a?(SafeString)
8
+ name
9
+ else
10
+ "`#{name.to_s.gsub('`', '``')}`"
11
+ end
12
+ end
13
+
14
+ def escape_function(name)
15
+ if name.is_a?(SafeString)
16
+ name
17
+ else
18
+ name.to_s.gsub(/[^a-z0-9\_]/i, '').upcase
19
+ end
20
+ end
21
+
22
+ def value_escape(value)
23
+ if value == true
24
+ 1
25
+ elsif value == false
26
+ 0
27
+ elsif value.nil?
28
+ 'NULL'
29
+ elsif value.is_a?(Integer)
30
+ value.to_i
31
+ else
32
+ @prepared_arguments << value.to_s
33
+ '?'
34
+ end
35
+ end
36
+
37
+ def with_table_and_column(input, &block)
38
+ if input.is_a?(Hash)
39
+ input.each { |table, column| block.call(table, column) }
40
+ else
41
+ block.call(@table_name, input)
42
+ end
43
+ end
44
+
45
+ def escape_and_join(*parts)
46
+ if parts.last.is_a?(SafeString)
47
+ # If a safe string is provided as a column name, we'll
48
+ # always use this even if a table name is provided too.
49
+ parts.last
50
+ else
51
+ parts.compact.map { |part| escape(part) }.join('.')
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,109 @@
1
+ module SQB
2
+ module Filtering
3
+
4
+ # Add a condition to the query by providing a hash of keys and values.
5
+ #
6
+ # @param hash [Hash]
7
+ # @return [Query]
8
+ def where(hash)
9
+ if @where_within_or && @where_within_or.last
10
+ @where_within_or.last << hash
11
+ else
12
+ @where ||= []
13
+ @where << hash_to_sql(hash, @table_name)
14
+ end
15
+ self
16
+ end
17
+
18
+ # Set that all conditions added in this block should be joined using OR
19
+ # rather than AND.
20
+ def or(&block)
21
+ @where_within_or ||= []
22
+ # Start by making an array within the OR block for this calling
23
+ @where_within_or << []
24
+ # Execute the block. All queries to 'where' will be added to the last
25
+ # array in the chain (created above)
26
+ block.call
27
+ ensure
28
+ # Start work on a full array of SQL fragments for all OR queries
29
+ @where_within_or_sql ||= []
30
+ # After each OR call, store up the SQL fragment for all where queries
31
+ # executed within the block.
32
+ if w = @where_within_or.pop
33
+ @where_within_or_sql << w.map do |w|
34
+ hash_to_sql(w, @table_name)
35
+ end.join(' OR ')
36
+ end
37
+
38
+ # When there are no fragments in the chain left, add it to the main
39
+ # where chain for the query.
40
+ if @where_within_or.empty?
41
+ @where ||= []
42
+ @where << "(#{@where_within_or_sql.flatten.join(' OR ')})"
43
+ @where_within_or_sql = nil
44
+ end
45
+ self
46
+ end
47
+
48
+ private
49
+
50
+ def hash_to_sql(hash, table, joiner = ' AND ')
51
+ sql = hash.map do |key, value|
52
+ if key.is_a?(Hash)
53
+ table = key.first[0]
54
+ key = key.first[1]
55
+ end
56
+
57
+ key = escape_and_join(table, key)
58
+
59
+ if value.is_a?(Array)
60
+ escaped_values = value.map { |v| value_escape(v) }.join(', ')
61
+ "#{key} IN (#{escaped_values})"
62
+ elsif value.is_a?(Hash)
63
+ sql = []
64
+ value.each do |operator, value|
65
+ case operator
66
+ when :not_equal
67
+ if value.nil?
68
+ sql << "#{key} IS NOT NULL"
69
+ else
70
+ sql << "#{key} != #{value_escape(value)}"
71
+ end
72
+ when :equal
73
+ if value.nil?
74
+ sql << "#{key} IS NULL"
75
+ else
76
+ sql << "#{key} = #{value_escape(value)}"
77
+ end
78
+ when :less_than
79
+ sql << "#{key} < #{value_escape(value)}"
80
+ when :greater_than
81
+ sql << "#{key} > #{value_escape(value)}"
82
+ when :less_than_or_equal_to
83
+ sql << "#{key} <= #{value_escape(value)}"
84
+ when :greater_than_or_equal_to
85
+ sql << "#{key} >= #{value_escape(value)}"
86
+ when :in, :not_in
87
+ escaped_values = value.map { |v| value_escape(v) }.join(', ')
88
+ op = operator == :in ? "IN" : "NOT IN"
89
+ sql << "#{key} #{op} (#{escaped_values})"
90
+ when :like
91
+ sql << "#{key} LIKE #{value_escape(value)}"
92
+ when :not_like
93
+ sql << "#{key} NOT LIKE #{value_escape(value)}"
94
+ else
95
+ raise InvalidOperatorError, "Invalid operator '#{operator}'"
96
+ end
97
+ end
98
+ sql.empty? ? "1=0" : sql.join(joiner)
99
+ elsif value == nil
100
+ "#{key} IS NULL"
101
+ else
102
+ "#{key} = #{value_escape(value)}"
103
+ end
104
+ end.join(joiner)
105
+ "(#{sql})"
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,17 @@
1
+ module SQB
2
+ module Grouping
3
+
4
+ # Add a grouping
5
+ #
6
+ # @param column [String, Symbol, Hash]
7
+ # @return [Query]
8
+ def group_by(column)
9
+ @groups ||= []
10
+ with_table_and_column(column) do |table, column|
11
+ @groups << escape_and_join(table, column)
12
+ end
13
+ self
14
+ end
15
+
16
+ end
17
+ end
data/lib/sqb/insert.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'sqb/base'
2
+
3
+ module SQB
4
+ class Insert < Base
5
+
6
+ def to_sql
7
+ [].tap do |query|
8
+ query << "INSERT INTO"
9
+ query << escape_and_join(@options[:database_name], @table_name)
10
+ if @values.nil? || @values.empty?
11
+ raise NoValuesError, "No values have been specified. Use `value` to add values to the query."
12
+ end
13
+ query << "(#{columns.join(', ')})"
14
+ query << "VALUES"
15
+ query << "(#{values.join(', ')})"
16
+ end.join(' ')
17
+ end
18
+
19
+ # Set a value to be inserted
20
+ #
21
+ # @param key [String]
22
+ # @param value [String, nil]
23
+ def value(hash)
24
+ @values ||= {}
25
+ hash.each do |key, value|
26
+ @values[key] = value
27
+ end
28
+ self
29
+ end
30
+ alias_method :values, :value
31
+
32
+ private
33
+
34
+ def columns
35
+ @values.keys.map { |k| escape(k) }
36
+ end
37
+
38
+ def values
39
+ @values.values.map { |v| value_escape(v) }
40
+ end
41
+
42
+ end
43
+ end
data/lib/sqb/joins.rb ADDED
@@ -0,0 +1,55 @@
1
+ module SQB
2
+ module Joins
3
+
4
+ # Add a join
5
+ #
6
+ # @param table_name [String, Symbol]
7
+ # @param foreign_key [String, Symbol]
8
+ # @option options [Hash] :where
9
+ # @option options [Array] :select
10
+ # @return [Query]
11
+ def join(table_name, foreign_key, options = {})
12
+ @joins ||= []
13
+ @joins_name_mapping ||= {}
14
+
15
+ if options[:name]
16
+ join_name = options[:name]
17
+ else
18
+ @joins_name_mapping[table_name] ||= 0
19
+ join_name= "#{table_name}_#{@joins_name_mapping[table_name]}"
20
+ @joins_name_mapping[table_name] += 1
21
+ end
22
+
23
+ @joins << [].tap do |query|
24
+ query << "INNER JOIN"
25
+ query << escape_and_join(@options[:database_name], table_name)
26
+ query << "AS"
27
+ query << escape(join_name)
28
+ query << "ON"
29
+ query << escape_and_join(@table_name, 'id')
30
+ query << "="
31
+ query << escape_and_join(join_name, foreign_key)
32
+ end.join(' ')
33
+
34
+ if options[:where]
35
+ join_where = options[:where].each_with_object({}) do |(column, value), hash|
36
+ hash[{join_name => column}] = value
37
+ end
38
+ where(join_where)
39
+ end
40
+
41
+ if columns = options[:columns]
42
+ for field in columns
43
+ column({join_name => field}, :as => "#{join_name}_#{field}")
44
+ end
45
+ end
46
+
47
+ if g = options[:group_by]
48
+ group_by(join_name => g.is_a?(Symbol) ? g : :id)
49
+ end
50
+
51
+ self
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,14 @@
1
+ module SQB
2
+ module Limiting
3
+
4
+ # Limit the number of records return
5
+ #
6
+ # @param number [Integer]
7
+ # @return [Query]
8
+ def limit(number)
9
+ @limit = number&.to_i
10
+ self
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module SQB
2
+ module Offsetting
3
+
4
+ # Set the offset
5
+ #
6
+ # @param number [Integer]
7
+ # @return [Query]
8
+ def offset(number)
9
+ @offset = number&.to_i
10
+ self
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,39 @@
1
+ module SQB
2
+ module Ordering
3
+
4
+ VALID_ORDERS = ['ASC', 'DESC']
5
+
6
+ # Add an order column
7
+ #
8
+ # @param column [String, Symbol, Hash]
9
+ # @param direction [String] 'ASC' or 'DESC' (default 'ASC')
10
+ # @return [Query]
11
+ def order(column, direction = nil)
12
+ direction = direction ? direction.to_s.upcase : 'ASC'
13
+
14
+ unless VALID_ORDERS.include?(direction)
15
+ raise InvalidOrderDirectionError, "Invalid order direction #{direction}"
16
+ end
17
+
18
+ @orders ||= []
19
+
20
+ with_table_and_column(column) do |table, column|
21
+ @orders << [escape_and_join(table, column), direction].join(' ')
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ # Add an order replacing all previous ones
28
+ def order!(*args)
29
+ @orders = []
30
+ order(*args)
31
+ end
32
+
33
+ # Remove all ordering for this query
34
+ def no_order!
35
+ @orders = []
36
+ end
37
+
38
+ end
39
+ end
data/lib/sqb/query.rb CHANGED
@@ -1,375 +1,6 @@
1
- require 'sqb/error'
1
+ require 'sqb/select'
2
2
 
3
3
  module SQB
4
- class Query
5
-
6
- attr_reader :prepared_arguments
7
-
8
- VALID_ORDERS = ['ASC', 'DESC']
9
-
10
- def initialize(table_name, options = {}, &escape_block)
11
- @table_name = table_name
12
- @columns = []
13
- @joins = []
14
- @joins_name_mapping = {}
15
- @where = []
16
- @orders = []
17
- @groups = []
18
- @limit = nil
19
- @offset = nil
20
- @distinct = false
21
- @where_within_or = []
22
- @options = options
23
- @prepared_arguments = []
24
-
25
- if @options[:prepared] == false && escape_block.nil?
26
- raise EscapeBlockMissingError, "An escape block must be provided if prepared statements are disabled."
27
- else
28
- @escape_block = escape_block
29
- end
30
- end
31
-
32
- # Generate the full SQL query for this query.
33
- #
34
- # @return [String]
35
- def to_sql
36
- [].tap do |query|
37
- query << "SELECT"
38
- query << "DISTINCT" if @distinct
39
- if @columns.empty?
40
- query << escape_and_join(@table_name, '*')
41
- else
42
- query << @columns.join(', ')
43
- end
44
- query << "FROM"
45
- query << escape_and_join(@options[:database_name], @table_name)
46
-
47
- unless @joins.empty?
48
- query << @joins.join(' ')
49
- end
50
-
51
- unless @where.empty?
52
- query << "WHERE"
53
- query << @where.join(' AND ')
54
- end
55
-
56
- unless @groups.empty?
57
- query << "GROUP BY"
58
- query << @groups.join(', ')
59
- end
60
-
61
- unless @orders.empty?
62
- query << "ORDER BY"
63
- query << @orders.join(', ')
64
- end
65
-
66
- if @limit
67
- query << "LIMIT #{@limit.to_i}"
68
- end
69
-
70
- if @offset
71
- query << "OFFSET #{@offset.to_i}"
72
- end
73
- end.join(' ')
74
- end
75
-
76
- # Add a column to the query
77
- #
78
- # @param column [String, Symbol, Hash] the column name (or a hash with table & column name)
79
- # @option options [String] :function a function to wrap around the column
80
- # @options options [String] :as the name to return this column as
81
- # @return [Query] returns the query
82
- def column(column, options = {})
83
- with_table_and_column(column) do |table, column|
84
- @columns << [].tap do |query|
85
- if options[:function]
86
- query << "#{escape_function(options[:function])}("
87
- end
88
- query << escape_and_join(table, column)
89
- if options[:function]
90
- query << ")"
91
- end
92
- if options[:as]
93
- query << "AS"
94
- query << escape(options[:as])
95
- end
96
- end.join(' ')
97
- end
98
- self
99
- end
100
-
101
- # Replace all existing columns with the given column
102
- def column!(*args)
103
- @columns = []
104
- column(*args)
105
- end
106
-
107
- # Add a condition to the query by providing a hash of keys and values.
108
- #
109
- # @param hash [Hash]
110
- # @return [Query]
111
- def where(hash)
112
- if @where_within_or.last
113
- @where_within_or.last << hash
114
- else
115
- @where << hash_to_sql(hash, @table_name)
116
- end
117
- self
118
- end
119
-
120
- # Set that all conditions added in this block should be joined using OR
121
- # rather than AND.
122
- def or(&block)
123
- # Start by making an array within the OR block for this calling
124
- @where_within_or << []
125
- # Execute the block. All queries to 'where' will be added to the last
126
- # array in the chain (created above)
127
- block.call
128
- ensure
129
- # Start work on a full array of SQL fragments for all OR queries
130
- @where_within_or_sql ||= []
131
- # After each OR call, store up the SQL fragment for all where queries
132
- # executed within the block.
133
- if w = @where_within_or.pop
134
- @where_within_or_sql << w.map do |w|
135
- hash_to_sql(w, @table_name)
136
- end.join(' OR ')
137
- end
138
-
139
- # When there are no fragments in the chain left, add it to the main
140
- # where chain for the query.
141
- if @where_within_or.empty?
142
- @where << "(#{@where_within_or_sql.flatten.join(' OR ')})"
143
- @where_within_or_sql = nil
144
- end
145
- self
146
- end
147
-
148
- # Limit the number of records return
149
- #
150
- # @param number [Integer]
151
- # @return [Query]
152
- def limit(number)
153
- @limit = number&.to_i
154
- self
155
- end
156
-
157
- # Set the offset
158
- #
159
- # @param number [Integer]
160
- # @return [Query]
161
- def offset(number)
162
- @offset = number&.to_i
163
- self
164
- end
165
-
166
- # Add an order column
167
- #
168
- # @param column [String, Symbol, Hash]
169
- # @param direction [String] 'ASC' or 'DESC' (default 'ASC')
170
- # @return [Query]
171
- def order(column, direction = nil)
172
- direction = direction ? direction.to_s.upcase : 'ASC'
173
-
174
- unless VALID_ORDERS.include?(direction)
175
- raise InvalidOrderDirectionError, "Invalid order direction #{direction}"
176
- end
177
-
178
- with_table_and_column(column) do |table, column|
179
- @orders << [escape_and_join(table, column), direction].join(' ')
180
- end
181
-
182
- self
183
- end
184
-
185
- # Add an order replacing all previous ones
186
- def order!(*args)
187
- @orders = []
188
- order(*args)
189
- end
190
-
191
- # Remove all ordering for this query
192
- def no_order!
193
- @orders = []
194
- end
195
-
196
- # Add a grouping
197
- #
198
- # @param column [String, Symbol, Hash]
199
- # @return [Query]
200
- def group_by(column)
201
- with_table_and_column(column) do |table, column|
202
- @groups << escape_and_join(table, column)
203
- end
204
- self
205
- end
206
-
207
- # Add a join
208
- #
209
- # @param table_name [String, Symbol]
210
- # @param foreign_key [String, Symbol]
211
- # @option options [Hash] :where
212
- # @option options [Array] :select
213
- # @return [Query]
214
- def join(table_name, foreign_key, options = {})
215
-
216
- if options[:name]
217
- join_name = options[:name]
218
- else
219
- @joins_name_mapping[table_name] ||= 0
220
- join_name= "#{table_name}_#{@joins_name_mapping[table_name]}"
221
- @joins_name_mapping[table_name] += 1
222
- end
223
-
224
- @joins << [].tap do |query|
225
- query << "INNER JOIN"
226
- query << escape_and_join(@options[:database_name], table_name)
227
- query << "AS"
228
- query << escape(join_name)
229
- query << "ON"
230
- query << escape_and_join(@table_name, 'id')
231
- query << "="
232
- query << escape_and_join(join_name, foreign_key)
233
- end.join(' ')
234
-
235
- if options[:where]
236
- join_where = options[:where].each_with_object({}) do |(column, value), hash|
237
- hash[{join_name => column}] = value
238
- end
239
- where(join_where)
240
- end
241
-
242
- if columns = options[:columns]
243
- for field in columns
244
- column({join_name => field}, :as => "#{join_name}_#{field}")
245
- end
246
- end
247
-
248
- if g = options[:group_by]
249
- group_by(join_name => g.is_a?(Symbol) ? g : :id)
250
- end
251
-
252
- self
253
- end
254
-
255
- def distinct
256
- @distinct = true
257
- self
258
- end
259
-
260
- private
261
-
262
- def hash_to_sql(hash, table, joiner = ' AND ')
263
- sql = hash.map do |key, value|
264
- if key.is_a?(Hash)
265
- table = key.first[0]
266
- key = key.first[1]
267
- end
268
-
269
- key = escape_and_join(table, key)
270
-
271
- if value.is_a?(Array)
272
- escaped_values = value.map { |v| value_escape(v) }.join(', ')
273
- "#{key} IN (#{escaped_values})"
274
- elsif value.is_a?(Hash)
275
- sql = []
276
- value.each do |operator, value|
277
- case operator
278
- when :not_equal
279
- if value.nil?
280
- sql << "#{key} IS NOT NULL"
281
- else
282
- sql << "#{key} != #{value_escape(value)}"
283
- end
284
- when :equal
285
- if value.nil?
286
- sql << "#{key} IS NULL"
287
- else
288
- sql << "#{key} = #{value_escape(value)}"
289
- end
290
- when :less_than
291
- sql << "#{key} < #{value_escape(value)}"
292
- when :greater_than
293
- sql << "#{key} > #{value_escape(value)}"
294
- when :less_than_or_equal_to
295
- sql << "#{key} <= #{value_escape(value)}"
296
- when :greater_than_or_equal_to
297
- sql << "#{key} >= #{value_escape(value)}"
298
- when :in, :not_in
299
- escaped_values = value.map { |v| value_escape(v) }.join(', ')
300
- op = operator == :in ? "IN" : "NOT IN"
301
- sql << "#{key} #{op} (#{escaped_values})"
302
- when :like
303
- sql << "#{key} LIKE #{value_escape(value)}"
304
- when :not_like
305
- sql << "#{key} NOT LIKE #{value_escape(value)}"
306
- else
307
- raise InvalidOperatorError, "Invalid operator '#{operator}'"
308
- end
309
- end
310
- sql.empty? ? "1=0" : sql.join(joiner)
311
- elsif value == nil
312
- "#{key} IS NULL"
313
- else
314
- "#{key} = #{value_escape(value)}"
315
- end
316
- end.join(joiner)
317
- "(#{sql})"
318
- end
319
-
320
- def escape(name)
321
- if name.is_a?(SafeString)
322
- name
323
- else
324
- "`#{name.to_s.gsub('`', '``')}`"
325
- end
326
- end
327
-
328
- def escape_function(name)
329
- if name.is_a?(SafeString)
330
- name
331
- else
332
- name.to_s.gsub(/[^a-z0-9\_]/i, '').upcase
333
- end
334
- end
335
-
336
- def value_escape(value)
337
- if value == true
338
- 1
339
- elsif value == false
340
- 0
341
- elsif value.nil?
342
- 'NULL'
343
- elsif value.is_a?(Integer)
344
- value.to_i
345
- else
346
- if @options[:prepared] == false
347
- escaped_value = @escape_block ? @escape_block.call(value.to_s) : value.to_s
348
- "'" + escaped_value + "'"
349
- else
350
- @prepared_arguments << value.to_s
351
- '?'
352
- end
353
- end
354
- end
355
-
356
- def with_table_and_column(input, &block)
357
- if input.is_a?(Hash)
358
- input.each { |table, column| block.call(table, column) }
359
- else
360
- block.call(@table_name, input)
361
- end
362
- end
363
-
364
- def escape_and_join(*parts)
365
- if parts.last.is_a?(SafeString)
366
- # If a safe string is provided as a column name, we'll
367
- # always use this even if a table name is provided too.
368
- parts.last
369
- else
370
- parts.compact.map { |part| escape(part) }.join('.')
371
- end
372
- end
373
-
4
+ class Query < Select
374
5
  end
375
6
  end
@@ -1,5 +1,4 @@
1
1
  module SQB
2
2
  class SafeString < String
3
-
4
3
  end
5
4
  end
data/lib/sqb/select.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'sqb/base'
2
+ require 'sqb/distinct'
3
+ require 'sqb/columns'
4
+ require 'sqb/filtering'
5
+ require 'sqb/joins'
6
+ require 'sqb/ordering'
7
+ require 'sqb/grouping'
8
+ require 'sqb/limiting'
9
+ require 'sqb/offsetting'
10
+
11
+ module SQB
12
+ class Select < Base
13
+
14
+ include SQB::Distinct
15
+ include SQB::Columns
16
+ include SQB::Filtering
17
+ include SQB::Joins
18
+ include SQB::Ordering
19
+ include SQB::Grouping
20
+ include SQB::Limiting
21
+ include SQB::Offsetting
22
+
23
+ def to_sql
24
+ [].tap do |query|
25
+ query << "SELECT"
26
+ query << "DISTINCT" if @distinct
27
+ if @columns.nil? || @columns.empty?
28
+ query << escape_and_join(@table_name, '*')
29
+ else
30
+ query << @columns.join(', ')
31
+ end
32
+
33
+ query << "FROM"
34
+ query << escape_and_join(@options[:database_name], @table_name)
35
+
36
+ if @joins && !@joins.empty?
37
+ query << @joins.join(' ')
38
+ end
39
+
40
+ if @where && !@where.empty?
41
+ query << "WHERE"
42
+ query << @where.join(' AND ')
43
+ end
44
+
45
+ if @groups && !@groups.empty?
46
+ query << "GROUP BY"
47
+ query << @groups.join(', ')
48
+ end
49
+
50
+ if @orders && !@orders.empty?
51
+ query << "ORDER BY"
52
+ query << @orders.join(', ')
53
+ end
54
+
55
+ if @limit
56
+ query << "LIMIT #{@limit.to_i}"
57
+ end
58
+
59
+ if @offset
60
+ query << "OFFSET #{@offset.to_i}"
61
+ end
62
+ end.join(' ')
63
+ end
64
+
65
+ end
66
+ end
data/lib/sqb/update.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'sqb/base'
2
+ require 'sqb/filtering'
3
+ require 'sqb/limiting'
4
+
5
+ module SQB
6
+ class Update < Base
7
+
8
+ include SQB::Filtering
9
+ include SQB::Limiting
10
+
11
+ def to_sql
12
+ [].tap do |query|
13
+ query << "UPDATE"
14
+ query << escape_and_join(@options[:database_name], @table_name)
15
+ query << "SET"
16
+ if @sets && !@sets.empty?
17
+ query << @sets.map do |key, value|
18
+ "#{escape_and_join(@table_name, key)} = #{value_escape(value)}"
19
+ end.join(', ')
20
+ else
21
+ raise NoValuesError, "No values have been updated. Use `set` to set the values to update."
22
+ end
23
+
24
+ if @where && !@where.empty?
25
+ query << "WHERE"
26
+ query << @where.join(' AND ')
27
+ end
28
+
29
+ if @limit
30
+ query << "LIMIT #{@limit.to_i}"
31
+ end
32
+
33
+ if @offset
34
+ query << "OFFSET #{@offset.to_i}"
35
+ end
36
+ end.join(' ')
37
+ end
38
+
39
+ # Set a value to be updated
40
+ #
41
+ # @param key [String]
42
+ # @param value [String, nil]
43
+ def set(hash)
44
+ @sets ||= {}
45
+ hash.each do |key, value|
46
+ @sets[key] = value
47
+ end
48
+ self
49
+ end
50
+
51
+ end
52
+ end
data/lib/sqb/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module SQB
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.3'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Cooke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-27 00:00:00.000000000 Z
11
+ date: 2018-02-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A friendly SQL builder for MySQL.
14
14
  email:
@@ -18,9 +18,23 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - lib/sqb.rb
21
+ - lib/sqb/base.rb
22
+ - lib/sqb/columns.rb
23
+ - lib/sqb/delete.rb
24
+ - lib/sqb/distinct.rb
21
25
  - lib/sqb/error.rb
26
+ - lib/sqb/escaping.rb
27
+ - lib/sqb/filtering.rb
28
+ - lib/sqb/grouping.rb
29
+ - lib/sqb/insert.rb
30
+ - lib/sqb/joins.rb
31
+ - lib/sqb/limiting.rb
32
+ - lib/sqb/offsetting.rb
33
+ - lib/sqb/ordering.rb
22
34
  - lib/sqb/query.rb
23
35
  - lib/sqb/safe_string.rb
36
+ - lib/sqb/select.rb
37
+ - lib/sqb/update.rb
24
38
  - lib/sqb/version.rb
25
39
  homepage: https://github.com/adamcooke/sqb
26
40
  licenses: