sqlstmt 0.2.9 → 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a699b087738e5ba85981e2b914a3b9538ef09d3b
4
- data.tar.gz: d684bc830ce3a0750954c336890de5ebd594a87e
3
+ metadata.gz: 0f15173de416b124dcceb20abbd5ed52b75a02fa
4
+ data.tar.gz: 32cf06f1abe2899c9bd4a576f41652434a5f5c15
5
5
  SHA512:
6
- metadata.gz: 715b1603957d23a2e6f13d89e55fe45a9a46eaf8fcb5687b8ed84ae837098982e621fc042d9704784ddd28641d0d6a3fc1401f788612df8e080d8616fd9f25ed
7
- data.tar.gz: 2e1dd28397d42d82ecb81396f156e4d2325f118167084d831de17d35f21376e8b4360f9dc9715323c1733dd0ce6f060f597e5c9743b060fc26fc3ecd3523737a
6
+ metadata.gz: 493cc16b5693113549812f69a8eae2088cbee81afbd12ca3b7f84c7fdf83364e8e2bc8fbb01fa5af205430c465aa18acf5f6cd4041f4a91889f877d221cbbd6b
7
+ data.tar.gz: 53df18c4c4c03a10ccb8415f66cd803e29c829528f8c49352b7176dbf7d5959c285c25fe1afd17ccaf83c98c19856fa38b305162f8cdf5f22530bd61f5f4991e
data/lib/sqlstmt/data.rb CHANGED
@@ -1,12 +1,21 @@
1
+ require 'set'
2
+
1
3
  module SqlStmtLib
2
4
  extend self
3
5
 
6
+ # in the case where only one identifier is specified, :name and :alias are both set to that value
7
+ # this may be the wrong approach, but for now at least, it seems the most intuitive/useful option
4
8
  SqlTable = Struct.new(:str, :name, :alias, :index)
5
9
 
