rdl 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +7 -6
- data/CHANGES.md +29 -0
- data/README.md +94 -26
- data/lib/rdl/boot.rb +82 -41
- data/lib/rdl/boot_rails.rb +5 -0
- data/lib/rdl/config.rb +9 -1
- data/lib/rdl/query.rb +2 -2
- data/lib/rdl/typecheck.rb +972 -225
- data/lib/rdl/types/annotated_arg.rb +8 -0
- data/lib/rdl/types/ast_node.rb +73 -0
- data/lib/rdl/types/bot.rb +8 -0
- data/lib/rdl/types/bound_arg.rb +63 -0
- data/lib/rdl/types/computed.rb +48 -0
- data/lib/rdl/types/dependent_arg.rb +9 -0
- data/lib/rdl/types/dynamic.rb +61 -0
- data/lib/rdl/types/finite_hash.rb +54 -9
- data/lib/rdl/types/generic.rb +33 -0
- data/lib/rdl/types/intersection.rb +8 -0
- data/lib/rdl/types/lexer.rex +6 -1
- data/lib/rdl/types/lexer.rex.rb +13 -1
- data/lib/rdl/types/method.rb +14 -0
- data/lib/rdl/types/nominal.rb +8 -0
- data/lib/rdl/types/non_null.rb +8 -0
- data/lib/rdl/types/optional.rb +8 -0
- data/lib/rdl/types/parser.racc +31 -5
- data/lib/rdl/types/parser.tab.rb +540 -302
- data/lib/rdl/types/rdl_types.rb +45 -0
- data/lib/rdl/types/singleton.rb +14 -1
- data/lib/rdl/types/string.rb +104 -0
- data/lib/rdl/types/structural.rb +8 -0
- data/lib/rdl/types/top.rb +8 -0
- data/lib/rdl/types/tuple.rb +32 -8
- data/lib/rdl/types/type.rb +54 -11
- data/lib/rdl/types/union.rb +41 -2
- data/lib/rdl/types/var.rb +10 -0
- data/lib/rdl/types/vararg.rb +8 -0
- data/lib/rdl/util.rb +13 -10
- data/lib/rdl/wrap.rb +271 -27
- data/lib/rdl_disable.rb +16 -2
- data/lib/types/active_record.rb +1 -0
- data/lib/types/core/array.rb +442 -23
- data/lib/types/core/basic_object.rb +3 -3
- data/lib/types/core/bigdecimal.rb +5 -0
- data/lib/types/core/class.rb +2 -0
- data/lib/types/core/dir.rb +3 -3
- data/lib/types/core/enumerable.rb +4 -4
- data/lib/types/core/enumerator.rb +1 -1
- data/lib/types/core/file.rb +4 -4
- data/lib/types/core/float.rb +203 -0
- data/lib/types/core/hash.rb +390 -15
- data/lib/types/core/integer.rb +223 -10
- data/lib/types/core/io.rb +2 -2
- data/lib/types/core/kernel.rb +8 -5
- data/lib/types/core/marshal.rb +3 -0
- data/lib/types/core/module.rb +3 -3
- data/lib/types/core/numeric.rb +0 -2
- data/lib/types/core/object.rb +5 -5
- data/lib/types/core/pathname.rb +2 -2
- data/lib/types/core/process.rb +1 -3
- data/lib/types/core/range.rb +1 -1
- data/lib/types/core/regexp.rb +2 -2
- data/lib/types/core/set.rb +1 -1
- data/lib/types/core/string.rb +408 -16
- data/lib/types/core/symbol.rb +3 -3
- data/lib/types/core/time.rb +1 -1
- data/lib/types/core/uri.rb +13 -13
- data/lib/types/rails/_helpers.rb +7 -1
- data/lib/types/rails/action_controller/mime_responds.rb +2 -0
- data/lib/types/rails/active_record/associations.rb +42 -30
- data/lib/types/rails/active_record/comp_types.rb +637 -0
- data/lib/types/rails/active_record/finder_methods.rb +1 -1
- data/lib/types/rails/active_record/model_schema.rb +28 -16
- data/lib/types/rails/active_record/relation.rb +5 -3
- data/lib/types/rails/active_record/sql-strings.rb +166 -0
- data/lib/types/rails/string.rb +1 -1
- data/lib/types/sequel.rb +1 -0
- data/lib/types/sequel/comp_types.rb +581 -0
- data/rdl.gemspec +5 -4
- data/test/test_alias.rb +4 -0
- data/test/test_array_types.rb +244 -0
- data/test/test_bound_types.rb +80 -0
- data/test/test_contract.rb +4 -0
- data/test/test_dsl.rb +5 -0
- data/test/test_dyn_comptype_checks.rb +206 -0
- data/test/test_generic.rb +21 -20
- data/test/test_hash_types.rb +322 -0
- data/test/test_intersection.rb +1 -0
- data/test/test_le.rb +29 -4
- data/test/test_member.rb +3 -1
- data/test/test_parser.rb +5 -0
- data/test/test_query.rb +1 -0
- data/test/test_rdl.rb +63 -28
- data/test/test_rdl_type.rb +4 -0
- data/test/test_string_types.rb +102 -0
- data/test/test_type_contract.rb +59 -37
- data/test/test_typecheck.rb +480 -75
- data/test/test_types.rb +17 -0
- data/test/test_wrap.rb +5 -0
- metadata +35 -5
- data/lib/types/rails/active_record/schema_types.rb +0 -51
@@ -1,2 +1,2 @@
|
|
1
1
|
RDL.nowrap :'ActiveRecord::FinderMethods'
|
2
|
-
RDL.type :'ActiveRecord::FinderMethods', :exists?, '(?String or
|
2
|
+
#RDL.type :'ActiveRecord::FinderMethods', :exists?, '(?String or Integer or Array<String> or Hash<String or Symbol, %any>) -> %bool'
|
@@ -1,37 +1,49 @@
|
|
1
|
+
class ActiveRecord::Base
|
2
|
+
extend RDL::RDLAnnotate
|
3
|
+
end
|
4
|
+
|
1
5
|
module ActiveRecord::ModelSchema::ClassMethods
|
2
6
|
extend RDL::RDLAnnotate
|
3
7
|
|
4
8
|
rdl_post(:load_schema!) { |ret| # load_schema! doesn't return anything interesting
|
9
|
+
=begin
|
5
10
|
columns_hash.each { |name, col|
|
6
11
|
t = RDL::Rails.column_to_rdl(col.type)
|
7
12
|
if col.null
|
8
13
|
# may be null; show nullability in return type
|
9
14
|
rdl_type name, "() -> #{t} or nil" # getter
|
10
|
-
rdl_type "#{name}=", "(#{t}) -> #{t} or nil" # setter
|
11
|
-
rdl_type
|
12
|
-
rdl_type
|
13
|
-
rdl_type
|
15
|
+
rdl_type :"#{name}=", "(#{t}) -> #{t} or nil" # setter
|
16
|
+
rdl_type :write_attribute, "(:#{name}, #{t}) -> %bool"
|
17
|
+
rdl_type :update_attribute, "(:#{name}, #{t}) -> %bool"
|
18
|
+
rdl_type :update_column, "(:#{name}, #{t}) -> %bool"
|
14
19
|
else
|
15
20
|
# not null; can't truly check in type system but hint via the name
|
16
21
|
rdl_type name, "() -> !#{t}" # getter
|
17
|
-
rdl_type "#{name}=", "(!#{t}) -> !#{t}" # setter
|
18
|
-
rdl_type
|
19
|
-
rdl_type
|
20
|
-
rdl_type
|
22
|
+
rdl_type :"#{name}=", "(!#{t}) -> !#{t}" # setter
|
23
|
+
rdl_type :write_attribute, "(:#{name}, !#{t}) -> %bool"
|
24
|
+
rdl_type :update_attribute, "(:#{name}, #{t}) -> %bool"
|
25
|
+
rdl_type :update_column, "(:#{name}, #{t}) -> %bool"
|
21
26
|
end
|
22
27
|
}
|
23
28
|
|
24
29
|
attribute_types = RDL::Rails.attribute_types(self)
|
25
|
-
rdl_type 'self.find_by', '(' + attribute_types + ") -> #{self} or nil"
|
26
|
-
rdl_type 'self.find_by!', '(' + attribute_types + ") -> #{self}"
|
27
|
-
rdl_type
|
28
|
-
rdl_type
|
29
|
-
rdl_type 'attributes=', '(' + attribute_types + ') -> %bool'
|
30
|
+
rdl_type :'self.find_by', '(' + attribute_types + ") -> #{self} or nil"
|
31
|
+
rdl_type :'self.find_by!', '(' + attribute_types + ") -> #{self}"
|
32
|
+
rdl_type :update, '(' + attribute_types + ') -> %bool'
|
33
|
+
rdl_type :update_columns, '(' + attribute_types + ') -> %bool'
|
34
|
+
rdl_type :'attributes=', '(' + attribute_types + ') -> %bool'
|
30
35
|
|
31
36
|
# If called with String arguments, can't check types as precisely
|
32
|
-
rdl_type
|
33
|
-
rdl_type
|
34
|
-
rdl_type
|
37
|
+
rdl_type :write_attribute, '(String, %any) -> %bool'
|
38
|
+
rdl_type :update_attribute, '(String, %any) -> %bool'
|
39
|
+
rdl_type :update_column, '(String, %any) -> %bool'
|
40
|
+
|
41
|
+
rdl_type :'self.joins', "(Symbol or String) -> ActiveRecord::Associations::CollectionProxy<#{self.to_s}>"
|
42
|
+
rdl_type :'self.none', "() -> ActiveRecord::Associations::CollectionProxy<#{self.to_s}>"
|
43
|
+
rdl_type :'self.where', '(String, *%any) -> ActiveRecord::Associations::CollectionProxy<t>'
|
44
|
+
rdl_type :'self.where', '(**%any) -> ActiveRecord::Associations::CollectionProxy<t>'
|
45
|
+
=end
|
35
46
|
true
|
36
47
|
}
|
48
|
+
|
37
49
|
end
|
@@ -2,10 +2,12 @@ RDL.nowrap :'ActiveRecord::Relation'
|
|
2
2
|
|
3
3
|
RDL.type_params :'ActiveRecord::Relation', [:t], :all?
|
4
4
|
|
5
|
-
|
5
|
+
=begin
|
6
|
+
RDL.type :'ActiveRecord::Relation', :[], '(Integer) -> t'
|
6
7
|
RDL.type :'ActiveRecord::Relation', :empty?, '() -> %bool'
|
7
8
|
RDL.type :'ActiveRecord::Relation', :first, '() -> t'
|
8
|
-
RDL.type :'ActiveRecord::Relation', :length, '() ->
|
9
|
-
RDL.type :'ActiveRecord::Relation', :sort, '() {(t, t) ->
|
9
|
+
RDL.type :'ActiveRecord::Relation', :length, '() -> Integer'
|
10
|
+
RDL.type :'ActiveRecord::Relation', :sort, '() {(t, t) -> Integer} -> Array<t>'
|
10
11
|
RDL.type :'ActiveRecord::Relation', :each, '() -> Enumerator<t>'
|
11
12
|
RDL.type :'ActiveRecord::Relation', :each, '() { (t) -> %any } -> Array<t>'
|
13
|
+
=end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'sql-parser'
|
2
|
+
|
3
|
+
# Mocking the SQLVistor internal class behavior to do our own stuff
|
4
|
+
class ASTVisitor
|
5
|
+
def initialize(table, targs)
|
6
|
+
@table = table # default table name, if we don't have a qualified column name
|
7
|
+
@targs = targs
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit(node)
|
11
|
+
node.accept(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def binary_op(o)
|
15
|
+
table, column = visit(o.left)
|
16
|
+
ident = visit(o.right)
|
17
|
+
query_type = @targs[ident] || @targs.last
|
18
|
+
schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym]
|
19
|
+
query_type = query_type.elts[column.to_sym] if query_type.is_a? RDL::Type::FiniteHashType
|
20
|
+
# puts query_type, schema_type
|
21
|
+
raise RDL::Typecheck::StaticTypeError, "type error" unless query_type <= schema_type
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :visit_Greater, :binary_op
|
25
|
+
alias_method :visit_Equals, :binary_op
|
26
|
+
alias_method :visit_Less, :binary_op
|
27
|
+
alias_method :visit_GreaterOrEquals, :binary_op
|
28
|
+
|
29
|
+
def visit_And(o)
|
30
|
+
visit(o.left)
|
31
|
+
visit(o.right)
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_Subquery(o)
|
35
|
+
raise RDL::Typecheck::StaticTypeError, "only works with SELECT queries now" unless o.query_specification.is_a? SQLParser::Statement::Select
|
36
|
+
select_query = o.query_specification
|
37
|
+
raise RDL::Typecheck::StaticTypeError, "expected only 1 column in SELECT sub queries" unless select_query.list.is_a? SQLParser::Statement::SelectList and select_query.list.columns.length == 1
|
38
|
+
column = select_query.list.columns[0].name
|
39
|
+
table = select_query.table_expression.from_clause.tables[0].name
|
40
|
+
visitor = ASTVisitor.new table, @targs
|
41
|
+
search_cond = select_query.table_expression.where_clause.search_condition
|
42
|
+
visitor.visit(search_cond)
|
43
|
+
RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym])
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_In(o)
|
47
|
+
table, column = visit(o.left)
|
48
|
+
ident = visit(o.right)
|
49
|
+
# TODO: add a case where ident is an integer and targs doesn't have named params, but just ?-ed params
|
50
|
+
if ident.is_a? Integer and @targs.last.is_a? RDL::Type::FiniteHashType
|
51
|
+
# query_params is a finite hash
|
52
|
+
query_params = @targs.last.elts
|
53
|
+
# this is a hack, assumes keys are in order, which isn't necessarily true
|
54
|
+
query_type = query_params[query_params.keys[ident - 1]]
|
55
|
+
elsif ident.is_a? RDL::Type::GenericType
|
56
|
+
query_type = ident
|
57
|
+
else
|
58
|
+
# puts "(TODO) Unexpected"
|
59
|
+
end
|
60
|
+
|
61
|
+
schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym]
|
62
|
+
# IN works with arrays
|
63
|
+
promoted = if query_type.is_a?(RDL::Type::TupleType) then query_type.promote else query_type end
|
64
|
+
if promoted.is_a? RDL::Type::GenericType
|
65
|
+
# base type is Array, maybe add check?
|
66
|
+
raise RDL::Typecheck::StaticTypeError, "type error" unless promoted.params[0] <= schema_type
|
67
|
+
else
|
68
|
+
raise RDL::Typecheck::StaticTypeError, "some other type after promotion"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_Not(o)
|
73
|
+
visit(o.value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_Integer(o)
|
77
|
+
return o.value
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_QualifiedColumn(o)
|
81
|
+
[o.table.name, o.column.name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit_Column(o)
|
85
|
+
[@table, o.name]
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_InValueList(o)
|
89
|
+
o.values.value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_sql_strings(trec, targs)
|
94
|
+
parser = SQLParser::Parser.new
|
95
|
+
|
96
|
+
case trec
|
97
|
+
when RDL::Type::GenericType
|
98
|
+
if trec.base.klass == ActiveRecord_Relation
|
99
|
+
handle_sql_strings trec.params[0], targs
|
100
|
+
elsif trec.base.klass == JoinTable
|
101
|
+
# works only for the base class right now, need to extend for the params as well
|
102
|
+
base_klass = trec.params[0]
|
103
|
+
joined_with = trec.params[1]
|
104
|
+
case joined_with
|
105
|
+
when RDL::Type::UnionType
|
106
|
+
joined_with.types.each do |klass|
|
107
|
+
# add the joining association column on this
|
108
|
+
sql_query = "SELECT * FROM `#{base_klass.name.tableize}` INNER JOIN `#{klass.name.tableize}` ON a.id = b.a_id WHERE #{build_string_from_precise_string(targs)}"
|
109
|
+
# puts sql_query
|
110
|
+
begin
|
111
|
+
ast = parser.scan_str(sql_query)
|
112
|
+
rescue Racc::ParseError => e
|
113
|
+
# puts "There was a parse error with above query, moving on"
|
114
|
+
return
|
115
|
+
end
|
116
|
+
search_cond = ast.query_expression.table_expression.where_clause.search_condition
|
117
|
+
visitor = ASTVisitor.new base_klass.name.tableize, targs
|
118
|
+
visitor.visit(search_cond)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
# TODO
|
122
|
+
# puts "== TODO =="
|
123
|
+
end
|
124
|
+
else
|
125
|
+
# puts "UNEXPECTED #{trec}, #{targs}"
|
126
|
+
end
|
127
|
+
when RDL::Type::NominalType
|
128
|
+
base_klass = trec
|
129
|
+
sql_query = "SELECT * FROM `#{base_klass.name.tableize}` WHERE #{build_string_from_precise_string(targs)}"
|
130
|
+
# puts sql_query
|
131
|
+
ast = parser.scan_str(sql_query)
|
132
|
+
search_cond = ast.query_expression.table_expression.where_clause.search_condition
|
133
|
+
visitor = ASTVisitor.new base_klass.name.tableize, targs
|
134
|
+
visitor.visit(search_cond)
|
135
|
+
when RDL::Type::SingletonType
|
136
|
+
base_klass = trec
|
137
|
+
sql_query = "SELECT * FROM `#{base_klass.val.to_s.tableize}` WHERE #{build_string_from_precise_string(targs)}"
|
138
|
+
# puts sql_query
|
139
|
+
ast = parser.scan_str(sql_query)
|
140
|
+
search_cond = ast.query_expression.table_expression.where_clause.search_condition
|
141
|
+
visitor = ASTVisitor.new base_klass.val.to_s.tableize, targs
|
142
|
+
visitor.visit(search_cond)
|
143
|
+
else
|
144
|
+
# puts "UNEXPECTED #{trec}, #{targs}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def build_string_from_precise_string(args)
|
149
|
+
str = args[0]
|
150
|
+
raise "Bad type!" unless str.is_a? RDL::Type::PreciseStringType
|
151
|
+
# TODO: handles only non-interpolated strings for now
|
152
|
+
base_query = str.vals[0]
|
153
|
+
|
154
|
+
# Get rid of SQL functions here, that just ends up confusing the parser anyway
|
155
|
+
base_query.gsub!('LOWER(', '(')
|
156
|
+
|
157
|
+
counter = 1
|
158
|
+
if args[1].is_a? RDL::Type::FiniteHashType
|
159
|
+
# the query has named params
|
160
|
+
args[1].elts.keys.each { |k| base_query.gsub!(":#{k}", counter.to_s); counter += 1 }
|
161
|
+
else
|
162
|
+
# the query has ? symbols
|
163
|
+
args[1..-1].each { |t| base_query.sub!('?', counter.to_s); counter += 1}
|
164
|
+
end
|
165
|
+
base_query
|
166
|
+
end
|
data/lib/types/rails/string.rb
CHANGED
data/lib/types/sequel.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + "/sequel/*.rb"].each { |f| require f }
|
@@ -0,0 +1,581 @@
|
|
1
|
+
class SequelDB
|
2
|
+
extend RDL::Annotate
|
3
|
+
|
4
|
+
type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
|
5
|
+
type '[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
|
6
|
+
type :transaction, "() { () -> %any } -> self", wrap: false
|
7
|
+
|
8
|
+
|
9
|
+
type RDL::Globals, 'self.seq_db_schema', "()-> Hash<Symbol, RDL::Type::FiniteHashType>", wrap: false, effect: [:+, :+]
|
10
|
+
|
11
|
+
def self.gen_output_type(targ)
|
12
|
+
case targ
|
13
|
+
when RDL::Type::SingletonType
|
14
|
+
t = RDL::Globals.seq_db_schema[RDL.type_cast(targ.val, "Symbol", force: true)]
|
15
|
+
raise "no schema for table #{targ}" if t.nil?
|
16
|
+
new_t = t.elts.clone.merge({__selected: RDL::Globals.types[:nil], __last_joined: targ, __all_joined: targ, __orm: RDL::Globals.types[:false] })
|
17
|
+
new_fht = RDL::Type::FiniteHashType.new(new_t, nil)
|
18
|
+
return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Table), new_fht)
|
19
|
+
else
|
20
|
+
raise "unexpected type"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
25
|
+
end
|
26
|
+
|
27
|
+
module Sequel::Mysql2; end
|
28
|
+
|
29
|
+
class Sequel::Mysql2::Database
|
30
|
+
extend RDL::Annotate
|
31
|
+
## This class is identical to SequelDB (above), except its name.
|
32
|
+
## Necessary to support different apps.
|
33
|
+
|
34
|
+
type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
|
35
|
+
type '[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
|
36
|
+
type :transaction, "() { () -> %any } -> self", wrap: false
|
37
|
+
|
38
|
+
|
39
|
+
type RDL::Globals, 'self.seq_db_schema', "()-> Hash<Symbol, RDL::Type::FiniteHashType>", wrap: false, effect: [:+, :+]
|
40
|
+
|
41
|
+
def self.gen_output_type(targ)
|
42
|
+
case targ
|
43
|
+
when RDL::Type::SingletonType
|
44
|
+
t = RDL::Globals.seq_db_schema[RDL.type_cast(targ.val, "Symbol", force: true)]
|
45
|
+
raise "no schema for table #{targ}" if t.nil?
|
46
|
+
new_t = t.elts.clone.merge({__selected: RDL::Globals.types[:nil], __last_joined: targ, __all_joined: targ, __orm: RDL::Globals.types[:false] })
|
47
|
+
new_fht = RDL::Type::FiniteHashType.new(new_t, nil)
|
48
|
+
return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Table), new_fht)
|
49
|
+
else
|
50
|
+
raise "unexpected type #{targ.class}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
module Sequel
|
59
|
+
extend RDL::Annotate
|
60
|
+
|
61
|
+
type 'self.sqlite', '() -> DBVal', wrap: false
|
62
|
+
|
63
|
+
type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
|
64
|
+
type 'self.qualify', '(Symbol, Symbol) -> ``qualify_output_type(targs)``', wrap: false
|
65
|
+
|
66
|
+
def self.gen_output_type(targ)
|
67
|
+
case targ
|
68
|
+
when RDL::Type::SingletonType
|
69
|
+
RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqIdent), targ)
|
70
|
+
else
|
71
|
+
raise "unexpected type"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
75
|
+
|
76
|
+
def self.qualify_output_type(targs)
|
77
|
+
raise "unexpected types" unless targs.all? { |a| a.is_a?(RDL::Type::SingletonType) }
|
78
|
+
RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), targs[0], targs[1])
|
79
|
+
end
|
80
|
+
RDL.type Sequel, 'self.qualify_output_type', "(Array<RDL::Type::Type>) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
81
|
+
end
|
82
|
+
class SeqIdent
|
83
|
+
extend RDL::Annotate
|
84
|
+
type_params [:t], :all?
|
85
|
+
type :[], '(Symbol) -> ``gen_output_type(trec, targs[0])``', wrap: false
|
86
|
+
|
87
|
+
def self.gen_output_type(trec, targ)
|
88
|
+
case trec
|
89
|
+
when RDL::Type::GenericType
|
90
|
+
param = trec.params[0]
|
91
|
+
case targ
|
92
|
+
when RDL::Type::SingletonType
|
93
|
+
return RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), param, targ)
|
94
|
+
else
|
95
|
+
raise "expected singleton"
|
96
|
+
end
|
97
|
+
else
|
98
|
+
raise "unexpected trec type"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
class SeqQualIdent
|
106
|
+
extend RDL::Annotate
|
107
|
+
type_params [:table, :column], :all?
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class Table
|
112
|
+
extend RDL::Annotate
|
113
|
+
type_params [:t], :all?
|
114
|
+
|
115
|
+
type :join, "(Symbol, %any) -> ``join_ret_type(trec, targs)``", wrap: false
|
116
|
+
type :join, "(Symbol, %any, %any) -> ``join_ret_type(trec, targs)``", wrap: false
|
117
|
+
|
118
|
+
RDL.rdl_alias :Table, :inner_join, :join
|
119
|
+
RDL.rdl_alias :Table, :left_join, :join
|
120
|
+
RDL.rdl_alias :Table, :left_outer_join, :join
|
121
|
+
|
122
|
+
def self.get_schema(hash)
|
123
|
+
hash.select { |key, val| ![:__last_joined, :__all_joined, :__selected, :__orm].member?(key) }
|
124
|
+
end
|
125
|
+
RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
126
|
+
|
127
|
+
def self.get_all_joined(t)
|
128
|
+
case t
|
129
|
+
when RDL::Type::SingletonType
|
130
|
+
sing = RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true)
|
131
|
+
raise "unexpected type #{t} in __all_joined clause" unless sing.val.is_a?(Symbol)
|
132
|
+
return [sing.val]
|
133
|
+
when RDL::Type::UnionType
|
134
|
+
all = t.types.map { |subt|
|
135
|
+
raise "unexpected type #{subt} in union type within __all_joined clause" unless subt.is_a?(RDL::Type::SingletonType) && RDL.type_cast(subt, "RDL::Type::SingletonType<Symbol>", force: true).val.is_a?(Symbol)
|
136
|
+
RDL.type_cast(subt, "RDL::Type::SingletonType<Symbol>", force: true).val
|
137
|
+
}
|
138
|
+
return all
|
139
|
+
when nil
|
140
|
+
return RDL.type_cast([], "Array<Symbol>", force: true)
|
141
|
+
else
|
142
|
+
raise "unexpected type #{t} in __all_joined clause"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array<Symbol>", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
146
|
+
|
147
|
+
def self.join_ret_type(trec, targs)
|
148
|
+
raise RDL::Typecheck::StaticTypeError, "Unexpected number of arguments to `join`." unless targs.size == 2
|
149
|
+
targ1, targ2 = *targs
|
150
|
+
raise RDL::Typecheck::StaticTypeError, "Unexpected second argument type #{targ2} to `join`." unless targ2.is_a?(RDL::Type::FiniteHashType)
|
151
|
+
arg_join_column = RDL.type_cast(RDL.type_cast(targ2, "RDL::Type::FiniteHashType").elts.keys[0], "Symbol", force: true) ## column name of arg table which is joined on
|
152
|
+
rec_join_column = RDL.type_cast(RDL.type_cast(targ2, "RDL::Type::FiniteHashType").elts[arg_join_column], "Symbol", force: true) ## column name of receiver table which is joined on
|
153
|
+
case trec
|
154
|
+
when RDL::Type::GenericType
|
155
|
+
raise RDL::Typecheck::StaticTypeError, "unexpceted generic type in call to join" unless trec.base.name == "Table"
|
156
|
+
receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
157
|
+
receiver_schema = get_schema(receiver_param)
|
158
|
+
join_source_schema = get_schema(RDL::Globals.seq_db_schema[RDL.type_cast(receiver_param[:__last_joined], "RDL::Type::SingletonType<Symbol>", force: true).val].elts)
|
159
|
+
rec_all_joined = get_all_joined(receiver_param[:__all_joined])
|
160
|
+
|
161
|
+
case rec_join_column
|
162
|
+
when RDL::Type::SingletonType
|
163
|
+
## given symbol for second column to join on
|
164
|
+
if rec_join_column.to_s.include?("__")
|
165
|
+
## qualified column name in old versions of sequel.
|
166
|
+
check_qual_column(rec_join_column, rec_all_joined)
|
167
|
+
else
|
168
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{rec_join_column} for receiver in call to `join`." if join_source_schema[rec_join_column.val].nil?
|
169
|
+
end
|
170
|
+
when RDL::Type::GenericType
|
171
|
+
## given qualified column, e.g. Sequel[:people][:name]
|
172
|
+
raise RDL::Typecheck::StaticTypeError, "unexpected generic type #{rec_join_column}" unless rec_join_column.base.name == "SeqQualIdent"
|
173
|
+
qual_table, qual_column = rec_join_column.params.map { |t| t.val }
|
174
|
+
raise RDL::Typecheck::StaticTypeError, "qualified table #{qual_table} is not joined in receiver table, and so its columns cannot be joined on" unless rec_all_joined.include?(qual_table)
|
175
|
+
qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts)
|
176
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil?
|
177
|
+
else
|
178
|
+
raise "Unexpected column #{rec_join_column} to join on"
|
179
|
+
end
|
180
|
+
case targ1
|
181
|
+
when RDL::Type::SingletonType
|
182
|
+
val = RDL.type_cast(targ1.val, "Symbol", force: true)
|
183
|
+
raise RDL::Typecheck::StaticTypeError, "Expected Symbol for first argument to `join`." unless val.is_a?(Symbol)
|
184
|
+
table_name = val
|
185
|
+
table_schema = RDL::Globals.seq_db_schema[table_name]
|
186
|
+
raise "No schema found for table #{table_name}." unless table_schema
|
187
|
+
arg_schema = get_schema(table_schema.elts) ## look up table schema for argument
|
188
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{arg_join_column} for arg in call to `join`." if arg_schema[arg_join_column].nil?
|
189
|
+
result_schema = receiver_schema.merge(arg_schema).merge({ __all_joined: RDL::Type::UnionType.new(*[receiver_param[:__all_joined], targ1]), __last_joined: targ1, __selected: receiver_param[:__selected], __orm: receiver_param[:__orm] }) ## resulting schema as hash
|
190
|
+
result_fht = RDL::Type::FiniteHashType.new(result_schema, nil) ## resulting schema as FiniteHashType
|
191
|
+
|
192
|
+
return RDL::Type::GenericType.new(trec.base, result_fht)
|
193
|
+
when RDL::Type::NominalType
|
194
|
+
## TODO: this will catch case that first argument is a non-singleton Symbol
|
195
|
+
raise "not implemented, likely not needed in practice"
|
196
|
+
else
|
197
|
+
raise RDL::Typecheck::StaticTypeError, "Unexpected type of first argument to `join`."
|
198
|
+
end
|
199
|
+
when RDL::Type::NominalType
|
200
|
+
raise RDL::Typecheck::StaticTypeError unless trec.name == "Table"
|
201
|
+
else
|
202
|
+
raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type in call to `join`."
|
203
|
+
end
|
204
|
+
end
|
205
|
+
RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
206
|
+
|
207
|
+
|
208
|
+
type :insert, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false
|
209
|
+
type :insert, "(``insert_arg_type(trec, targs, true)``, %any) -> Integer", wrap: false
|
210
|
+
type :where, "(``where_arg_type(trec, targs)``) -> self", wrap: false
|
211
|
+
type :where, "(``where_arg_type(trec, targs, true)``, %any) -> self", wrap: false
|
212
|
+
type :exclude, "(``where_arg_type(trec, targs)``) -> self", wrap: false
|
213
|
+
type :exclude, "(``where_arg_type(trec, targs, true)``, %any) -> self", wrap: false
|
214
|
+
type :[], "(``where_arg_type(trec, targs)``) -> ``first_output(trec)``", wrap: false
|
215
|
+
type :first, "() -> ``first_output(trec)``", wrap: false
|
216
|
+
type :first, "(``if targs[0] then where_arg_type(trec, targs) else RDL::Globals.types[:bot] end``) -> ``first_output(trec)``", wrap: false
|
217
|
+
type :get, '(``get_input(trec)``) -> ``get_output(trec, targs)``', wrap: false
|
218
|
+
type :order, '(``order_input(trec, targs)``) -> self', wrap: false
|
219
|
+
type Sequel, 'self.desc', '(%any) -> ``targs[0]``', wrap: false ## args will ultimately be checked by `order`
|
220
|
+
type :select_map, '(Symbol) -> ``select_map_output(trec, targs, :select_map)``', wrap: false
|
221
|
+
type :pluck, '(Symbol) -> ``select_map_output(trec, targs, :select_map)``', wrap: false
|
222
|
+
type :any?, "() -> %bool", wrap: false
|
223
|
+
type :select, "(*%any) -> ``select_map_output(trec, targs, :select)``", wrap: false
|
224
|
+
type :all, "() -> ``all_output(trec)``", wrap: false
|
225
|
+
type Sequel, 'self.lit', "(%any) -> String", wrap: false
|
226
|
+
type :server, "(Symbol) -> self", wrap: false
|
227
|
+
type :empty?, '() -> %bool', wrap: false
|
228
|
+
type :update, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false
|
229
|
+
type :count, "() -> Integer", wrap: false
|
230
|
+
type :map, "() { (``map_block_input(trec)``) -> x } -> Array<x>", wrap: false
|
231
|
+
type :each, "() { (``map_block_input(trec)``) -> x } -> self", wrap: false
|
232
|
+
type :import, "(``import_arg_type(trec, targs)``, Array<u>) -> Array<String>", wrap: false
|
233
|
+
|
234
|
+
def self.order_input(trec, targs)
|
235
|
+
case trec
|
236
|
+
when RDL::Type::GenericType
|
237
|
+
trp0 = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true)
|
238
|
+
sym_keys = get_schema(trp0.elts).keys
|
239
|
+
all_joined = get_all_joined(trp0.elts[:__all_joined])
|
240
|
+
targs.each { |a|
|
241
|
+
case a
|
242
|
+
when RDL::Type::SingletonType
|
243
|
+
return RDL::Globals.types[:bot] unless sym_keys.include?(a.val)
|
244
|
+
when RDL::Type::GenericType
|
245
|
+
return RDL::Globals.types[:bot] unless a.base.name == "SeqQualIdent"
|
246
|
+
check_qual_column(a, all_joined)
|
247
|
+
end
|
248
|
+
}
|
249
|
+
return RDL::Type::VarargType.new(RDL::Type::UnionType.new(*targs))
|
250
|
+
else
|
251
|
+
raise "unexpected type #{trec}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
255
|
+
|
256
|
+
def self.map_block_input(trec)
|
257
|
+
schema = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts)
|
258
|
+
RDL::Type::FiniteHashType.new(schema, nil)
|
259
|
+
end
|
260
|
+
RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
261
|
+
|
262
|
+
def self.all_output(trec)
|
263
|
+
f = first_output(trec)
|
264
|
+
if f.is_a?(RDL::Type::FiniteHashType)
|
265
|
+
trp0 = RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true)
|
266
|
+
selected = trp0.elts[:__selected]
|
267
|
+
all_joined = get_all_joined(trp0.elts[:__all_joined])
|
268
|
+
if !(selected == RDL::Globals.types[:nil])
|
269
|
+
## something is selected
|
270
|
+
sel_arr = RDL.type_cast(selected.is_a?(RDL::Type::UnionType) ? RDL.type_cast(selected, "RDL::Type::UnionType").types : [selected], "Array<RDL::Type::SingletonType<Symbol>>", force: true)
|
271
|
+
new_hash = Hash[sel_arr.map { |sel|
|
272
|
+
if sel.val.to_s.include?("__")
|
273
|
+
t = check_qual_column(sel.val, all_joined)
|
274
|
+
_, col_name = sel.val.to_s.split "__"
|
275
|
+
col_name = col_name.to_sym
|
276
|
+
[col_name, t]
|
277
|
+
else
|
278
|
+
raise "no selected column found" unless (t = RDL.type_cast(f, "RDL::Type::FiniteHashType", force: true).elts[sel.val])
|
279
|
+
[sel.val, t]
|
280
|
+
end
|
281
|
+
}]
|
282
|
+
new_hash_type = RDL::Type::FiniteHashType.new(RDL.type_cast(new_hash, "Hash<%any, RDL::Type::Type>", force: true), nil)
|
283
|
+
return RDL::Type::GenericType.new(RDL::Globals.types[:array], new_hash_type)
|
284
|
+
else
|
285
|
+
return RDL::Type::GenericType.new(RDL::Globals.types[:array], f)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
return RDL::Type::GenericType.new(RDL::Globals.types[:array], f)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
292
|
+
|
293
|
+
def self.select_map_output(trec, targs, meth)
|
294
|
+
case trec
|
295
|
+
when RDL::Type::GenericType
|
296
|
+
raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
|
297
|
+
receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
298
|
+
all_joined = get_all_joined(receiver_param[:__all_joined])
|
299
|
+
|
300
|
+
map_types = targs.map { |arg|
|
301
|
+
case arg
|
302
|
+
when RDL::Type::SingletonType
|
303
|
+
column = RDL.type_cast(arg.val, "Symbol", force: true)
|
304
|
+
raise "unexpected arg type #{arg}" unless column.is_a?(Symbol)
|
305
|
+
raise "Ambiguous column identifier #{arg}." unless unique_ids?([column], receiver_param[:__all_joined])
|
306
|
+
if column.to_s.include?("__")
|
307
|
+
check_qual_column(column, all_joined)
|
308
|
+
else
|
309
|
+
raise "No column #{column} in receiver table." unless receiver_param[column]
|
310
|
+
receiver_param[column]
|
311
|
+
end
|
312
|
+
when RDL::Type::GenericType
|
313
|
+
raise "unexpected arg type #{arg}" unless arg.base.name == "SeqQualIdent"
|
314
|
+
check_qual_column(arg, all_joined)
|
315
|
+
else
|
316
|
+
raise "unexpected arg type #{arg}"
|
317
|
+
end
|
318
|
+
}
|
319
|
+
|
320
|
+
targs.each { |arg|
|
321
|
+
case arg
|
322
|
+
when RDL::Type::SingletonType
|
323
|
+
column = RDL.type_cast(arg.val, "Symbol", force: true)
|
324
|
+
raise "unexpected arg type #{arg}" unless column.is_a?(Symbol)
|
325
|
+
raise "Ambiguous column identifier #{arg}." unless unique_ids?([column], receiver_param[:__all_joined])
|
326
|
+
if column.to_s.include?("__")
|
327
|
+
map_types = map_types + [check_qual_column(column, all_joined)]
|
328
|
+
else
|
329
|
+
raise "No column #{column} in receiver table." unless receiver_param[column]
|
330
|
+
map_types = map_types + [receiver_param[column]]
|
331
|
+
end
|
332
|
+
when RDL::Type::GenericType
|
333
|
+
raise "unexpected arg type #{arg}" unless arg.base.name == "SeqQualIdent"
|
334
|
+
map_types = map_types + [check_qual_column(arg, all_joined)]
|
335
|
+
else
|
336
|
+
raise "unexpected arg type #{arg}"
|
337
|
+
end
|
338
|
+
}
|
339
|
+
if meth == :select
|
340
|
+
result_schema = receiver_param.clone.merge({ __selected: RDL::Type::UnionType.new(*targs).canonical })
|
341
|
+
return RDL::Type::GenericType.new(trec.base, RDL::Type::FiniteHashType.new(result_schema, nil))
|
342
|
+
elsif meth == :select_map
|
343
|
+
if targs.size >1
|
344
|
+
return RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::TupleType.new(*map_types))
|
345
|
+
else
|
346
|
+
return RDL::Type::GenericType.new(RDL::Globals.types[:array], map_types[0])
|
347
|
+
end
|
348
|
+
else
|
349
|
+
raise 'unexpected'
|
350
|
+
end
|
351
|
+
else
|
352
|
+
raise 'unexpected type #{trec}'
|
353
|
+
end
|
354
|
+
end
|
355
|
+
RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
356
|
+
|
357
|
+
def self.get_input(trec)
|
358
|
+
case trec
|
359
|
+
when RDL::Type::GenericType
|
360
|
+
sym_keys = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts).keys
|
361
|
+
RDL::Type::UnionType.new(*RDL.type_cast(sym_keys.map { |k| RDL::Type::SingletonType.new(k) }, "Array<RDL::Type::Type>"))
|
362
|
+
else
|
363
|
+
raise 'unexpected type #{trec}'
|
364
|
+
end
|
365
|
+
end
|
366
|
+
RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
367
|
+
|
368
|
+
def self.get_output(trec, targs)
|
369
|
+
RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[RDL.type_cast(targs[0], "RDL::Type::SingletonType<Symbol>", force: true).val]
|
370
|
+
end
|
371
|
+
RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
372
|
+
|
373
|
+
def self.first_output(trec)
|
374
|
+
case trec
|
375
|
+
when RDL::Type::GenericType
|
376
|
+
raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
|
377
|
+
receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
378
|
+
if !(receiver_param[:__orm] == RDL::Globals.types[:false])
|
379
|
+
receiver_param[:__orm]
|
380
|
+
else
|
381
|
+
RDL::Type::FiniteHashType.new(get_schema(receiver_param), nil)
|
382
|
+
end
|
383
|
+
else
|
384
|
+
raise 'unexpected type #{trec}'
|
385
|
+
end
|
386
|
+
end
|
387
|
+
RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
388
|
+
|
389
|
+
def self.insert_arg_type(trec, targs, tuple=false)
|
390
|
+
raise "Cannot insert/update for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType)
|
391
|
+
if tuple
|
392
|
+
schema_arg_tuple_type(trec, targs, :insert)
|
393
|
+
else
|
394
|
+
schema_arg_type(trec, targs, :insert)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
398
|
+
|
399
|
+
def self.import_arg_type(trec, targs)
|
400
|
+
raise "Cannot import for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType)
|
401
|
+
raise "Expected tuple for first arg to `import`, got #{targs[0]} instead." unless targs[0].is_a?(RDL::Type::TupleType)
|
402
|
+
arg1 = targs[1]
|
403
|
+
case arg1
|
404
|
+
when RDL::Type::TupleType
|
405
|
+
arg1.params.each { |t| schema_arg_tuple_type(trec, [targs[0], t], :import) } ## check each individual tuple inside second arg tuple
|
406
|
+
when RDL::Type::GenericType
|
407
|
+
raise "expected Array, got #{arg1}" unless (arg1.base == RDL::Globals.types[:array])
|
408
|
+
raise "`import` type not yet implemented for type #{arg1}" unless arg1.params[0].is_a?(RDL::Type::TupleType)
|
409
|
+
schema_arg_tuple_type(trec, [targs[0], arg1.params[0]], :import)
|
410
|
+
else
|
411
|
+
raise "Not yet implemented for type #{arg1}."
|
412
|
+
end
|
413
|
+
return targs[0]
|
414
|
+
end
|
415
|
+
RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
416
|
+
|
417
|
+
def self.get_nominal_where_type(type)
|
418
|
+
## `where` can accept arrays/tuples and tables with a single column selected
|
419
|
+
## this method just extracts the parameter type
|
420
|
+
case type
|
421
|
+
when RDL::Type::GenericType
|
422
|
+
if type.base == RDL::Globals.types[:array]
|
423
|
+
type.params[0]
|
424
|
+
elsif type.base == RDL::Type::NominalType.new(Table)
|
425
|
+
schema = RDL.type_cast(type.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
426
|
+
sel = schema[:__selected]
|
427
|
+
raise "Call to where expects table with a single column selected, got #{type}" unless sel.is_a?(RDL::Type::SingletonType)
|
428
|
+
nominal = schema[RDL.type_cast(sel, "RDL::Type::SingletonType", force: true).val]
|
429
|
+
raise "No type found for column #{sel} in call to `where`." unless nominal
|
430
|
+
nominal
|
431
|
+
else
|
432
|
+
type
|
433
|
+
end
|
434
|
+
when RDL::Type::TupleType
|
435
|
+
type = type.promote.params[0]
|
436
|
+
raise "`where` passed tuple containing different types." if type.is_a?(RDL::Type::UnionType)
|
437
|
+
type
|
438
|
+
else
|
439
|
+
type
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
444
|
+
|
445
|
+
def self.schema_arg_type(trec, targs, meth)
|
446
|
+
return RDL::Type::NominalType.new(Hash) if targs.size != 1
|
447
|
+
case trec
|
448
|
+
when RDL::Type::GenericType
|
449
|
+
raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
|
450
|
+
receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
451
|
+
receiver_schema = get_schema(receiver_param)
|
452
|
+
all_joined = get_all_joined(receiver_param[:__all_joined])
|
453
|
+
arg0 = targs[0]
|
454
|
+
case arg0
|
455
|
+
when RDL::Type::FiniteHashType
|
456
|
+
insert_hash = arg0.elts
|
457
|
+
insert_hash.each { |column_name, type|
|
458
|
+
cn = RDL.type_cast(column_name, "Symbol", force: true)
|
459
|
+
if cn.to_s.include?("__") && (meth == :where)
|
460
|
+
check_qual_column(cn, all_joined, type)
|
461
|
+
else
|
462
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver #{trec}." unless receiver_schema.has_key?(cn)
|
463
|
+
type = get_nominal_where_type(type) if (meth == :where)
|
464
|
+
raise RDL::Typecheck::StaticTypeError, "Incompatible column types #{type} and #{receiver_schema[column_name]} for column #{cn} in call to #{meth}." unless RDL::Type::Type.leq(type, receiver_schema[cn])
|
465
|
+
end
|
466
|
+
}
|
467
|
+
return arg0
|
468
|
+
else
|
469
|
+
return arg0 if (meth==:where) && targs[0] <= RDL::Globals.types[:string]
|
470
|
+
raise "TODO WITH #{trec} AND #{targs} AND #{meth}"
|
471
|
+
end
|
472
|
+
when RDL::Type::NominalType
|
473
|
+
##TODO
|
474
|
+
else
|
475
|
+
|
476
|
+
end
|
477
|
+
end
|
478
|
+
RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
479
|
+
|
480
|
+
## [+ column_name +] if a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age
|
481
|
+
## [+ all_joined +] is an array of symbols of joined tables (must check if qualifying table is a member)
|
482
|
+
## [+ type +] is optional RDL type. If given, we check that it matches the type of the column in the schema.
|
483
|
+
## returns type of given column
|
484
|
+
def self.check_qual_column(column_name, all_joined, type=nil)
|
485
|
+
case column_name
|
486
|
+
when RDL::Type::GenericType
|
487
|
+
cn = RDL.type_cast(column_name, "RDL::Type::GenericType", force: true)
|
488
|
+
raise "Expected qualified column type." unless cn.base.name == "SeqQualIdent"
|
489
|
+
qual_table, qual_column = cn.params.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true).val }
|
490
|
+
else
|
491
|
+
## symbol with name including underscores
|
492
|
+
qual_table, qual_column = RDL.type_cast(column_name, "Symbol", force: true).to_s.split "__"
|
493
|
+
qual_table = if qual_table.start_with?(":") then qual_table[1..-1].to_sym else qual_table.to_sym end
|
494
|
+
qual_column = qual_column.to_sym
|
495
|
+
end
|
496
|
+
raise RDL::Typecheck::StaticTypeError, "qualified table #{qual_table} is not joined in receiver table, cannot reference its columns" unless all_joined.include?(qual_table)
|
497
|
+
qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts)
|
498
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil?
|
499
|
+
if type
|
500
|
+
types = (if type.is_a?(RDL::Type::UnionType) then RDL.type_cast(type, "RDL::Type::UnionType", force: true).types else [type] end)
|
501
|
+
types.each { |t|
|
502
|
+
t = RDL.type_cast(t, "RDL::Type::GenericType", force: true).params[0] if t.is_a?(RDL::Type::GenericType) && (RDL.type_cast(t, "RDL::Type::GenericType", force: true).base == RDL::Globals.types[:array]) ## only happens if meth is where, don't need to check
|
503
|
+
raise RDL::Typecheck::StaticTypeError, "Incompatible column types. Given #{t} but expected #{qual_table_schema[qual_column]} for column #{column_name}." unless RDL::Type::Type.leq(t, qual_table_schema[qual_column])
|
504
|
+
}
|
505
|
+
end
|
506
|
+
return qual_table_schema[qual_column]
|
507
|
+
end
|
508
|
+
RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array<Symbol>, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
509
|
+
|
510
|
+
def self.schema_arg_tuple_type(trec, targs, meth)
|
511
|
+
return RDL::Type::NominalType.new(Array) if targs.size != 2
|
512
|
+
case trec
|
513
|
+
when RDL::Type::GenericType
|
514
|
+
raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
|
515
|
+
receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
|
516
|
+
all_joined = get_all_joined(receiver_param[:__all_joined])
|
517
|
+
receiver_schema = get_schema(receiver_param)
|
518
|
+
if targs[0].is_a?(RDL::Type::TupleType) && targs[1].is_a?(RDL::Type::TupleType)
|
519
|
+
RDL.type_cast(targs[0], "RDL::Type::TupleType", force: true).params.each_with_index { |column_name, i|
|
520
|
+
cn = RDL.type_cast(column_name, "RDL::Type::SingletonType<Symbol>", force: true)
|
521
|
+
raise "Expected singleton symbol in call to insert, got #{column_name}" unless cn.is_a?(RDL::Type::SingletonType) && cn.val.is_a?(Symbol)
|
522
|
+
type = RDL.type_cast(targs[1], "RDL::Type::TupleType", force: true).params[i]
|
523
|
+
if cn.val.to_s.include?("__") && (meth == :where)
|
524
|
+
check_qual_column(cn.val, all_joined, type)
|
525
|
+
else
|
526
|
+
raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver in call to `insert`." unless receiver_schema.has_key?(cn.val)
|
527
|
+
type = get_nominal_where_type(type) if (meth == :where)
|
528
|
+
raise RDL::Typecheck::StaticTypeError, "Incompatible column types." unless RDL::Type::Type.leq(type, receiver_schema[cn.val])
|
529
|
+
end
|
530
|
+
}
|
531
|
+
return targs[0]
|
532
|
+
else
|
533
|
+
raise "not yet implemented for types #{targs[0]} and #{targs[1]}"
|
534
|
+
end
|
535
|
+
else
|
536
|
+
raise 'not yet implemented'
|
537
|
+
end
|
538
|
+
end
|
539
|
+
RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
540
|
+
|
541
|
+
def self.where_arg_type(trec, targs, tuple=false)
|
542
|
+
trp0 = RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true)
|
543
|
+
if trp0.elts[:__all_joined].is_a?(RDL::Type::UnionType)
|
544
|
+
arg0 = targs[0]
|
545
|
+
case arg0
|
546
|
+
when RDL::Type::TupleType
|
547
|
+
raise "Unexpected column type." unless arg0.params.all? { |t| t.is_a?(RDL::Type::SingletonType) && RDL.type_cast(t, "RDL::Type::SingletonType<Object>", force: true).val.is_a?(Symbol) }
|
548
|
+
raise "Ambigious identifier in call to where." unless unique_ids?(arg0.params.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true).val }, trp0.elts[:__all_joined])
|
549
|
+
when RDL::Type::FiniteHashType
|
550
|
+
raise "Ambigious identifier in call to where." unless unique_ids?(RDL.type_cast(arg0.elts.keys, "Array<Symbol>", force: true), trp0.elts[:__all_joined])
|
551
|
+
else
|
552
|
+
raise "unexpected arg type #{arg0}"
|
553
|
+
end
|
554
|
+
end
|
555
|
+
if tuple
|
556
|
+
schema_arg_tuple_type(trec, targs, :where)
|
557
|
+
else
|
558
|
+
schema_arg_type(trec, targs, :where)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
562
|
+
|
563
|
+
def self.unique_ids?(ids, joined)
|
564
|
+
j = get_all_joined(joined)
|
565
|
+
count = Hash[ids.map { |id| [id, 0] }]
|
566
|
+
|
567
|
+
j.each { |t1|
|
568
|
+
schema1 = RDL::Globals.seq_db_schema[t1]
|
569
|
+
raise "schema not found" unless schema1
|
570
|
+
j.each { |t2|
|
571
|
+
schema2 = RDL::Globals.seq_db_schema[t2]
|
572
|
+
ids.each { |id|
|
573
|
+
return false if schema1.elts.has_key?(id) && schema2.elts.has_key?(id) && (t1 != t2)
|
574
|
+
}
|
575
|
+
}
|
576
|
+
}
|
577
|
+
return true
|
578
|
+
end
|
579
|
+
RDL.type Table, 'self.unique_ids?', "(Array<Symbol>, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false, effect: [:+, :+]
|
580
|
+
|
581
|
+
end
|