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 +4 -4
- data/lib/sqlstmt/data.rb +11 -3
- data/lib/sqlstmt/mysql/build.rb +11 -12
- data/lib/sqlstmt/mysql/check.rb +36 -21
- data/lib/sqlstmt/sqlstmt.rb +46 -60
- data/test/helper.rb +5 -0
- data/test/select_test.rb +9 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f15173de416b124dcceb20abbd5ed52b75a02fa
|
4
|
+
data.tar.gz: 32cf06f1abe2899c9bd4a576f41652434a5f5c15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/sqlstmt/mysql/build.rb
CHANGED
@@ -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
|
-
|
40
|
-
|
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
|
-
|
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
|
|
data/lib/sqlstmt/mysql/check.rb
CHANGED
@@ -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, "
|
19
|
+
raise SqlStmtError, "must call :select, :update, :insert or :delete"
|
14
20
|
end
|
15
21
|
if @data.tables.empty?
|
16
|
-
raise SqlStmtError, "
|
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, "
|
28
|
+
raise SqlStmtError, "must call :where, :no_where, or :optional_where"
|
21
29
|
elsif (@data.where_behavior == :exclude) && !@data.wheres.empty?
|
22
|
-
raise SqlStmtError, "
|
30
|
+
raise SqlStmtError, ":where and :no_where must not both be called, consider :optional_where instead"
|
23
31
|
end
|
32
|
+
end
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
33
|
-
raise SqlStmtError, "
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/sqlstmt/sqlstmt.rb
CHANGED
@@ -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,
|
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
|
30
|
+
return type('select')
|
48
31
|
end
|
49
32
|
|
50
33
|
def update
|
51
|
-
return
|
34
|
+
return type('update')
|
52
35
|
end
|
53
36
|
|
54
37
|
def insert
|
55
|
-
return
|
38
|
+
return type('insert')
|
56
39
|
end
|
57
40
|
|
58
41
|
def delete(*tables)
|
59
|
-
|
42
|
+
type('delete')
|
60
43
|
@data.tables_to_delete = tables
|
61
44
|
return self
|
62
45
|
end
|
63
46
|
|
64
|
-
|
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(
|
67
|
-
|
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
|
63
|
+
return any_join('JOIN', table, exprs)
|
75
64
|
end
|
76
65
|
|
77
66
|
def left_join(table, *exprs)
|
78
|
-
return
|
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
|
-
|
190
|
-
|
191
|
-
|
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.
|
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
|
-
|
203
|
-
|
204
|
-
|
188
|
+
if parts.size == 2
|
189
|
+
tbl_name, tbl_alias = parts
|
190
|
+
else
|
191
|
+
tbl_name = tbl_alias = parts.first
|
205
192
|
end
|
206
|
-
|
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
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.
|
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-
|
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
|