6
10
  SqlData = Struct.new(
7
11
  :stmt_type,
8
12
  :tables,
9
13
  :joins,
14
+
15
+ # set of all table names and aliases
16
+ # this includes ones added by a join
17
+ :table_ids,
18
+
10
19
  :wheres,
11
20
  :where_behavior,
12
21
  :fields,
@@ -16,7 +25,6 @@ SqlData = Struct.new(
16
25
  :limit,
17
26
  :having,
18
27
  :into_table,
19
- :rows,
20
28
  :tables_to_delete,
21
29
  :distinct,
22
30
  :straight_join,
@@ -29,12 +37,12 @@ SqlData = Struct.new(
29
37
  def initialize
30
38
  self.tables = []
31
39
  self.joins = []
40
+ self.table_ids = Set.new
32
41
  self.wheres = []
33
42
  self.where_behavior = :require
34
43
  self.fields = []
35
44
  self.values = []
36
45
  self.having = []
37
- self.rows = []
38
46
  self.tables_to_delete = []
39
47
  self.ignore = ''
40
48
  self.outfile = ''
@@ -46,11 +54,11 @@ def initialize_copy(orig)
46
54
  super
47
55
  self.tables = orig.tables.dup
48
56
  self.joins = orig.joins.dup
57
+ self.table_ids = orig.table_ids.dup
49
58
  self.wheres = orig.wheres.dup
50
59
  self.fields = orig.fields.dup
51
60
  self.values = orig.values.dup
52
61
  self.having = orig.having.dup
53
- self.rows = orig.rows.dup
54
62
  end
55
63
 
56
64
  end
@@ -24,10 +24,6 @@ class MysqlBuilder
24
24
  end
25
25
 
26
26
  def build_stmt_insert
27
- if !@data.fields.empty? && !@data.rows.empty?
28
- raise "unable to use INSERT SELECT and INSERT VALUES together, may only call :set or :add_row, but not both"
29
- end
30
-
31
27
  keyword = @data.replace ? 'REPLACE' : 'INSERT'
32
28
  value_list = @data.values.join(',')
33
29
  start_str = "#{keyword} #{@data.ignore}INTO #{@data.into_table} "
@@ -36,13 +32,8 @@ class MysqlBuilder
36
32
  start_str += "(#{field_list}) "
37
33
  end
38
34
 
39
- if @data.rows.empty?
40
- distinct_str = @data.distinct ? 'DISTINCT ' : ''
41
- return "#{start_str}SELECT #{distinct_str}#{value_list}#{build_from_clause}"
42
- else
43
- raise "DISTINCT not supported when inserting values" if @data.distinct
44
- return "#{start_str}VALUES (#{value_list})"
45
- end
35
+ distinct_str = @data.distinct ? 'DISTINCT ' : ''
36
+ return "#{start_str}SELECT #{distinct_str}#{value_list}#{build_from_clause}"
46
37
  end
47
38
 
48
39
  def build_stmt_delete
@@ -89,11 +80,19 @@ class MysqlBuilder
89
80
  return value ? " #{keywords} #{value}" : ''
90
81
  end
91
82
 
83
+ def join_to_str(join_ary)
84
+ kwstr, tbl, on_expr = join_ary
85
+ return [kwstr, tbl.str, on_expr].join(' ')
86
+ end
87
+
92
88
  def build_join_clause
93
89
  if @data.joins.empty?
94
90
  return ''
95
91
  else
96
- return ' ' + @data.joins.map {|ary| ary.join(' ')}.uniq.join(' ')
92
+ # we call uniq here to be tolerant of a table being joined to multiple times in an identical fashion
93
+ # where the intention is not actually to include the table multiple times
94
+ # but I'm thinking we may want to reconsider, or at least warn when this happens so the source bug can be fixed
95
+ return ' ' + @data.joins.map {|ary| join_to_str(ary)}.uniq.join(' ')
97
96
  end
98
97
  end
99
98
 
@@ -9,40 +9,55 @@ class MysqlChecker
9
9
  end
10
10
 
11
11
  def run
12
+ check_basics
13
+ check_where
14
+ check_statement_type_specific
15
+ end
16
+
17
+ def check_basics
12
18
  if !@data.stmt_type
13
- raise SqlStmtError, "unable to build sql - must call :select, :update, :insert or :delete to specify statement type"
19
+ raise SqlStmtError, "must call :select, :update, :insert or :delete"
14
20
  end
15
21
  if @data.tables.empty?
16
- raise SqlStmtError, "unable to build sql - must call :table"
22
+ raise SqlStmtError, "must call :table"
17
23
  end
24
+ end
18
25
 
26
+ def check_where
19
27
  if (@data.where_behavior == :require) && @data.wheres.empty?
20
- raise SqlStmtError, "unable to build sql - must call :where, :no_where, or :optional_where"
28
+ raise SqlStmtError, "must call :where, :no_where, or :optional_where"
21
29
  elsif (@data.where_behavior == :exclude) && !@data.wheres.empty?
22
- raise SqlStmtError, "unable to build sql - :where and :no_where must not both be called, consider :optional_where instead"
30
+ raise SqlStmtError, ":where and :no_where must not both be called, consider :optional_where instead"
23
31
  end
32
+ end
24
33
 
25
- if @data.stmt_type == 'select'
26
- raise SqlStmtError, "unable to build sql - must call :get" if @data.fields.empty?
27
- raise SqlStmtError, "unable to build sql - must not call :set" if !@data.values.empty?
28
- else
29
- raise SqlStmtError, "unable to build sql - must not call :get" if @data.called_get
30
- end
34
+ def check_statement_type_specific
35
+ method_name = "check_stmt_#{@data.stmt_type}"
36
+ send(method_name)
31
37
 
32
- if ['update','insert'].include?(@data.stmt_type)
33
- raise SqlStmtError, "unable to build sql - must call :set or :setq" if @data.values.empty?
34
- raise SqlStmtError, "unable to build sql - must not call :get" if @data.called_get
38
+ if @data.stmt_type != 'select'
39
+ raise SqlStmtError, "must not call :get on #{@data.stmt_type} statement" if @data.called_get
35
40
  end
41
+ end
36
42
 
37
- if @data.stmt_type == 'insert'
38
- raise SqlStmtError, "unable to build sql - must call :into" if @data.into_table.nil?
39
- end
43
+ def check_stmt_select
44
+ raise SqlStmtError, "must call :get on select statement" if @data.fields.empty?
45
+ raise SqlStmtError, "must not call :set on select statement" if !@data.values.empty?
46
+ end
47
+
48
+ def check_stmt_update
49
+ raise SqlStmtError, "must call :set on update statement" if @data.values.empty?
50
+ end
51
+
52
+ def check_stmt_insert
53
+ raise SqlStmtError, "must call :set on insert statement" if @data.values.empty?
54
+ raise SqlStmtError, "must call :into on insert statement" if @data.into_table.nil?
55
+ end
40
56
 
41
- if @data.stmt_type == 'delete'
42
- raise SqlStmtError, "unable to build sql - must not call :get or :set" if !@data.fields.empty?
43
- if @data.tables_to_delete.empty? && ((@data.tables.size + @data.joins.size) > 1)
44
- raise SqlStmtError, "unable to build sql - must specify tables to delete when including multiple tables"
45
- end
57
+ def check_stmt_delete
58
+ raise SqlStmtError, "must not call :set on delete statement" if !@data.values.empty?
59
+ if @data.tables_to_delete.empty? && ((@data.tables.size + @data.joins.size) > 1)
60
+ raise SqlStmtError, "must specify tables to delete when including multiple tables"
46
61
  end
47
62
  end
48
63
  end
@@ -12,7 +12,7 @@ require 'sqlstmt/error'
12
12
  # also, looking to the future of supporting other dialects of SQL, I think the same will be true there
13
13
  # meaning, we don't the choice of SQL dialect to be allowed at any time
14
14
 
15
- # unless there is something better to return, all methods return self so they can be chained together
15
+ # unless there is something better to return, methods return self so they can be chained together
16
16
  class SqlStmt
17
17
  attr_reader :data
18
18
 
@@ -24,60 +24,61 @@ class SqlStmt
24
24
  @data = @data.dup
25
25
  end
26
26
 
27
- ###### temporary transition methods
28
- def fields
29
- return @data.fields
30
- end
31
-
32
- def tables
33
- return @data.tables
34
- end
35
-
36
- def joins
37
- return @data.joins
38
- end
39
-
40
- def wheres
41
- return @data.wheres
42
- end
43
-
44
27
  ###### pick statement type
45
28
 
46
29
  def select
47
- return set_statement_type('select')
30
+ return type('select')
48
31
  end
49
32
 
50
33
  def update
51
- return set_statement_type('update')
34
+ return type('update')
52
35
  end
53
36
 
54
37
  def insert
55
- return set_statement_type('insert')
38
+ return type('insert')
56
39
  end
57
40
 
58
41
  def delete(*tables)
59
- set_statement_type('delete')
42
+ type('delete')
60
43
  @data.tables_to_delete = tables
61
44
  return self
62
45
  end
63
46
 
64
- ###### common operations
47
+ def type(stmt_type)
48
+ if @data.stmt_type
49
+ raise SqlStmtError, "statement type already set to #{@data.stmt_type}"
50
+ end
51
+ @data.stmt_type = stmt_type
52
+ return self
53
+ end
54
+
55
+ ###### tables & joins
65
56
 
66
- def table(table_str, use_index = nil)
67
- parts = table_str.split(' ')
68
- table_obj = SqlStmtLib::SqlTable.new(table_str, parts[0], parts[1], use_index)
69
- @data.tables << table_obj
57
+ def table(ref, use_index = nil)
58
+ @data.tables << include_table(ref, use_index)
70
59
  return self
71
60
  end
72
61
 
73
62
  def join(table, *exprs)
74
- return add_join('JOIN', table, exprs)
63
+ return any_join('JOIN', table, exprs)
75
64
  end
76
65
 
77
66
  def left_join(table, *exprs)
78
- return add_join('LEFT JOIN', table, exprs)
67
+ return any_join('LEFT JOIN', table, exprs)
68
+ end
69
+
70
+ def any_join(kwstr, ref, exprs)
71
+ tbl = include_table(ref)
72
+ @data.joins << [kwstr, tbl, "ON #{exprs.join(' AND ')}"]
73
+ return self
74
+ end
75
+
76
+ def includes_table?(table_to_find)
77
+ return @data.table_ids.include?(table_to_find)
79
78
  end
80
79
 
80
+ ###### where
81
+
81
82
  def where(*expr)
82
83
  @data.wheres.concat(expr)
83
84
  return self
@@ -93,6 +94,8 @@ class SqlStmt
93
94
  return self
94
95
  end
95
96
 
97
+ ###### fields & values
98
+
96
99
  def get(*exprs)
97
100
  @data.fields.concat(exprs)
98
101
  @data.called_get = true
@@ -100,7 +103,7 @@ class SqlStmt
100
103
  end
101
104
 
102
105
  def set(field, value)
103
- raise "trying to include field #{field} again" if @data.fields.include?(field)
106
+ raise SqlStmtError, "trying to include field #{field} again" if @data.fields.include?(field)
104
107
  # this is to support the special case of INSERT INTO table SELECT * FROM ...
105
108
  # where * specified with no matching insert field list specified
106
109
  if field
@@ -115,6 +118,8 @@ class SqlStmt
115
118
  return set(field, value.to_sql)
116
119
  end
117
120
 
121
+ ###### to be sorted
122
+
118
123
  def group_by(expr)
119
124
  @data.group_by = expr
120
125
  return self
@@ -146,13 +151,6 @@ class SqlStmt
146
151
  return self
147
152
  end
148
153
 
149
- # used with INSERT VALUES statements only
150
- def add_row(row)
151
- @data.rows << row
152
- end
153
-
154
- ###### less commonly used methods
155
-
156
154
  def distinct
157
155
  @data.distinct = true
158
156
  return self
@@ -178,32 +176,20 @@ class SqlStmt
178
176
  return self
179
177
  end
180
178
 
181
- ###### methods to analyze what the statement contains
182
- def includes_table?(table_to_find)
183
- retval = @data.tables.find { |table| (table.name == table_to_find) || (table.alias == table_to_find) }
184
- retval ||= @data.joins.find { |_, table, _| table_names_match?(table, table_to_find) }
185
- return retval
186
- end
187
-
188
179
  private
189
- def set_statement_type(stmt_type)
190
- if @data.stmt_type
191
- raise "statement type already set to #{@data.stmt_type}"
180
+ # this is used for method calls to :table and :any_join
181
+ def include_table(ref, use_index = nil)
182
+ parts = ref.split(' ')
183
+ if parts.size == 3
184
+ parts.delete_at(1)
192
185
  end
193
- @data.stmt_type = stmt_type
194
- return self
195
- end
196
-
197
- def add_join(keyword, table, exprs)
198
- @data.joins << [keyword, table, "ON #{exprs.join(' AND ')}"]
199
- return self
200
- end
186
+ @data.table_ids.merge(parts)
201
187
 
202
- def table_names_match?(fullstr, tofind)
203
- if tofind.index(' ') || !fullstr.index(' ')
204
- return fullstr == tofind
188
+ if parts.size == 2
189
+ tbl_name, tbl_alias = parts
190
+ else
191
+ tbl_name = tbl_alias = parts.first
205
192
  end
206
- orig_name, _, tblas = fullstr.partition(' ')
207
- return (orig_name == tofind) || (tblas == tofind)
193
+ return SqlStmtLib::SqlTable.new(ref, tbl_name, tbl_alias, use_index)
208
194
  end
209
195
  end
data/test/helper.rb CHANGED
@@ -1,2 +1,7 @@
1
+ require 'simplecov'
2
+ SimpleCov.command_name 'Unit Tests'
3
+ SimpleCov.add_filter "/test/"
4
+ SimpleCov.start
5
+
1
6
  require 'minitest/autorun'
2
7
  require 'sqlstmt'
data/test/select_test.rb CHANGED
@@ -2,10 +2,18 @@ require_relative 'helper'
2
2
 
3
3
  class TestSelect < Minitest::Test
4
4
  def test_includes_table
5
+ sqlb = SqlStmt.new.select.table('target')
6
+ assert(sqlb.includes_table?('target'))
7
+ assert_equal(['target'], sqlb.data.table_ids.to_a)
8
+
5
9
  sqlb = SqlStmt.new.select.table('target t')
6
10
  assert(sqlb.includes_table?('target'))
7
11
  assert(sqlb.includes_table?('t'))
8
12
  assert(!sqlb.includes_table?('blah'))
13
+
14
+ sqlb = SqlStmt.new.select.table('target AS t')
15
+ assert(sqlb.includes_table?('target'))
16
+ assert(sqlb.includes_table?('t'))
9
17
  end
10
18
 
11
19
  def test_tables
@@ -16,6 +24,7 @@ class TestSelect < Minitest::Test
16
24
 
17
25
  def test_minimum_requirements
18
26
  assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').to_s }
27
+ assert_raises(SqlStmtError) { SqlStmt.new.type('select').table('target').to_s }
19
28
  assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').no_where.to_s }
20
29
  assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').optional_where.to_s }
21
30
  assert_equal('SELECT blah FROM target', SqlStmt.new.select.table('target').optional_where.get('blah').to_sql)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlstmt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Makani Mason
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-08-02 00:00:00.000000000 Z
12
+ date: 2018-08-03 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Build SQL statements using method calls instead of strings. This is not
15
15
  an ORM. It has only been used and tested with MySQL so far but the intention is