sqlstmt 0.2.9 → 0.2.10
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/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
|